summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rw-r--r--contrib/blameview/README9
-rwxr-xr-xcontrib/blameview/blameview.perl155
-rw-r--r--contrib/buildsystems/CMakeLists.txt1046
-rw-r--r--contrib/buildsystems/Generators.pm2
-rw-r--r--contrib/buildsystems/Generators/Vcproj.pm119
-rw-r--r--contrib/buildsystems/Generators/Vcxproj.pm393
-rwxr-xr-x[-rw-r--r--]contrib/buildsystems/engine.pl59
-rwxr-xr-x[-rw-r--r--]contrib/buildsystems/generate0
-rwxr-xr-x[-rw-r--r--]contrib/buildsystems/parse.pl0
-rw-r--r--contrib/ciabot/INSTALL54
-rw-r--r--contrib/ciabot/README11
-rwxr-xr-xcontrib/ciabot/ciabot.py249
-rwxr-xr-xcontrib/ciabot/ciabot.sh233
-rw-r--r--contrib/coccinelle/.gitignore1
-rw-r--r--contrib/coccinelle/README43
-rw-r--r--contrib/coccinelle/array.cocci98
-rw-r--r--contrib/coccinelle/commit.cocci52
-rw-r--r--contrib/coccinelle/flex_alloc.cocci13
-rw-r--r--contrib/coccinelle/free.cocci18
-rw-r--r--contrib/coccinelle/hashmap.cocci16
-rw-r--r--contrib/coccinelle/object_id.cocci87
-rw-r--r--contrib/coccinelle/preincr.cocci5
-rw-r--r--contrib/coccinelle/qsort.cocci37
-rw-r--r--contrib/coccinelle/strbuf.cocci62
-rw-r--r--contrib/coccinelle/swap.cocci28
-rw-r--r--contrib/coccinelle/the_repository.pending.cocci144
-rw-r--r--contrib/coccinelle/xcalloc.cocci10
-rw-r--r--contrib/coccinelle/xopen.cocci19
-rw-r--r--contrib/coccinelle/xstrdup_or_null.cocci13
-rw-r--r--contrib/completion/.gitattributes1
-rw-r--r--contrib/completion/git-completion.bash3167
-rw-r--r--contrib/completion/git-completion.tcsh21
-rw-r--r--contrib/completion/git-completion.zsh270
-rw-r--r--contrib/completion/git-prompt.sh505
-rw-r--r--contrib/contacts/.gitignore3
-rw-r--r--contrib/contacts/Makefile71
-rwxr-xr-xcontrib/contacts/git-contacts203
-rw-r--r--contrib/contacts/git-contacts.txt94
-rw-r--r--contrib/continuous/cidaemon503
-rw-r--r--contrib/continuous/post-receive-cinotify104
-rw-r--r--contrib/convert-objects/convert-objects.c329
-rw-r--r--contrib/convert-objects/git-convert-objects.txt29
-rwxr-xr-xcontrib/coverage-diff.sh108
-rw-r--r--contrib/credential/gnome-keyring/Makefile5
-rw-r--r--contrib/credential/gnome-keyring/git-credential-gnome-keyring.c345
-rw-r--r--contrib/credential/libsecret/Makefile25
-rw-r--r--contrib/credential/libsecret/git-credential-libsecret.c369
-rw-r--r--contrib/credential/netrc/.gitignore1
-rw-r--r--contrib/credential/netrc/Makefile30
-rwxr-xr-xcontrib/credential/netrc/git-credential-netrc.perl440
-rwxr-xr-xcontrib/credential/netrc/t-git-credential-netrc.sh32
-rwxr-xr-xcontrib/credential/netrc/test.command-option-gpg2
-rwxr-xr-xcontrib/credential/netrc/test.git-config-gpg2
-rw-r--r--contrib/credential/netrc/test.netrc13
-rw-r--r--contrib/credential/netrc/test.netrc.gpg0
-rwxr-xr-xcontrib/credential/netrc/test.pl139
-rw-r--r--contrib/credential/osxkeychain/git-credential-osxkeychain.c15
-rw-r--r--contrib/credential/wincred/Makefile16
-rw-r--r--contrib/credential/wincred/git-credential-wincred.c233
-rw-r--r--contrib/diff-highlight/.gitignore2
-rw-r--r--contrib/diff-highlight/DiffHighlight.pm285
-rw-r--r--contrib/diff-highlight/Makefile23
-rw-r--r--contrib/diff-highlight/README71
-rwxr-xr-xcontrib/diff-highlight/diff-highlight173
-rw-r--r--contrib/diff-highlight/diff-highlight.perl8
-rw-r--r--contrib/diff-highlight/t/.gitignore2
-rw-r--r--contrib/diff-highlight/t/Makefile22
-rwxr-xr-xcontrib/diff-highlight/t/t9400-diff-highlight.sh341
-rw-r--r--contrib/diffall/README31
-rwxr-xr-xcontrib/diffall/git-diffall257
-rw-r--r--contrib/emacs/.gitignore1
-rw-r--r--contrib/emacs/Makefile21
-rw-r--r--contrib/emacs/README32
-rw-r--r--contrib/emacs/git-blame.el490
-rw-r--r--contrib/emacs/git.el1711
-rw-r--r--contrib/examples/README23
-rw-r--r--contrib/examples/builtin-fetch--tool.c574
-rwxr-xr-xcontrib/examples/git-checkout.sh302
-rwxr-xr-xcontrib/examples/git-clean.sh118
-rwxr-xr-xcontrib/examples/git-clone.sh525
-rwxr-xr-xcontrib/examples/git-commit.sh639
-rwxr-xr-xcontrib/examples/git-fetch.sh379
-rwxr-xr-xcontrib/examples/git-gc.sh37
-rwxr-xr-xcontrib/examples/git-ls-remote.sh142
-rwxr-xr-xcontrib/examples/git-merge-ours.sh14
-rwxr-xr-xcontrib/examples/git-merge.sh620
-rwxr-xr-xcontrib/examples/git-notes.sh121
-rwxr-xr-xcontrib/examples/git-remote.perl474
-rwxr-xr-xcontrib/examples/git-rerere.perl284
-rwxr-xr-xcontrib/examples/git-reset.sh106
-rwxr-xr-xcontrib/examples/git-resolve.sh112
-rwxr-xr-xcontrib/examples/git-revert.sh207
-rwxr-xr-xcontrib/examples/git-svnimport.perl976
-rw-r--r--contrib/examples/git-svnimport.txt179
-rwxr-xr-xcontrib/examples/git-tag.sh205
-rwxr-xr-xcontrib/examples/git-verify-tag.sh45
-rwxr-xr-xcontrib/fast-import/git-import.perl2
-rwxr-xr-xcontrib/fast-import/git-import.sh2
-rwxr-xr-xcontrib/fast-import/import-directories.perl7
-rwxr-xr-xcontrib/fast-import/import-tars.perl58
-rwxr-xr-xcontrib/fast-import/import-zips.py99
-rw-r--r--contrib/git-jump/README28
-rwxr-xr-xcontrib/git-jump/git-jump15
-rwxr-xr-xcontrib/git-resurrect.sh18
-rwxr-xr-xcontrib/gitview/gitview1305
-rw-r--r--contrib/gitview/gitview.txt57
-rwxr-xr-xcontrib/hg-to-git/hg-to-git.py60
-rw-r--r--contrib/hooks/multimail/README.Git7
-rwxr-xr-xcontrib/hooks/post-receive-email36
-rwxr-xr-x[-rw-r--r--]contrib/hooks/pre-auto-gc-battery5
-rwxr-xr-x[-rw-r--r--]contrib/hooks/setgitperms.perl2
-rwxr-xr-x[-rw-r--r--]contrib/hooks/update-paranoid2
-rwxr-xr-xcontrib/long-running-filter/example.pl132
-rw-r--r--contrib/mw-to-git/.gitignore2
-rw-r--r--contrib/mw-to-git/.perlcriticrc28
-rw-r--r--contrib/mw-to-git/Git/Mediawiki.pm101
-rw-r--r--contrib/mw-to-git/Makefile81
-rwxr-xr-xcontrib/mw-to-git/bin-wrapper/git14
-rwxr-xr-xcontrib/mw-to-git/git-mw.perl368
-rwxr-xr-xcontrib/mw-to-git/git-remote-mediawiki.perl (renamed from contrib/mw-to-git/git-remote-mediawiki)766
-rw-r--r--contrib/mw-to-git/git-remote-mediawiki.txt2
-rw-r--r--contrib/mw-to-git/t/.gitignore2
-rw-r--r--contrib/mw-to-git/t/README18
-rwxr-xr-xcontrib/mw-to-git/t/install-wiki.sh12
-rw-r--r--contrib/mw-to-git/t/install-wiki/.gitignore1
-rw-r--r--contrib/mw-to-git/t/install-wiki/LocalSettings.php129
-rw-r--r--contrib/mw-to-git/t/install-wiki/db_install.php120
-rwxr-xr-xcontrib/mw-to-git/t/t9360-mw-to-git-clone.sh28
-rwxr-xr-xcontrib/mw-to-git/t/t9362-mw-to-git-utf8.sh22
-rwxr-xr-xcontrib/mw-to-git/t/t9363-mw-to-git-export-import.sh28
-rwxr-xr-xcontrib/mw-to-git/t/t9365-continuing-queries.sh23
-rwxr-xr-xcontrib/mw-to-git/t/test-gitmw-lib.sh189
-rwxr-xr-xcontrib/mw-to-git/t/test-gitmw.pl22
-rw-r--r--contrib/mw-to-git/t/test.config23
-rw-r--r--contrib/p4import/README1
-rw-r--r--contrib/p4import/git-p4import.py360
-rw-r--r--contrib/p4import/git-p4import.txt167
-rw-r--r--contrib/patches/docbook-xsl-manpages-charmap.patch21
-rw-r--r--contrib/persistent-https/Makefile6
-rw-r--r--contrib/persistent-https/README10
-rw-r--r--contrib/remote-helpers/Makefile13
-rw-r--r--contrib/remote-helpers/README15
-rwxr-xr-xcontrib/remote-helpers/git-remote-bzr11
-rwxr-xr-xcontrib/remote-helpers/git-remote-hg805
-rwxr-xr-xcontrib/remote-helpers/test-hg-bidi.sh243
-rwxr-xr-xcontrib/remote-helpers/test-hg-hg-git.sh466
-rwxr-xr-xcontrib/remote-helpers/test-hg.sh121
-rwxr-xr-xcontrib/rerere-train.sh58
-rw-r--r--contrib/subtree/.gitignore3
-rw-r--r--contrib/subtree/Makefile89
-rwxr-xr-xcontrib/subtree/git-subtree.sh1111
-rw-r--r--contrib/subtree/git-subtree.txt309
-rw-r--r--contrib/subtree/t/Makefile31
-rwxr-xr-xcontrib/subtree/t/t7900-subtree.sh1726
-rw-r--r--contrib/subtree/todo8
-rw-r--r--contrib/svn-fe/.gitignore4
-rw-r--r--contrib/svn-fe/Makefile63
-rw-r--r--contrib/svn-fe/svn-fe.c18
-rw-r--r--contrib/svn-fe/svn-fe.txt71
-rwxr-xr-xcontrib/svn-fe/svnrdump_sim.py53
-rwxr-xr-xcontrib/thunderbird-patch-inline/appp.sh18
-rw-r--r--contrib/update-unicode/.gitignore3
-rw-r--r--contrib/update-unicode/README20
-rwxr-xr-xcontrib/update-unicode/update_unicode.sh33
-rw-r--r--contrib/vim/README22
-rw-r--r--contrib/vscode/.gitattributes1
-rw-r--r--contrib/vscode/README.md14
-rwxr-xr-xcontrib/vscode/init.sh375
-rw-r--r--contrib/workdir/.gitattributes1
-rwxr-xr-xcontrib/workdir/git-new-workdir53
170 files changed, 11943 insertions, 17797 deletions
diff --git a/contrib/blameview/README b/contrib/blameview/README
deleted file mode 100644
index fada5ce909..0000000000
--- a/contrib/blameview/README
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a sample program to use 'git-blame --incremental', based
-on this message.
-
-From: Jeff King <peff@peff.net>
-Subject: Re: More precise tag following
-To: Linus Torvalds <torvalds@linux-foundation.org>
-Cc: git@vger.kernel.org
-Date: Sat, 27 Jan 2007 18:52:38 -0500
-Message-ID: <20070127235238.GA28706@coredump.intra.peff.net>
diff --git a/contrib/blameview/blameview.perl b/contrib/blameview/blameview.perl
deleted file mode 100755
index 1dec00137b..0000000000
--- a/contrib/blameview/blameview.perl
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/perl
-
-use Gtk2 -init;
-use Gtk2::SimpleList;
-
-my $hash;
-my $fn;
-if ( @ARGV == 1 ) {
- $hash = "HEAD";
- $fn = shift;
-} elsif ( @ARGV == 2 ) {
- $hash = shift;
- $fn = shift;
-} else {
- die "Usage blameview [<rev>] <filename>";
-}
-
-Gtk2::Rc->parse_string(<<'EOS');
-style "treeview_style"
-{
- GtkTreeView::vertical-separator = 0
-}
-class "GtkTreeView" style "treeview_style"
-EOS
-
-my $window = Gtk2::Window->new('toplevel');
-$window->signal_connect(destroy => sub { Gtk2->main_quit });
-my $vpan = Gtk2::VPaned->new();
-$window->add($vpan);
-my $scrolled_window = Gtk2::ScrolledWindow->new;
-$vpan->pack1($scrolled_window, 1, 1);
-my $fileview = Gtk2::SimpleList->new(
- 'Commit' => 'text',
- 'FileLine' => 'text',
- 'Data' => 'text'
-);
-$scrolled_window->add($fileview);
-$fileview->get_column(0)->set_spacing(0);
-$fileview->set_size_request(1024, 768);
-$fileview->set_rules_hint(1);
-$fileview->signal_connect (row_activated => sub {
- my ($sl, $path, $column) = @_;
- my $row_ref = $sl->get_row_data_from_path ($path);
- system("blameview @$row_ref[0]~1 $fn &");
- });
-
-my $commitwindow = Gtk2::ScrolledWindow->new();
-$commitwindow->set_policy ('GTK_POLICY_AUTOMATIC','GTK_POLICY_AUTOMATIC');
-$vpan->pack2($commitwindow, 1, 1);
-my $commit_text = Gtk2::TextView->new();
-my $commit_buffer = Gtk2::TextBuffer->new();
-$commit_text->set_buffer($commit_buffer);
-$commitwindow->add($commit_text);
-
-$fileview->signal_connect (cursor_changed => sub {
- my ($sl) = @_;
- my ($path, $focus_column) = $sl->get_cursor();
- my $row_ref = $sl->get_row_data_from_path ($path);
- my $c_fh;
- open($c_fh, '-|', "git cat-file commit @$row_ref[0]")
- or die "unable to find commit @$row_ref[0]";
- my @buffer = <$c_fh>;
- $commit_buffer->set_text("@buffer");
- close($c_fh);
- });
-
-my $fh;
-open($fh, '-|', "git cat-file blob $hash:$fn")
- or die "unable to open $fn: $!";
-
-while(<$fh>) {
- chomp;
- $fileview->{data}->[$.] = ['HEAD', "$fn:$.", $_];
-}
-
-my $blame;
-open($blame, '-|', qw(git blame --incremental --), $fn, $hash)
- or die "cannot start git-blame $fn";
-
-Glib::IO->add_watch(fileno($blame), 'in', \&read_blame_line);
-
-$window->show_all;
-Gtk2->main;
-exit 0;
-
-my %commitinfo = ();
-
-sub flush_blame_line {
- my ($attr) = @_;
-
- return unless defined $attr;
-
- my ($commit, $s_lno, $lno, $cnt) =
- @{$attr}{qw(COMMIT S_LNO LNO CNT)};
-
- my ($filename, $author, $author_time, $author_tz) =
- @{$commitinfo{$commit}}{qw(FILENAME AUTHOR AUTHOR-TIME AUTHOR-TZ)};
- my $info = $author . ' ' . format_time($author_time, $author_tz);
-
- for(my $i = 0; $i < $cnt; $i++) {
- @{$fileview->{data}->[$lno+$i-1]}[0,1,2] =
- (substr($commit, 0, 8), $filename . ':' . ($s_lno+$i));
- }
-}
-
-my $buf;
-my $current;
-sub read_blame_line {
-
- my $r = sysread($blame, $buf, 1024, length($buf));
- die "I/O error" unless defined $r;
-
- if ($r == 0) {
- flush_blame_line($current);
- $current = undef;
- return 0;
- }
-
- while ($buf =~ s/([^\n]*)\n//) {
- my $line = $1;
-
- if (($commit, $s_lno, $lno, $cnt) =
- ($line =~ /^([0-9a-f]{40}) (\d+) (\d+) (\d+)$/)) {
- flush_blame_line($current);
- $current = +{
- COMMIT => $1,
- S_LNO => $2,
- LNO => $3,
- CNT => $4,
- };
- next;
- }
-
- # extended attribute values
- if ($line =~ /^(author|author-mail|author-time|author-tz|committer|committer-mail|committer-time|committer-tz|summary|filename) (.*)$/) {
- my $commit = $current->{COMMIT};
- $commitinfo{$commit}{uc($1)} = $2;
- next;
- }
- }
- return 1;
-}
-
-sub format_time {
- my $time = shift;
- my $tz = shift;
-
- my $minutes = $tz < 0 ? 0-$tz : $tz;
- $minutes = ($minutes / 100)*60 + ($minutes % 100);
- $minutes = $tz < 0 ? 0-$minutes : $minutes;
- $time += $minutes * 60;
- my @t = gmtime($time);
- return sprintf('%04d-%02d-%02d %02d:%02d:%02d %s',
- $t[5] + 1900, @t[4,3,2,1,0], $tz);
-}
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
new file mode 100644
index 0000000000..fd1399c440
--- /dev/null
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -0,0 +1,1046 @@
+#
+# Copyright (c) 2020 Sibi Siddharthan
+#
+
+#[[
+
+Instructions how to use this in Visual Studio:
+
+Open the worktree as a folder. Visual Studio 2019 and later will detect
+the CMake configuration automatically and set everything up for you,
+ready to build. You can then run the tests in `t/` via a regular Git Bash.
+
+Note: Visual Studio also has the option of opening `CMakeLists.txt`
+directly; Using this option, Visual Studio will not find the source code,
+though, therefore the `File>Open>Folder...` option is preferred.
+
+Instructions to run CMake manually:
+
+ mkdir -p contrib/buildsystems/out
+ cd contrib/buildsystems/out
+ cmake ../ -DCMAKE_BUILD_TYPE=Release
+
+This will build the git binaries in contrib/buildsystems/out
+directory (our top-level .gitignore file knows to ignore contents of
+this directory).
+
+Possible build configurations(-DCMAKE_BUILD_TYPE) with corresponding
+compiler flags
+Debug : -g
+Release: -O3
+RelWithDebInfo : -O2 -g
+MinSizeRel : -Os
+empty(default) :
+
+NOTE: -DCMAKE_BUILD_TYPE is optional. For multi-config generators like Visual Studio
+this option is ignored
+
+This process generates a Makefile(Linux/*BSD/MacOS) , Visual Studio solution(Windows) by default.
+Run `make` to build Git on Linux/*BSD/MacOS.
+Open git.sln on Windows and build Git.
+
+NOTE: By default CMake uses Makefile as the build tool on Linux and Visual Studio in Windows,
+to use another tool say `ninja` add this to the command line when configuring.
+`-G Ninja`
+
+NOTE: By default CMake will install vcpkg locally to your source tree on configuration,
+to avoid this, add `-DNO_VCPKG=TRUE` to the command line when configuring.
+
+]]
+cmake_minimum_required(VERSION 3.14)
+
+#set the source directory to root of git
+set(CMAKE_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
+
+option(USE_VCPKG "Whether or not to use vcpkg for obtaining dependencies. Only applicable to Windows platforms" ON)
+if(NOT WIN32)
+ set(USE_VCPKG OFF CACHE BOOL FORCE)
+endif()
+
+if(NOT DEFINED CMAKE_EXPORT_COMPILE_COMMANDS)
+ set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
+endif()
+
+if(USE_VCPKG)
+ set(VCPKG_DIR "${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg")
+ if(NOT EXISTS ${VCPKG_DIR})
+ message("Initializing vcpkg and building the Git's dependencies (this will take a while...)")
+ execute_process(COMMAND ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg_install.bat)
+ endif()
+ list(APPEND CMAKE_PREFIX_PATH "${VCPKG_DIR}/installed/x64-windows")
+
+ # In the vcpkg edition, we need this to be able to link to libcurl
+ set(CURL_NO_CURL_CMAKE ON)
+
+ # Copy the necessary vcpkg DLLs (like iconv) to the install dir
+ set(X_VCPKG_APPLOCAL_DEPS_INSTALL ON)
+ set(CMAKE_TOOLCHAIN_FILE ${VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")
+endif()
+
+find_program(SH_EXE sh PATHS "C:/Program Files/Git/bin")
+if(NOT SH_EXE)
+ message(FATAL_ERROR "sh: shell interpreter was not found in your path, please install one."
+ "On Windows, you can get it as part of 'Git for Windows' install at https://gitforwindows.org/")
+endif()
+
+#Create GIT-VERSION-FILE using GIT-VERSION-GEN
+if(NOT EXISTS ${CMAKE_SOURCE_DIR}/GIT-VERSION-FILE)
+ message("Generating GIT-VERSION-FILE")
+ execute_process(COMMAND ${SH_EXE} ${CMAKE_SOURCE_DIR}/GIT-VERSION-GEN
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+endif()
+
+#Parse GIT-VERSION-FILE to get the version
+file(STRINGS ${CMAKE_SOURCE_DIR}/GIT-VERSION-FILE git_version REGEX "GIT_VERSION = (.*)")
+string(REPLACE "GIT_VERSION = " "" git_version ${git_version})
+string(FIND ${git_version} "GIT" location)
+if(location EQUAL -1)
+ string(REGEX MATCH "[0-9]*\\.[0-9]*\\.[0-9]*" git_version ${git_version})
+else()
+ string(REGEX MATCH "[0-9]*\\.[0-9]*" git_version ${git_version})
+ string(APPEND git_version ".0") #for building from a snapshot
+endif()
+
+project(git
+ VERSION ${git_version}
+ LANGUAGES C)
+
+
+#TODO gitk git-gui gitweb
+#TODO Enable NLS on windows natively
+#TODO Add pcre support
+
+#macros for parsing the Makefile for sources and scripts
+macro(parse_makefile_for_sources list_var regex)
+ file(STRINGS ${CMAKE_SOURCE_DIR}/Makefile ${list_var} REGEX "^${regex} \\+=(.*)")
+ string(REPLACE "${regex} +=" "" ${list_var} ${${list_var}})
+ string(REPLACE "$(COMPAT_OBJS)" "" ${list_var} ${${list_var}}) #remove "$(COMPAT_OBJS)" This is only for libgit.
+ string(STRIP ${${list_var}} ${list_var}) #remove trailing/leading whitespaces
+ string(REPLACE ".o" ".c;" ${list_var} ${${list_var}}) #change .o to .c, ; is for converting the string into a list
+ list(TRANSFORM ${list_var} STRIP) #remove trailing/leading whitespaces for each element in list
+ list(REMOVE_ITEM ${list_var} "") #remove empty list elements
+endmacro()
+
+macro(parse_makefile_for_scripts list_var regex lang)
+ file(STRINGS ${CMAKE_SOURCE_DIR}/Makefile ${list_var} REGEX "^${regex} \\+=(.*)")
+ string(REPLACE "${regex} +=" "" ${list_var} ${${list_var}})
+ string(STRIP ${${list_var}} ${list_var}) #remove trailing/leading whitespaces
+ string(REPLACE " " ";" ${list_var} ${${list_var}}) #convert string to a list
+ if(NOT ${lang}) #exclude for SCRIPT_LIB
+ list(TRANSFORM ${list_var} REPLACE "${lang}" "") #do the replacement
+ endif()
+endmacro()
+
+macro(parse_makefile_for_executables list_var regex)
+ file(STRINGS ${CMAKE_SOURCE_DIR}/Makefile ${list_var} REGEX "^${regex} \\+= git-(.*)")
+ string(REPLACE "${regex} +=" "" ${list_var} ${${list_var}})
+ string(STRIP ${${list_var}} ${list_var}) #remove trailing/leading whitespaces
+ string(REPLACE "git-" "" ${list_var} ${${list_var}}) #strip `git-` prefix
+ string(REPLACE "\$X" ";" ${list_var} ${${list_var}}) #strip $X, ; is for converting the string into a list
+ list(TRANSFORM ${list_var} STRIP) #remove trailing/leading whitespaces for each element in list
+ list(REMOVE_ITEM ${list_var} "") #remove empty list elements
+endmacro()
+
+include(CheckTypeSize)
+include(CheckCSourceRuns)
+include(CheckCSourceCompiles)
+include(CheckIncludeFile)
+include(CheckFunctionExists)
+include(CheckSymbolExists)
+include(CheckStructHasMember)
+include(CTest)
+
+find_package(ZLIB REQUIRED)
+find_package(CURL)
+find_package(EXPAT)
+find_package(Iconv)
+
+#Don't use libintl on Windows Visual Studio and Clang builds
+if(NOT (WIN32 AND (CMAKE_C_COMPILER_ID STREQUAL "MSVC" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")))
+ find_package(Intl)
+endif()
+
+if(NOT Intl_FOUND)
+ add_compile_definitions(NO_GETTEXT)
+ if(NOT Iconv_FOUND)
+ add_compile_definitions(NO_ICONV)
+ endif()
+endif()
+
+include_directories(SYSTEM ${ZLIB_INCLUDE_DIRS})
+if(CURL_FOUND)
+ include_directories(SYSTEM ${CURL_INCLUDE_DIRS})
+endif()
+if(EXPAT_FOUND)
+ include_directories(SYSTEM ${EXPAT_INCLUDE_DIRS})
+endif()
+if(Iconv_FOUND)
+ include_directories(SYSTEM ${Iconv_INCLUDE_DIRS})
+endif()
+if(Intl_FOUND)
+ include_directories(SYSTEM ${Intl_INCLUDE_DIRS})
+endif()
+
+
+if(WIN32 AND NOT MSVC)#not required for visual studio builds
+ find_program(WINDRES_EXE windres)
+ if(NOT WINDRES_EXE)
+ message(FATAL_ERROR "Install windres on Windows for resource files")
+ endif()
+endif()
+
+if(NO_GETTEXT)
+ message(STATUS "msgfmt not used under NO_GETTEXT")
+else()
+ find_program(MSGFMT_EXE msgfmt)
+ if(NOT MSGFMT_EXE)
+ if(USE_VCPKG)
+ set(MSGFMT_EXE ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg/downloads/tools/msys2/msys64/usr/bin/msgfmt.exe)
+ endif()
+ if(NOT EXISTS ${MSGFMT_EXE})
+ message(WARNING "Text Translations won't be built")
+ unset(MSGFMT_EXE)
+ endif()
+ endif()
+endif()
+
+#Force all visual studio outputs to CMAKE_BINARY_DIR
+if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR})
+ add_compile_options(/MP)
+endif()
+
+#default behaviour
+include_directories(${CMAKE_SOURCE_DIR})
+add_compile_definitions(GIT_HOST_CPU="${CMAKE_SYSTEM_PROCESSOR}")
+add_compile_definitions(SHA256_BLK INTERNAL_QSORT RUNTIME_PREFIX)
+add_compile_definitions(NO_OPENSSL SHA1_DC SHA1DC_NO_STANDARD_INCLUDES
+ SHA1DC_INIT_SAFE_HASH_DEFAULT=0
+ SHA1DC_CUSTOM_INCLUDE_SHA1_C="cache.h"
+ SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C="git-compat-util.h" )
+list(APPEND compat_SOURCES sha1dc_git.c sha1dc/sha1.c sha1dc/ubc_check.c block-sha1/sha1.c sha256/block/sha256.c compat/qsort_s.c)
+
+
+add_compile_definitions(PAGER_ENV="LESS=FRX LV=-c"
+ GIT_EXEC_PATH="libexec/git-core"
+ GIT_LOCALE_PATH="share/locale"
+ GIT_MAN_PATH="share/man"
+ GIT_INFO_PATH="share/info"
+ GIT_HTML_PATH="share/doc/git-doc"
+ DEFAULT_HELP_FORMAT="html"
+ DEFAULT_GIT_TEMPLATE_DIR="share/git-core/templates"
+ GIT_VERSION="${PROJECT_VERSION}.GIT"
+ GIT_USER_AGENT="git/${PROJECT_VERSION}.GIT"
+ BINDIR="bin"
+ GIT_BUILT_FROM_COMMIT="")
+
+if(WIN32)
+ set(FALLBACK_RUNTIME_PREFIX /mingw64)
+ # Move system config into top-level /etc/
+ add_compile_definitions(FALLBACK_RUNTIME_PREFIX="${FALLBACK_RUNTIME_PREFIX}"
+ ETC_GITATTRIBUTES="../etc/gitattributes"
+ ETC_GITCONFIG="../etc/gitconfig")
+else()
+ set(FALLBACK_RUNTIME_PREFIX /home/$ENV{USER})
+ add_compile_definitions(FALLBACK_RUNTIME_PREFIX="${FALLBACK_RUNTIME_PREFIX}"
+ ETC_GITATTRIBUTES="etc/gitattributes"
+ ETC_GITCONFIG="etc/gitconfig")
+endif()
+
+
+#Platform Specific
+if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+ if(CMAKE_C_COMPILER_ID STREQUAL "MSVC" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
+ include_directories(${CMAKE_SOURCE_DIR}/compat/vcbuild/include)
+ add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE)
+ endif()
+ include_directories(${CMAKE_SOURCE_DIR}/compat/win32)
+ add_compile_definitions(HAVE_ALLOCA_H NO_POSIX_GOODIES NATIVE_CRLF NO_UNIX_SOCKETS WIN32
+ _CONSOLE DETECT_MSYS_TTY STRIP_EXTENSION=".exe" NO_SYMLINK_HEAD UNRELIABLE_FSTAT
+ NOGDI OBJECT_CREATION_MODE=1 __USE_MINGW_ANSI_STDIO=0
+ USE_NED_ALLOCATOR OVERRIDE_STRDUP MMAP_PREVENTS_DELETE USE_WIN32_MMAP
+ UNICODE _UNICODE HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET)
+ list(APPEND compat_SOURCES compat/mingw.c compat/winansi.c compat/win32/path-utils.c
+ compat/win32/pthread.c compat/win32mmap.c compat/win32/syslog.c
+ compat/win32/trace2_win32_process_info.c compat/win32/dirent.c
+ compat/nedmalloc/nedmalloc.c compat/strdup.c)
+ set(NO_UNIX_SOCKETS 1)
+
+elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ add_compile_definitions(PROCFS_EXECUTABLE_PATH="/proc/self/exe" HAVE_DEV_TTY )
+ list(APPEND compat_SOURCES unix-socket.c unix-stream-server.c)
+endif()
+
+if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+ list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c)
+ add_compile_definitions(SUPPORTS_SIMPLE_IPC)
+ set(SUPPORTS_SIMPLE_IPC 1)
+else()
+ # Simple IPC requires both Unix sockets and pthreads on Unix-based systems.
+ if(NOT NO_UNIX_SOCKETS AND NOT NO_PTHREADS)
+ list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c)
+ add_compile_definitions(SUPPORTS_SIMPLE_IPC)
+ set(SUPPORTS_SIMPLE_IPC 1)
+ endif()
+endif()
+
+set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})
+
+#header checks
+check_include_file(libgen.h HAVE_LIBGEN_H)
+if(NOT HAVE_LIBGEN_H)
+ add_compile_definitions(NO_LIBGEN_H)
+ list(APPEND compat_SOURCES compat/basename.c)
+endif()
+
+check_include_file(sys/sysinfo.h HAVE_SYSINFO)
+if(HAVE_SYSINFO)
+ add_compile_definitions(HAVE_SYSINFO)
+endif()
+
+check_c_source_compiles("
+#include <alloca.h>
+
+int main(void)
+{
+ char *p = (char *) alloca(2 * sizeof(int));
+
+ if (p)
+ return 0;
+ return 0;
+}"
+HAVE_ALLOCA_H)
+if(HAVE_ALLOCA_H)
+ add_compile_definitions(HAVE_ALLOCA_H)
+endif()
+
+check_include_file(strings.h HAVE_STRINGS_H)
+if(HAVE_STRINGS_H)
+ add_compile_definitions(HAVE_STRINGS_H)
+endif()
+
+check_include_file(sys/select.h HAVE_SYS_SELECT_H)
+if(NOT HAVE_SYS_SELECT_H)
+ add_compile_definitions(NO_SYS_SELECT_H)
+endif()
+
+check_include_file(sys/poll.h HAVE_SYS_POLL_H)
+if(NOT HAVE_SYS_POLL_H)
+ add_compile_definitions(NO_SYS_POLL_H)
+endif()
+
+check_include_file(poll.h HAVE_POLL_H)
+if(NOT HAVE_POLL_H)
+ add_compile_definitions(NO_POLL_H)
+endif()
+
+check_include_file(inttypes.h HAVE_INTTYPES_H)
+if(NOT HAVE_INTTYPES_H)
+ add_compile_definitions(NO_INTTYPES_H)
+endif()
+
+check_include_file(paths.h HAVE_PATHS_H)
+if(HAVE_PATHS_H)
+ add_compile_definitions(HAVE_PATHS_H)
+endif()
+
+#function checks
+set(function_checks
+ strcasestr memmem strlcpy strtoimax strtoumax strtoull
+ setenv mkdtemp poll pread memmem)
+
+#unsetenv,hstrerror are incompatible with windows build
+if(NOT WIN32)
+ list(APPEND function_checks unsetenv hstrerror)
+endif()
+
+foreach(f ${function_checks})
+ string(TOUPPER ${f} uf)
+ check_function_exists(${f} HAVE_${uf})
+ if(NOT HAVE_${uf})
+ add_compile_definitions(NO_${uf})
+ endif()
+endforeach()
+
+if(NOT HAVE_POLL_H OR NOT HAVE_SYS_POLL_H OR NOT HAVE_POLL)
+ include_directories(${CMAKE_SOURCE_DIR}/compat/poll)
+ add_compile_definitions(NO_POLL)
+ list(APPEND compat_SOURCES compat/poll/poll.c)
+endif()
+
+if(NOT HAVE_STRCASESTR)
+ list(APPEND compat_SOURCES compat/strcasestr.c)
+endif()
+
+if(NOT HAVE_STRLCPY)
+ list(APPEND compat_SOURCES compat/strlcpy.c)
+endif()
+
+if(NOT HAVE_STRTOUMAX)
+ list(APPEND compat_SOURCES compat/strtoumax.c compat/strtoimax.c)
+endif()
+
+if(NOT HAVE_SETENV)
+ list(APPEND compat_SOURCES compat/setenv.c)
+endif()
+
+if(NOT HAVE_MKDTEMP)
+ list(APPEND compat_SOURCES compat/mkdtemp.c)
+endif()
+
+if(NOT HAVE_PREAD)
+ list(APPEND compat_SOURCES compat/pread.c)
+endif()
+
+if(NOT HAVE_MEMMEM)
+ list(APPEND compat_SOURCES compat/memmem.c)
+endif()
+
+if(NOT WIN32)
+ if(NOT HAVE_UNSETENV)
+ list(APPEND compat_SOURCES compat/unsetenv.c)
+ endif()
+
+ if(NOT HAVE_HSTRERROR)
+ list(APPEND compat_SOURCES compat/hstrerror.c)
+ endif()
+endif()
+
+check_function_exists(getdelim HAVE_GETDELIM)
+if(HAVE_GETDELIM)
+ add_compile_definitions(HAVE_GETDELIM)
+endif()
+
+check_function_exists(clock_gettime HAVE_CLOCK_GETTIME)
+check_symbol_exists(CLOCK_MONOTONIC "time.h" HAVE_CLOCK_MONOTONIC)
+if(HAVE_CLOCK_GETTIME)
+ add_compile_definitions(HAVE_CLOCK_GETTIME)
+endif()
+if(HAVE_CLOCK_MONOTONIC)
+ add_compile_definitions(HAVE_CLOCK_MONOTONIC)
+endif()
+
+#check for st_blocks in struct stat
+check_struct_has_member("struct stat" st_blocks "sys/stat.h" STRUCT_STAT_HAS_ST_BLOCKS)
+if(NOT STRUCT_STAT_HAS_ST_BLOCKS)
+ add_compile_definitions(NO_ST_BLOCKS_IN_STRUCT_STAT)
+endif()
+
+#compile checks
+check_c_source_runs("
+#include<stdio.h>
+#include<stdarg.h>
+#include<string.h>
+#include<stdlib.h>
+
+int test_vsnprintf(char *str, size_t maxsize, const char *format, ...)
+{
+ int ret;
+ va_list ap;
+
+ va_start(ap, format);
+ ret = vsnprintf(str, maxsize, format, ap);
+ va_end(ap);
+ return ret;
+}
+
+int main(void)
+{
+ char buf[6];
+
+ if (test_vsnprintf(buf, 3, \"%s\", \"12345\") != 5
+ || strcmp(buf, \"12\"))
+ return 1;
+ if (snprintf(buf, 3, \"%s\", \"12345\") != 5
+ || strcmp(buf, \"12\"))
+ return 1;
+ return 0;
+}"
+SNPRINTF_OK)
+if(NOT SNPRINTF_OK)
+ add_compile_definitions(SNPRINTF_RETURNS_BOGUS)
+ list(APPEND compat_SOURCES compat/snprintf.c)
+endif()
+
+check_c_source_runs("
+#include<stdio.h>
+
+int main(void)
+{
+ FILE *f = fopen(\".\", \"r\");
+
+ return f != NULL;
+}"
+FREAD_READS_DIRECTORIES_NO)
+if(NOT FREAD_READS_DIRECTORIES_NO)
+ add_compile_definitions(FREAD_READS_DIRECTORIES)
+ list(APPEND compat_SOURCES compat/fopen.c)
+endif()
+
+check_c_source_compiles("
+#include <regex.h>
+#ifndef REG_STARTEND
+#error oops we dont have it
+#endif
+
+int main(void)
+{
+ return 0;
+}"
+HAVE_REGEX)
+if(NOT HAVE_REGEX)
+ include_directories(${CMAKE_SOURCE_DIR}/compat/regex)
+ list(APPEND compat_SOURCES compat/regex/regex.c )
+ add_compile_definitions(NO_REGEX NO_MBSUPPORT GAWK)
+endif()
+
+
+check_c_source_compiles("
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+int main(void)
+{
+ int val, mib[2];
+ size_t len;
+
+ mib[0] = CTL_HW;
+ mib[1] = 1;
+ len = sizeof(val);
+ return sysctl(mib, 2, &val, &len, NULL, 0) ? 1 : 0;
+}"
+HAVE_BSD_SYSCTL)
+if(HAVE_BSD_SYSCTL)
+ add_compile_definitions(HAVE_BSD_SYSCTL)
+endif()
+
+set(CMAKE_REQUIRED_LIBRARIES ${Iconv_LIBRARIES})
+set(CMAKE_REQUIRED_INCLUDES ${Iconv_INCLUDE_DIRS})
+
+check_c_source_compiles("
+#include <iconv.h>
+
+extern size_t iconv(iconv_t cd,
+ char **inbuf, size_t *inbytesleft,
+ char **outbuf, size_t *outbytesleft);
+
+int main(void)
+{
+ return 0;
+}"
+HAVE_NEW_ICONV)
+if(HAVE_NEW_ICONV)
+ set(HAVE_OLD_ICONV 0)
+else()
+ set(HAVE_OLD_ICONV 1)
+endif()
+
+check_c_source_runs("
+#include <iconv.h>
+#if ${HAVE_OLD_ICONV}
+typedef const char *iconv_ibp;
+#else
+typedef char *iconv_ibp;
+#endif
+
+int main(void)
+{
+ int v;
+ iconv_t conv;
+ char in[] = \"a\";
+ iconv_ibp pin = in;
+ char out[20] = \"\";
+ char *pout = out;
+ size_t isz = sizeof(in);
+ size_t osz = sizeof(out);
+
+ conv = iconv_open(\"UTF-16\", \"UTF-8\");
+ iconv(conv, &pin, &isz, &pout, &osz);
+ iconv_close(conv);
+ v = (unsigned char)(out[0]) + (unsigned char)(out[1]);
+ return v != 0xfe + 0xff;
+}"
+ICONV_DOESNOT_OMIT_BOM)
+if(NOT ICONV_DOESNOT_OMIT_BOM)
+ add_compile_definitions(ICONV_OMITS_BOM)
+endif()
+
+unset(CMAKE_REQUIRED_LIBRARIES)
+unset(CMAKE_REQUIRED_INCLUDES)
+
+
+#programs
+set(PROGRAMS_BUILT
+ git git-daemon git-http-backend git-sh-i18n--envsubst
+ git-shell)
+
+if(NOT CURL_FOUND)
+ list(APPEND excluded_progs git-http-fetch git-http-push)
+ add_compile_definitions(NO_CURL)
+ message(WARNING "git-http-push and git-http-fetch will not be built")
+else()
+ list(APPEND PROGRAMS_BUILT git-http-fetch git-http-push git-imap-send git-remote-http)
+ if(CURL_VERSION_STRING VERSION_GREATER_EQUAL 7.34.0)
+ add_compile_definitions(USE_CURL_FOR_IMAP_SEND)
+ endif()
+endif()
+
+if(NOT EXPAT_FOUND)
+ list(APPEND excluded_progs git-http-push)
+ add_compile_definitions(NO_EXPAT)
+else()
+ list(APPEND PROGRAMS_BUILT git-http-push)
+ if(EXPAT_VERSION_STRING VERSION_LESS_EQUAL 1.2)
+ add_compile_definitions(EXPAT_NEEDS_XMLPARSE_H)
+ endif()
+endif()
+
+list(REMOVE_DUPLICATES excluded_progs)
+list(REMOVE_DUPLICATES PROGRAMS_BUILT)
+
+
+foreach(p ${excluded_progs})
+ list(APPEND EXCLUSION_PROGS --exclude-program ${p} )
+endforeach()
+
+#for comparing null values
+list(APPEND EXCLUSION_PROGS empty)
+set(EXCLUSION_PROGS_CACHE ${EXCLUSION_PROGS} CACHE STRING "Programs not built" FORCE)
+
+if(NOT EXISTS ${CMAKE_BINARY_DIR}/command-list.h OR NOT EXCLUSION_PROGS_CACHE STREQUAL EXCLUSION_PROGS)
+ list(REMOVE_ITEM EXCLUSION_PROGS empty)
+ message("Generating command-list.h")
+ execute_process(COMMAND ${SH_EXE} ${CMAKE_SOURCE_DIR}/generate-cmdlist.sh ${EXCLUSION_PROGS} command-list.txt
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_FILE ${CMAKE_BINARY_DIR}/command-list.h)
+endif()
+
+if(NOT EXISTS ${CMAKE_BINARY_DIR}/config-list.h)
+ message("Generating config-list.h")
+ execute_process(COMMAND ${SH_EXE} ${CMAKE_SOURCE_DIR}/generate-configlist.sh
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_FILE ${CMAKE_BINARY_DIR}/config-list.h)
+endif()
+
+if(NOT EXISTS ${CMAKE_BINARY_DIR}/hook-list.h)
+ message("Generating hook-list.h")
+ execute_process(COMMAND ${SH_EXE} ${CMAKE_SOURCE_DIR}/generate-hooklist.sh
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_FILE ${CMAKE_BINARY_DIR}/hook-list.h)
+endif()
+
+include_directories(${CMAKE_BINARY_DIR})
+
+#build
+#libgit
+parse_makefile_for_sources(libgit_SOURCES "LIB_OBJS")
+
+list(TRANSFORM libgit_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
+list(TRANSFORM compat_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
+add_library(libgit ${libgit_SOURCES} ${compat_SOURCES})
+
+#libxdiff
+parse_makefile_for_sources(libxdiff_SOURCES "XDIFF_OBJS")
+
+list(TRANSFORM libxdiff_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
+add_library(xdiff STATIC ${libxdiff_SOURCES})
+
+if(WIN32)
+ if(NOT MSVC)#use windres when compiling with gcc and clang
+ add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/git.res
+ COMMAND ${WINDRES_EXE} -O coff -DMAJOR=${PROJECT_VERSION_MAJOR} -DMINOR=${PROJECT_VERSION_MINOR}
+ -DMICRO=${PROJECT_VERSION_PATCH} -DPATCHLEVEL=0 -DGIT_VERSION="\\\"${PROJECT_VERSION}.GIT\\\""
+ -i ${CMAKE_SOURCE_DIR}/git.rc -o ${CMAKE_BINARY_DIR}/git.res
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ VERBATIM)
+ else()#MSVC use rc
+ add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/git.res
+ COMMAND ${CMAKE_RC_COMPILER} /d MAJOR=${PROJECT_VERSION_MAJOR} /d MINOR=${PROJECT_VERSION_MINOR}
+ /d MICRO=${PROJECT_VERSION_PATCH} /d PATCHLEVEL=0 /d GIT_VERSION="${PROJECT_VERSION}.GIT"
+ /fo ${CMAKE_BINARY_DIR}/git.res ${CMAKE_SOURCE_DIR}/git.rc
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ VERBATIM)
+ endif()
+ add_custom_target(git-rc DEPENDS ${CMAKE_BINARY_DIR}/git.res)
+endif()
+
+#link all required libraries to common-main
+add_library(common-main OBJECT ${CMAKE_SOURCE_DIR}/common-main.c)
+
+target_link_libraries(common-main libgit xdiff ${ZLIB_LIBRARIES})
+if(Intl_FOUND)
+ target_link_libraries(common-main ${Intl_LIBRARIES})
+endif()
+if(Iconv_FOUND)
+ target_link_libraries(common-main ${Iconv_LIBRARIES})
+endif()
+if(WIN32)
+ target_link_libraries(common-main ws2_32 ntdll ${CMAKE_BINARY_DIR}/git.res)
+ add_dependencies(common-main git-rc)
+ if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
+ target_link_options(common-main PUBLIC -municode -Wl,--nxcompat -Wl,--dynamicbase -Wl,--pic-executable,-e,mainCRTStartup)
+ elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang")
+ target_link_options(common-main PUBLIC -municode -Wl,-nxcompat -Wl,-dynamicbase -Wl,-entry:wmainCRTStartup -Wl,invalidcontinue.obj)
+ elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
+ target_link_options(common-main PUBLIC /IGNORE:4217 /IGNORE:4049 /NOLOGO /ENTRY:wmainCRTStartup /SUBSYSTEM:CONSOLE invalidcontinue.obj)
+ else()
+ message(FATAL_ERROR "Unhandled compiler: ${CMAKE_C_COMPILER_ID}")
+ endif()
+elseif(UNIX)
+ target_link_libraries(common-main pthread rt)
+endif()
+
+#git
+parse_makefile_for_sources(git_SOURCES "BUILTIN_OBJS")
+
+list(TRANSFORM git_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
+add_executable(git ${CMAKE_SOURCE_DIR}/git.c ${git_SOURCES})
+target_link_libraries(git common-main)
+
+add_executable(git-daemon ${CMAKE_SOURCE_DIR}/daemon.c)
+target_link_libraries(git-daemon common-main)
+
+add_executable(git-http-backend ${CMAKE_SOURCE_DIR}/http-backend.c)
+target_link_libraries(git-http-backend common-main)
+
+add_executable(git-sh-i18n--envsubst ${CMAKE_SOURCE_DIR}/sh-i18n--envsubst.c)
+target_link_libraries(git-sh-i18n--envsubst common-main)
+
+add_executable(git-shell ${CMAKE_SOURCE_DIR}/shell.c)
+target_link_libraries(git-shell common-main)
+
+if(CURL_FOUND)
+ add_library(http_obj OBJECT ${CMAKE_SOURCE_DIR}/http.c)
+
+ add_executable(git-imap-send ${CMAKE_SOURCE_DIR}/imap-send.c)
+ target_link_libraries(git-imap-send http_obj common-main ${CURL_LIBRARIES})
+
+ add_executable(git-http-fetch ${CMAKE_SOURCE_DIR}/http-walker.c ${CMAKE_SOURCE_DIR}/http-fetch.c)
+ target_link_libraries(git-http-fetch http_obj common-main ${CURL_LIBRARIES})
+
+ add_executable(git-remote-http ${CMAKE_SOURCE_DIR}/http-walker.c ${CMAKE_SOURCE_DIR}/remote-curl.c)
+ target_link_libraries(git-remote-http http_obj common-main ${CURL_LIBRARIES} )
+
+ if(EXPAT_FOUND)
+ add_executable(git-http-push ${CMAKE_SOURCE_DIR}/http-push.c)
+ target_link_libraries(git-http-push http_obj common-main ${CURL_LIBRARIES} ${EXPAT_LIBRARIES})
+ endif()
+endif()
+
+parse_makefile_for_executables(git_builtin_extra "BUILT_INS")
+
+option(SKIP_DASHED_BUILT_INS "Skip hardlinking the dashed versions of the built-ins")
+
+#Creating hardlinks
+if(NOT SKIP_DASHED_BUILT_INS)
+foreach(s ${git_SOURCES} ${git_builtin_extra})
+ string(REPLACE "${CMAKE_SOURCE_DIR}/builtin/" "" s ${s})
+ string(REPLACE ".c" "" s ${s})
+ file(APPEND ${CMAKE_BINARY_DIR}/CreateLinks.cmake "file(CREATE_LINK git${EXE_EXTENSION} git-${s}${EXE_EXTENSION})\n")
+ list(APPEND git_links ${CMAKE_BINARY_DIR}/git-${s}${EXE_EXTENSION})
+endforeach()
+endif()
+
+if(CURL_FOUND)
+ set(remote_exes
+ git-remote-https git-remote-ftp git-remote-ftps)
+ foreach(s ${remote_exes})
+ file(APPEND ${CMAKE_BINARY_DIR}/CreateLinks.cmake "file(CREATE_LINK git-remote-http${EXE_EXTENSION} ${s}${EXE_EXTENSION})\n")
+ list(APPEND git_http_links ${CMAKE_BINARY_DIR}/${s}${EXE_EXTENSION})
+ endforeach()
+endif()
+
+add_custom_command(OUTPUT ${git_links} ${git_http_links}
+ COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/CreateLinks.cmake
+ DEPENDS git git-remote-http)
+add_custom_target(git-links ALL DEPENDS ${git_links} ${git_http_links})
+
+
+#creating required scripts
+set(SHELL_PATH /bin/sh)
+set(PERL_PATH /usr/bin/perl)
+set(LOCALEDIR ${FALLBACK_RUNTIME_PREFIX}/share/locale)
+set(GITWEBDIR ${FALLBACK_RUNTIME_PREFIX}/share/locale)
+set(INSTLIBDIR ${FALLBACK_RUNTIME_PREFIX}/share/perl5)
+
+#shell scripts
+parse_makefile_for_scripts(git_sh_scripts "SCRIPT_SH" ".sh")
+parse_makefile_for_scripts(git_shlib_scripts "SCRIPT_LIB" "")
+set(git_shell_scripts
+ ${git_sh_scripts} ${git_shlib_scripts} git-instaweb)
+
+foreach(script ${git_shell_scripts})
+ file(STRINGS ${CMAKE_SOURCE_DIR}/${script}.sh content NEWLINE_CONSUME)
+ string(REPLACE "@SHELL_PATH@" "${SHELL_PATH}" content "${content}")
+ string(REPLACE "@@DIFF@@" "diff" content "${content}")
+ string(REPLACE "@LOCALEDIR@" "${LOCALEDIR}" content "${content}")
+ string(REPLACE "@GITWEBDIR@" "${GITWEBDIR}" content "${content}")
+ string(REPLACE "@@NO_CURL@@" "" content "${content}")
+ string(REPLACE "@@USE_GETTEXT_SCHEME@@" "" content "${content}")
+ string(REPLACE "# @@BROKEN_PATH_FIX@@" "" content "${content}")
+ string(REPLACE "@@PERL@@" "${PERL_PATH}" content "${content}")
+ string(REPLACE "@@SANE_TEXT_GREP@@" "-a" content "${content}")
+ string(REPLACE "@@PAGER_ENV@@" "LESS=FRX LV=-c" content "${content}")
+ file(WRITE ${CMAKE_BINARY_DIR}/${script} ${content})
+endforeach()
+
+#perl scripts
+parse_makefile_for_scripts(git_perl_scripts "SCRIPT_PERL" ".perl")
+
+#create perl header
+file(STRINGS ${CMAKE_SOURCE_DIR}/perl/header_templates/fixed_prefix.template.pl perl_header )
+string(REPLACE "@@PATHSEP@@" ":" perl_header "${perl_header}")
+string(REPLACE "@@INSTLIBDIR@@" "${INSTLIBDIR}" perl_header "${perl_header}")
+
+foreach(script ${git_perl_scripts})
+ file(STRINGS ${CMAKE_SOURCE_DIR}/${script}.perl content NEWLINE_CONSUME)
+ string(REPLACE "#!/usr/bin/perl" "#!/usr/bin/perl\n${perl_header}\n" content "${content}")
+ string(REPLACE "@@GIT_VERSION@@" "${PROJECT_VERSION}" content "${content}")
+ file(WRITE ${CMAKE_BINARY_DIR}/${script} ${content})
+endforeach()
+
+#python script
+file(STRINGS ${CMAKE_SOURCE_DIR}/git-p4.py content NEWLINE_CONSUME)
+string(REPLACE "#!/usr/bin/env python" "#!/usr/bin/python" content "${content}")
+file(WRITE ${CMAKE_BINARY_DIR}/git-p4 ${content})
+
+#perl modules
+file(GLOB_RECURSE perl_modules "${CMAKE_SOURCE_DIR}/perl/*.pm")
+
+foreach(pm ${perl_modules})
+ string(REPLACE "${CMAKE_SOURCE_DIR}/perl/" "" file_path ${pm})
+ file(STRINGS ${pm} content NEWLINE_CONSUME)
+ string(REPLACE "@@LOCALEDIR@@" "${LOCALEDIR}" content "${content}")
+ string(REPLACE "@@NO_PERL_CPAN_FALLBACKS@@" "" content "${content}")
+ file(WRITE ${CMAKE_BINARY_DIR}/perl/build/lib/${file_path} ${content})
+#test-lib.sh requires perl/build/lib to be the build directory of perl modules
+endforeach()
+
+
+#templates
+file(GLOB templates "${CMAKE_SOURCE_DIR}/templates/*")
+list(TRANSFORM templates REPLACE "${CMAKE_SOURCE_DIR}/templates/" "")
+list(REMOVE_ITEM templates ".gitignore")
+list(REMOVE_ITEM templates "Makefile")
+list(REMOVE_ITEM templates "blt")# Prevents an error when reconfiguring for in source builds
+
+list(REMOVE_ITEM templates "branches--")
+file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/templates/blt/branches) #create branches
+
+#templates have @.*@ replacement so use configure_file instead
+foreach(tm ${templates})
+ string(REPLACE "--" "/" blt_tm ${tm})
+ string(REPLACE "this" "" blt_tm ${blt_tm})# for this--
+ configure_file(${CMAKE_SOURCE_DIR}/templates/${tm} ${CMAKE_BINARY_DIR}/templates/blt/${blt_tm} @ONLY)
+endforeach()
+
+
+#translations
+if(MSGFMT_EXE)
+ file(GLOB po_files "${CMAKE_SOURCE_DIR}/po/*.po")
+ list(TRANSFORM po_files REPLACE "${CMAKE_SOURCE_DIR}/po/" "")
+ list(TRANSFORM po_files REPLACE ".po" "")
+ foreach(po ${po_files})
+ file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/po/build/locale/${po}/LC_MESSAGES)
+ add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/po/build/locale/${po}/LC_MESSAGES/git.mo
+ COMMAND ${MSGFMT_EXE} --check --statistics -o ${CMAKE_BINARY_DIR}/po/build/locale/${po}/LC_MESSAGES/git.mo ${CMAKE_SOURCE_DIR}/po/${po}.po)
+ list(APPEND po_gen ${CMAKE_BINARY_DIR}/po/build/locale/${po}/LC_MESSAGES/git.mo)
+ endforeach()
+ add_custom_target(po-gen ALL DEPENDS ${po_gen})
+endif()
+
+
+#to help with the install
+list(TRANSFORM git_shell_scripts PREPEND "${CMAKE_BINARY_DIR}/")
+list(TRANSFORM git_perl_scripts PREPEND "${CMAKE_BINARY_DIR}/")
+
+#install
+foreach(program ${PROGRAMS_BUILT})
+if(program STREQUAL "git" OR program STREQUAL "git-shell")
+install(TARGETS ${program}
+ RUNTIME DESTINATION bin)
+else()
+install(TARGETS ${program}
+ RUNTIME DESTINATION libexec/git-core)
+endif()
+endforeach()
+
+install(PROGRAMS ${CMAKE_BINARY_DIR}/git-cvsserver
+ DESTINATION bin)
+
+set(bin_links
+ git-receive-pack git-upload-archive git-upload-pack)
+
+foreach(b ${bin_links})
+install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/bin/git${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/bin/${b}${EXE_EXTENSION})")
+endforeach()
+
+install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/bin/git${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/libexec/git-core/git${EXE_EXTENSION})")
+install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/bin/git-shell${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/libexec/git-core/git-shell${EXE_EXTENSION})")
+
+foreach(b ${git_links})
+ string(REPLACE "${CMAKE_BINARY_DIR}" "" b ${b})
+ install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/bin/git${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/libexec/git-core/${b})")
+endforeach()
+
+foreach(b ${git_http_links})
+ string(REPLACE "${CMAKE_BINARY_DIR}" "" b ${b})
+ install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/libexec/git-core/git-remote-http${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/libexec/git-core/${b})")
+endforeach()
+
+install(PROGRAMS ${git_shell_scripts} ${git_perl_scripts} ${CMAKE_BINARY_DIR}/git-p4
+ DESTINATION libexec/git-core)
+
+install(DIRECTORY ${CMAKE_SOURCE_DIR}/mergetools DESTINATION libexec/git-core)
+install(DIRECTORY ${CMAKE_BINARY_DIR}/perl/build/lib/ DESTINATION share/perl5
+ FILES_MATCHING PATTERN "*.pm")
+install(DIRECTORY ${CMAKE_BINARY_DIR}/templates/blt/ DESTINATION share/git-core/templates)
+
+if(MSGFMT_EXE)
+ install(DIRECTORY ${CMAKE_BINARY_DIR}/po/build/locale DESTINATION share)
+endif()
+
+
+if(BUILD_TESTING)
+
+#tests-helpers
+add_executable(test-fake-ssh ${CMAKE_SOURCE_DIR}/t/helper/test-fake-ssh.c)
+target_link_libraries(test-fake-ssh common-main)
+
+#test-tool
+parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS")
+
+list(TRANSFORM test-tool_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/t/helper/")
+add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES})
+target_link_libraries(test-tool common-main)
+
+set_target_properties(test-fake-ssh test-tool
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/helper)
+
+if(MSVC)
+ set_target_properties(test-fake-ssh test-tool
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/t/helper)
+ set_target_properties(test-fake-ssh test-tool
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/t/helper)
+endif()
+
+#wrapper scripts
+set(wrapper_scripts
+ git git-upload-pack git-receive-pack git-upload-archive git-shell git-remote-ext)
+
+set(wrapper_test_scripts
+ test-fake-ssh test-tool)
+
+
+foreach(script ${wrapper_scripts})
+ file(STRINGS ${CMAKE_SOURCE_DIR}/wrap-for-bin.sh content NEWLINE_CONSUME)
+ string(REPLACE "@@BUILD_DIR@@" "${CMAKE_BINARY_DIR}" content "${content}")
+ string(REPLACE "@@PROG@@" "${script}${EXE_EXTENSION}" content "${content}")
+ file(WRITE ${CMAKE_BINARY_DIR}/bin-wrappers/${script} ${content})
+endforeach()
+
+foreach(script ${wrapper_test_scripts})
+ file(STRINGS ${CMAKE_SOURCE_DIR}/wrap-for-bin.sh content NEWLINE_CONSUME)
+ string(REPLACE "@@BUILD_DIR@@" "${CMAKE_BINARY_DIR}" content "${content}")
+ string(REPLACE "@@PROG@@" "t/helper/${script}${EXE_EXTENSION}" content "${content}")
+ file(WRITE ${CMAKE_BINARY_DIR}/bin-wrappers/${script} ${content})
+endforeach()
+
+file(STRINGS ${CMAKE_SOURCE_DIR}/wrap-for-bin.sh content NEWLINE_CONSUME)
+string(REPLACE "@@BUILD_DIR@@" "${CMAKE_BINARY_DIR}" content "${content}")
+string(REPLACE "@@PROG@@" "git-cvsserver" content "${content}")
+file(WRITE ${CMAKE_BINARY_DIR}/bin-wrappers/git-cvsserver ${content})
+
+#options for configuring test options
+option(PERL_TESTS "Perform tests that use perl" ON)
+option(PYTHON_TESTS "Perform tests that use python" ON)
+
+#GIT-BUILD-OPTIONS
+set(TEST_SHELL_PATH ${SHELL_PATH})
+set(DIFF diff)
+set(PYTHON_PATH /usr/bin/python)
+set(TAR tar)
+set(NO_CURL )
+set(NO_EXPAT )
+set(USE_LIBPCRE2 )
+set(NO_PERL )
+set(NO_PTHREADS )
+set(NO_PYTHON )
+set(PAGER_ENV "LESS=FRX LV=-c")
+set(DC_SHA1 YesPlease)
+set(RUNTIME_PREFIX true)
+set(NO_GETTEXT )
+
+if(NOT CURL_FOUND)
+ set(NO_CURL 1)
+endif()
+
+if(NOT EXPAT_FOUND)
+ set(NO_EXPAT 1)
+endif()
+
+if(NOT Intl_FOUND)
+ set(NO_GETTEXT 1)
+endif()
+
+if(NOT PERL_TESTS)
+ set(NO_PERL 1)
+endif()
+
+if(NOT PYTHON_TESTS)
+ set(NO_PYTHON 1)
+endif()
+
+file(WRITE ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "SHELL_PATH='${SHELL_PATH}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "TEST_SHELL_PATH='${TEST_SHELL_PATH}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "PERL_PATH='${PERL_PATH}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "DIFF='${DIFF}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "PYTHON_PATH='${PYTHON_PATH}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "TAR='${TAR}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_CURL='${NO_CURL}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_EXPAT='${NO_EXPAT}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PERL='${NO_PERL}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PTHREADS='${NO_PTHREADS}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_UNIX_SOCKETS='${NO_UNIX_SOCKETS}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "PAGER_ENV='${PAGER_ENV}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "DC_SHA1='${DC_SHA1}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "X='${EXE_EXTENSION}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_GETTEXT='${NO_GETTEXT}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "RUNTIME_PREFIX='${RUNTIME_PREFIX}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PYTHON='${NO_PYTHON}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "SUPPORTS_SIMPLE_IPC='${SUPPORTS_SIMPLE_IPC}'\n")
+if(USE_VCPKG)
+ file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "PATH=\"$PATH:$TEST_DIRECTORY/../compat/vcbuild/vcpkg/installed/x64-windows/bin\"\n")
+endif()
+
+#Make the tests work when building out of the source tree
+get_filename_component(CACHE_PATH ${CMAKE_CURRENT_LIST_DIR}/../../CMakeCache.txt ABSOLUTE)
+if(NOT ${CMAKE_BINARY_DIR}/CMakeCache.txt STREQUAL ${CACHE_PATH})
+ file(RELATIVE_PATH BUILD_DIR_RELATIVE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}/CMakeCache.txt)
+ string(REPLACE "/CMakeCache.txt" "" BUILD_DIR_RELATIVE ${BUILD_DIR_RELATIVE})
+ #Setting the build directory in test-lib.sh before running tests
+ file(WRITE ${CMAKE_BINARY_DIR}/CTestCustom.cmake
+ "file(STRINGS ${CMAKE_SOURCE_DIR}/t/test-lib.sh GIT_BUILD_DIR_REPL REGEX \"GIT_BUILD_DIR=(.*)\")\n"
+ "file(STRINGS ${CMAKE_SOURCE_DIR}/t/test-lib.sh content NEWLINE_CONSUME)\n"
+ "string(REPLACE \"\${GIT_BUILD_DIR_REPL}\" \"GIT_BUILD_DIR=\\\"$TEST_DIRECTORY/../${BUILD_DIR_RELATIVE}\\\"\" content \"\${content}\")\n"
+ "file(WRITE ${CMAKE_SOURCE_DIR}/t/test-lib.sh \${content})")
+ #misc copies
+ file(COPY ${CMAKE_SOURCE_DIR}/t/chainlint.sed DESTINATION ${CMAKE_BINARY_DIR}/t/)
+ file(COPY ${CMAKE_SOURCE_DIR}/po/is.po DESTINATION ${CMAKE_BINARY_DIR}/po/)
+ file(COPY ${CMAKE_SOURCE_DIR}/mergetools/tkdiff DESTINATION ${CMAKE_BINARY_DIR}/mergetools/)
+ file(COPY ${CMAKE_SOURCE_DIR}/contrib/completion/git-prompt.sh DESTINATION ${CMAKE_BINARY_DIR}/contrib/completion/)
+ file(COPY ${CMAKE_SOURCE_DIR}/contrib/completion/git-completion.bash DESTINATION ${CMAKE_BINARY_DIR}/contrib/completion/)
+endif()
+
+file(GLOB test_scipts "${CMAKE_SOURCE_DIR}/t/t[0-9]*.sh")
+
+#test
+foreach(tsh ${test_scipts})
+ add_test(NAME ${tsh}
+ COMMAND ${SH_EXE} ${tsh}
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/t)
+endforeach()
+
+endif()#BUILD_TESTING
diff --git a/contrib/buildsystems/Generators.pm b/contrib/buildsystems/Generators.pm
index 408ef714b8..aa4cbaa2ad 100644
--- a/contrib/buildsystems/Generators.pm
+++ b/contrib/buildsystems/Generators.pm
@@ -17,7 +17,7 @@ BEGIN {
$me = dirname($me);
if (opendir(D,"$me/Generators")) {
foreach my $gen (readdir(D)) {
- next if ($gen =~ /^\.\.?$/);
+ next unless ($gen =~ /\.pm$/);
require "${me}/Generators/$gen";
$gen =~ s,\.pm,,;
push(@AVAILABLE, $gen);
diff --git a/contrib/buildsystems/Generators/Vcproj.pm b/contrib/buildsystems/Generators/Vcproj.pm
index cfa74adcc2..737647e76a 100644
--- a/contrib/buildsystems/Generators/Vcproj.pm
+++ b/contrib/buildsystems/Generators/Vcproj.pm
@@ -3,6 +3,7 @@ require Exporter;
use strict;
use vars qw($VERSION);
+use Digest::SHA qw(sha256_hex);
our $VERSION = '1.00';
our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE);
@@ -12,59 +13,12 @@ BEGIN {
push @EXPORT_OK, qw(generate);
}
-my $guid_index = 0;
-my @GUIDS = (
- "{E07B9989-2BF7-4F21-8918-BE22BA467AC3}",
- "{278FFB51-0296-4A44-A81A-22B87B7C3592}",
- "{7346A2C4-F0FD-444F-9EBE-1AF23B2B5650}",
- "{67F421AC-EB34-4D49-820B-3196807B423F}",
- "{385DCFE1-CC8C-4211-A451-80FCFC31CA51}",
- "{97CC46C5-D2CC-4D26-B634-E75792B79916}",
- "{C7CE21FE-6EF8-4012-A5C7-A22BCEDFBA11}",
- "{51575134-3FDF-42D1-BABD-3FB12669C6C9}",
- "{0AE195E4-9823-4B87-8E6F-20C5614AF2FF}",
- "{4B918255-67CA-43BB-A46C-26704B666E6B}",
- "{18CCFEEF-C8EE-4CC1-A265-26F95C9F4649}",
- "{5D5D90FA-01B7-4973-AFE5-CA88C53AC197}",
- "{1F054320-036D-49E1-B384-FB5DF0BC8AC0}",
- "{7CED65EE-F2D9-4171-825B-C7D561FE5786}",
- "{8D341679-0F07-4664-9A56-3BA0DE88B9BC}",
- "{C189FEDC-2957-4BD7-9FA4-7622241EA145}",
- "{66844203-1B9F-4C53-9274-164FFF95B847}",
- "{E4FEA145-DECC-440D-AEEA-598CF381FD43}",
- "{73300A8E-C8AC-41B0-B555-4F596B681BA7}",
- "{873FDEB1-D01D-40BF-A1BF-8BBC58EC0F51}",
- "{7922C8BE-76C5-4AC6-8BF7-885C0F93B782}",
- "{E245D370-308B-4A49-BFC1-1E527827975F}",
- "{F6FA957B-66FC-4ED7-B260-E59BBE4FE813}",
- "{E6055070-0198-431A-BC49-8DB6CEE770AE}",
- "{54159234-C3EB-43DA-906B-CE5DA5C74654}",
- "{594CFC35-0B60-46F6-B8EF-9983ACC1187D}",
- "{D93FCAB7-1F01-48D2-B832-F761B83231A5}",
- "{DBA5E6AC-E7BE-42D3-8703-4E787141526E}",
- "{6171953F-DD26-44C7-A3BE-CC45F86FC11F}",
- "{9E19DDBE-F5E4-4A26-A2FE-0616E04879B8}",
- "{AE81A615-99E3-4885-9CE0-D9CAA193E867}",
- "{FBF4067E-1855-4F6C-8BCD-4D62E801A04D}",
- "{17007948-6593-4AEB-8106-F7884B4F2C19}",
- "{199D4C8D-8639-4DA6-82EF-08668C35DEE0}",
- "{E085E50E-C140-4CF3-BE4B-094B14F0DDD6}",
- "{00785268-A9CC-4E40-AC29-BAC0019159CE}",
- "{4C06F56A-DCDB-46A6-B67C-02339935CF12}",
- "{3A62D3FD-519E-4EC9-8171-D2C1BFEA022F}",
- "{3A62D3FD-519E-4EC9-8171-D2C1BFEA022F}",
- "{9392EB58-D7BA-410B-B1F0-B2FAA6BC89A7}",
- "{2ACAB2D5-E0CE-4027-BCA0-D78B2D7A6C66}",
- "{86E216C3-43CE-481A-BCB2-BE5E62850635}",
- "{FB631291-7923-4B91-9A57-7B18FDBB7A42}",
- "{0A176EC9-E934-45B8-B87F-16C7F4C80039}",
- "{DF55CA80-46E8-4C53-B65B-4990A23DD444}",
- "{3A0F9895-55D2-4710-BE5E-AD7498B5BF44}",
- "{294BDC5A-F448-48B6-8110-DD0A81820F8C}",
- "{4B9F66E9-FAC9-47AB-B1EF-C16756FBFD06}",
- "{72EA49C6-2806-48BD-B81B-D4905102E19C}",
- "{5728EB7E-8929-486C-8CD5-3238D060E768}"
-);
+sub generate_guid ($) {
+ my $hex = sha256_hex($_[0]);
+ $hex =~ s/^(.{8})(.{4})(.{4})(.{4})(.{12}).*/{$1-$2-$3-$4-$5}/;
+ $hex =~ tr/a-z/A-Z/;
+ return $hex;
+}
sub generate {
my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
@@ -92,9 +46,8 @@ sub createLibProject {
$target =~ s/\//_/g;
$target =~ s/\.a//;
- my $uuid = $GUIDS[$guid_index];
+ my $uuid = generate_guid($libname);
$$build_structure{"LIBS_${target}_GUID"} = $uuid;
- $guid_index += 1;
my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"LIBS_${libname}_SOURCES"}}));
my @sources;
@@ -106,6 +59,8 @@ sub createLibProject {
my $includes= join(";", sort(map("&quot;$rel_dir\\$_&quot;", @{$$build_structure{"LIBS_${libname}_INCLUDES"}})));
my $cflags = join(" ", sort(@{$$build_structure{"LIBS_${libname}_CFLAGS"}}));
$cflags =~ s/\"/&quot;/g;
+ $cflags =~ s/</&lt;/g;
+ $cflags =~ s/>/&gt;/g;
my $cflags_debug = $cflags;
$cflags_debug =~ s/-MT/-MTd/;
@@ -127,6 +82,8 @@ sub createLibProject {
$defines =~ s/-D//g;
$defines =~ s/\"/\\&quot;/g;
+ $defines =~ s/</&lt;/g;
+ $defines =~ s/>/&gt;/g;
$defines =~ s/\'//g;
$includes =~ s/-I//g;
mkdir "$target" || die "Could not create the directory $target for lib project!\n";
@@ -163,9 +120,6 @@ sub createLibProject {
Name="VCXMLDataGeneratorTool"
/>
<Tool
- Name="VCWebServiceProxyGeneratorTool"
- />
- <Tool
Name="VCMIDLTool"
/>
<Tool
@@ -229,9 +183,6 @@ sub createLibProject {
Name="VCXMLDataGeneratorTool"
/>
<Tool
- Name="VCWebServiceProxyGeneratorTool"
- />
- <Tool
Name="VCMIDLTool"
/>
<Tool
@@ -311,9 +262,8 @@ sub createAppProject {
$target =~ s/\//_/g;
$target =~ s/\.exe//;
- my $uuid = $GUIDS[$guid_index];
+ my $uuid = generate_guid($appname);
$$build_structure{"APPS_${target}_GUID"} = $uuid;
- $guid_index += 1;
my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"APPS_${appname}_SOURCES"}}));
my @sources;
@@ -325,6 +275,8 @@ sub createAppProject {
my $includes= join(";", sort(map("&quot;$rel_dir\\$_&quot;", @{$$build_structure{"APPS_${appname}_INCLUDES"}})));
my $cflags = join(" ", sort(@{$$build_structure{"APPS_${appname}_CFLAGS"}}));
$cflags =~ s/\"/&quot;/g;
+ $cflags =~ s/</&lt;/g;
+ $cflags =~ s/>/&gt;/g;
my $cflags_debug = $cflags;
$cflags_debug =~ s/-MT/-MTd/;
@@ -351,6 +303,8 @@ sub createAppProject {
$defines =~ s/-D//g;
$defines =~ s/\"/\\&quot;/g;
+ $defines =~ s/</&lt;/g;
+ $defines =~ s/>/&gt;/g;
$defines =~ s/\'//g;
$defines =~ s/\\\\/\\/g;
$includes =~ s/-I//g;
@@ -388,9 +342,6 @@ sub createAppProject {
Name="VCXMLDataGeneratorTool"
/>
<Tool
- Name="VCWebServiceProxyGeneratorTool"
- />
- <Tool
Name="VCMIDLTool"
/>
<Tool
@@ -459,9 +410,6 @@ sub createAppProject {
Name="VCXMLDataGeneratorTool"
/>
<Tool
- Name="VCWebServiceProxyGeneratorTool"
- />
- <Tool
Name="VCMIDLTool"
/>
<Tool
@@ -561,20 +509,18 @@ sub createGlueProject {
foreach (@apps) {
$_ =~ s/\//_/g;
$_ =~ s/\.exe//;
- push(@tmp, $_);
+ if ($_ eq "git" ) {
+ unshift(@tmp, $_);
+ } else {
+ push(@tmp, $_);
+ }
}
@apps = @tmp;
open F, ">git.sln" || die "Could not open git.sln for writing!\n";
binmode F, ":crlf";
print F "$SLN_HEAD";
- foreach (@libs) {
- my $libname = $_;
- my $uuid = $build_structure{"LIBS_${libname}_GUID"};
- print F "$SLN_PRE";
- print F "\"${libname}\", \"${libname}\\${libname}.vcproj\", \"${uuid}\"";
- print F "$SLN_POST";
- }
+
my $uuid_libgit = $build_structure{"LIBS_libgit_GUID"};
my $uuid_xdiff_lib = $build_structure{"LIBS_xdiff_lib_GUID"};
foreach (@apps) {
@@ -588,6 +534,13 @@ sub createGlueProject {
print F " EndProjectSection";
print F "$SLN_POST";
}
+ foreach (@libs) {
+ my $libname = $_;
+ my $uuid = $build_structure{"LIBS_${libname}_GUID"};
+ print F "$SLN_PRE";
+ print F "\"${libname}\", \"${libname}\\${libname}.vcproj\", \"${uuid}\"";
+ print F "$SLN_POST";
+ }
print F << "EOM";
Global
@@ -599,17 +552,17 @@ EOM
print F << "EOM";
GlobalSection(ProjectConfigurationPlatforms) = postSolution
EOM
- foreach (@libs) {
- my $libname = $_;
- my $uuid = $build_structure{"LIBS_${libname}_GUID"};
+ foreach (@apps) {
+ my $appname = $_;
+ my $uuid = $build_structure{"APPS_${appname}_GUID"};
print F "\t\t${uuid}.Debug|Win32.ActiveCfg = Debug|Win32\n";
print F "\t\t${uuid}.Debug|Win32.Build.0 = Debug|Win32\n";
print F "\t\t${uuid}.Release|Win32.ActiveCfg = Release|Win32\n";
print F "\t\t${uuid}.Release|Win32.Build.0 = Release|Win32\n";
}
- foreach (@apps) {
- my $appname = $_;
- my $uuid = $build_structure{"APPS_${appname}_GUID"};
+ foreach (@libs) {
+ my $libname = $_;
+ my $uuid = $build_structure{"LIBS_${libname}_GUID"};
print F "\t\t${uuid}.Debug|Win32.ActiveCfg = Debug|Win32\n";
print F "\t\t${uuid}.Debug|Win32.Build.0 = Debug|Win32\n";
print F "\t\t${uuid}.Release|Win32.ActiveCfg = Release|Win32\n";
diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm
new file mode 100644
index 0000000000..d2584450ba
--- /dev/null
+++ b/contrib/buildsystems/Generators/Vcxproj.pm
@@ -0,0 +1,393 @@
+package Generators::Vcxproj;
+require Exporter;
+
+use strict;
+use vars qw($VERSION);
+use Digest::SHA qw(sha256_hex);
+
+our $VERSION = '1.00';
+our(@ISA, @EXPORT, @EXPORT_OK, @AVAILABLE);
+@ISA = qw(Exporter);
+
+BEGIN {
+ push @EXPORT_OK, qw(generate);
+}
+
+sub generate_guid ($) {
+ my $hex = sha256_hex($_[0]);
+ $hex =~ s/^(.{8})(.{4})(.{4})(.{4})(.{12}).*/{$1-$2-$3-$4-$5}/;
+ $hex =~ tr/a-z/A-Z/;
+ return $hex;
+}
+
+sub generate {
+ my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
+ my @libs = @{$build_structure{"LIBS"}};
+ foreach (@libs) {
+ createProject($_, $git_dir, $out_dir, $rel_dir, \%build_structure, 1);
+ }
+
+ my @apps = @{$build_structure{"APPS"}};
+ foreach (@apps) {
+ createProject($_, $git_dir, $out_dir, $rel_dir, \%build_structure, 0);
+ }
+
+ createGlueProject($git_dir, $out_dir, $rel_dir, %build_structure);
+ return 0;
+}
+
+sub createProject {
+ my ($name, $git_dir, $out_dir, $rel_dir, $build_structure, $static_library) = @_;
+ my $label = $static_library ? "lib" : "app";
+ my $prefix = $static_library ? "LIBS_" : "APPS_";
+ my $config_type = $static_library ? "StaticLibrary" : "Application";
+ print "Generate $name vcxproj $label project\n";
+ my $cdup = $name;
+ $cdup =~ s/[^\/]+/../g;
+ $cdup =~ s/\//\\/g;
+ $rel_dir = $rel_dir eq "." ? $cdup : "$cdup\\$rel_dir";
+ $rel_dir =~ s/\//\\/g;
+
+ my $target = $name;
+ if ($static_library) {
+ $target =~ s/\.a//;
+ } else {
+ $target =~ s/\.exe//;
+ }
+
+ my $uuid = generate_guid($name);
+ $$build_structure{"$prefix${target}_GUID"} = $uuid;
+ my $vcxproj = $target;
+ $vcxproj =~ s/(.*\/)?(.*)/$&\/$2.vcxproj/;
+ $vcxproj =~ s/([^\/]*)(\/lib)\/(lib.vcxproj)/$1$2\/$1_$3/;
+ $$build_structure{"$prefix${target}_VCXPROJ"} = $vcxproj;
+
+ my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"$prefix${name}_SOURCES"}}));
+ my @sources;
+ foreach (@srcs) {
+ $_ =~ s/\//\\/g;
+ push(@sources, $_);
+ }
+ my $defines = join(";", sort(@{$$build_structure{"$prefix${name}_DEFINES"}}));
+ my $includes= join(";", sort(map { s/^-I//; s/\//\\/g; File::Spec->file_name_is_absolute($_) ? $_ : "$rel_dir\\$_" } @{$$build_structure{"$prefix${name}_INCLUDES"}}));
+ my $cflags = join(" ", sort(map { s/^-[GLMOWZ].*//; s/.* .*/"$&"/; $_; } @{$$build_structure{"$prefix${name}_CFLAGS"}}));
+ $cflags =~ s/</&lt;/g;
+ $cflags =~ s/>/&gt;/g;
+
+ my $libs_release = "\n ";
+ my $libs_debug = "\n ";
+ if (!$static_library) {
+ $libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}}));
+ $libs_debug = $libs_release;
+ $libs_debug =~ s/zlib\.lib/zlibd\.lib/g;
+ $libs_debug =~ s/libexpat\.lib/libexpatd\.lib/g;
+ $libs_debug =~ s/libcurl\.lib/libcurl-d\.lib/g;
+ }
+
+ $defines =~ s/-D//g;
+ $defines =~ s/</&lt;/g;
+ $defines =~ s/>/&gt;/g;
+ $defines =~ s/\'//g;
+
+ die "Could not create the directory $target for $label project!\n" unless (-d "$target" || mkdir "$target");
+
+ open F, ">$vcxproj" or die "Could not open $vcxproj for writing!\n";
+ binmode F, ":crlf :utf8";
+ print F chr(0xFEFF);
+ print F << "EOM";
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>$uuid</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <VCPKGArch Condition="'\$(Platform)'=='Win32'">x86-windows</VCPKGArch>
+ <VCPKGArch Condition="'\$(Platform)'!='Win32'">x64-windows</VCPKGArch>
+ <VCPKGArchDirectory>$cdup\\compat\\vcbuild\\vcpkg\\installed\\\$(VCPKGArch)</VCPKGArchDirectory>
+ <VCPKGBinDirectory Condition="'\$(Configuration)'=='Debug'">\$(VCPKGArchDirectory)\\debug\\bin</VCPKGBinDirectory>
+ <VCPKGLibDirectory Condition="'\$(Configuration)'=='Debug'">\$(VCPKGArchDirectory)\\debug\\lib</VCPKGLibDirectory>
+ <VCPKGBinDirectory Condition="'\$(Configuration)'!='Debug'">\$(VCPKGArchDirectory)\\bin</VCPKGBinDirectory>
+ <VCPKGLibDirectory Condition="'\$(Configuration)'!='Debug'">\$(VCPKGArchDirectory)\\lib</VCPKGLibDirectory>
+ <VCPKGIncludeDirectory>\$(VCPKGArchDirectory)\\include</VCPKGIncludeDirectory>
+ <VCPKGLibs Condition="'\$(Configuration)'=='Debug'">$libs_debug</VCPKGLibs>
+ <VCPKGLibs Condition="'\$(Configuration)'!='Debug'">$libs_release</VCPKGLibs>
+ </PropertyGroup>
+ <Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'\$(Configuration)'=='Debug'" Label="Configuration">
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'\$(Configuration)'=='Release'" Label="Configuration">
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup>
+ <ConfigurationType>$config_type</ConfigurationType>
+ <PlatformToolset>v140</PlatformToolset>
+ <!-- <CharacterSet>UTF-8</CharacterSet> -->
+ <OutDir>..\\</OutDir>
+ <!-- <IntDir>\$(ProjectDir)\$(Configuration)\\</IntDir> -->
+ </PropertyGroup>
+ <Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets">
+ <Import Project="\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props" Condition="exists('\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <GenerateManifest>false</GenerateManifest>
+ <EnableManagedIncrementalBuild>true</EnableManagedIncrementalBuild>
+ </PropertyGroup>
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <AdditionalOptions>$cflags %(AdditionalOptions)</AdditionalOptions>
+ <AdditionalIncludeDirectories>$cdup;$cdup\\compat;$cdup\\compat\\regex;$cdup\\compat\\win32;$cdup\\compat\\poll;$cdup\\compat\\vcbuild\\include;\$(VCPKGIncludeDirectory);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <EnableParallelCodeGeneration />
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <PrecompiledHeader />
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ </ClCompile>
+ <Lib>
+ <SuppressStartupBanner>true</SuppressStartupBanner>
+ </Lib>
+ <Link>
+ <AdditionalLibraryDirectories>\$(VCPKGLibDirectory);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>\$(VCPKGLibs);\$(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalOptions>invalidcontinue.obj %(AdditionalOptions)</AdditionalOptions>
+ <EntryPointSymbol>wmainCRTStartup</EntryPointSymbol>
+ <ManifestFile>$cdup\\compat\\win32\\git.manifest</ManifestFile>
+ <SubSystem>Console</SubSystem>
+ </Link>
+EOM
+ if ($target eq 'libgit') {
+ print F << "EOM";
+ <PreBuildEvent Condition="!Exists('$cdup\\compat\\vcbuild\\vcpkg\\installed\\\$(VCPKGArch)\\include\\openssl\\ssl.h')">
+ <Message>Initialize VCPKG</Message>
+ <Command>del "$cdup\\compat\\vcbuild\\vcpkg"</Command>
+ <Command>call "$cdup\\compat\\vcbuild\\vcpkg_install.bat"</Command>
+ </PreBuildEvent>
+EOM
+ }
+ print F << "EOM";
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'\$(Platform)'=='Win32'">
+ <Link>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'\$(Configuration)'=='Debug'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;$defines;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'\$(Configuration)'=='Release'">
+ <ClCompile>
+ <Optimization>MaxSpeed</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;$defines;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+EOM
+ foreach(@sources) {
+ print F << "EOM";
+ <ClCompile Include="$_" />
+EOM
+ }
+ print F << "EOM";
+ </ItemGroup>
+EOM
+ if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') {
+ my $uuid_libgit = $$build_structure{"LIBS_libgit_GUID"};
+ my $uuid_xdiff_lib = $$build_structure{"LIBS_xdiff/lib_GUID"};
+
+ print F << "EOM";
+ <ItemGroup>
+ <ProjectReference Include="$cdup\\libgit\\libgit.vcxproj">
+ <Project>$uuid_libgit</Project>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ </ProjectReference>
+EOM
+ if (!($name =~ 'xdiff')) {
+ print F << "EOM";
+ <ProjectReference Include="$cdup\\xdiff\\lib\\xdiff_lib.vcxproj">
+ <Project>$uuid_xdiff_lib</Project>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ </ProjectReference>
+EOM
+ }
+ if ($name =~ /(test-(line-buffer|svn-fe)|^git-remote-testsvn)\.exe$/) {
+ my $uuid_vcs_svn_lib = $$build_structure{"LIBS_vcs-svn/lib_GUID"};
+ print F << "EOM";
+ <ProjectReference Include="$cdup\\vcs-svn\\lib\\vcs-svn_lib.vcxproj">
+ <Project>$uuid_vcs_svn_lib</Project>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ </ProjectReference>
+EOM
+ }
+ print F << "EOM";
+ </ItemGroup>
+EOM
+ }
+ print F << "EOM";
+ <Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.targets" />
+EOM
+ if (!$static_library) {
+ print F << "EOM";
+ <Target Name="${target}_AfterBuild" AfterTargets="AfterBuild">
+ <ItemGroup>
+ <DLLsAndPDBs Include="\$(VCPKGBinDirectory)\\*.dll;\$(VCPKGBinDirectory)\\*.pdb" />
+ </ItemGroup>
+ <Copy SourceFiles="@(DLLsAndPDBs)" DestinationFolder="\$(OutDir)" SkipUnchangedFiles="true" UseHardlinksIfPossible="true" />
+ <MakeDir Directories="..\\templates\\blt\\branches" />
+ </Target>
+EOM
+ }
+ if ($target eq 'git') {
+ print F " <Import Project=\"LinkOrCopyBuiltins.targets\" />\n";
+ }
+ if ($target eq 'git-remote-http') {
+ print F " <Import Project=\"LinkOrCopyRemoteHttp.targets\" />\n";
+ }
+ print F << "EOM";
+</Project>
+EOM
+ close F;
+}
+
+sub createGlueProject {
+ my ($git_dir, $out_dir, $rel_dir, %build_structure) = @_;
+ print "Generate solutions file\n";
+ $rel_dir = "..\\$rel_dir";
+ $rel_dir =~ s/\//\\/g;
+ my $SLN_HEAD = "Microsoft Visual Studio Solution File, Format Version 11.00\n# Visual Studio 2010\n";
+ my $SLN_PRE = "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = ";
+ my $SLN_POST = "\nEndProject\n";
+
+ my @libs = @{$build_structure{"LIBS"}};
+ my @tmp;
+ foreach (@libs) {
+ $_ =~ s/\.a//;
+ push(@tmp, $_);
+ }
+ @libs = @tmp;
+
+ my @apps = @{$build_structure{"APPS"}};
+ @tmp = ();
+ foreach (@apps) {
+ $_ =~ s/\.exe//;
+ if ($_ eq "git" ) {
+ unshift(@tmp, $_);
+ } else {
+ push(@tmp, $_);
+ }
+ }
+ @apps = @tmp;
+
+ open F, ">git.sln" || die "Could not open git.sln for writing!\n";
+ binmode F, ":crlf :utf8";
+ print F chr(0xFEFF);
+ print F "$SLN_HEAD";
+
+ foreach (@apps) {
+ my $appname = $_;
+ my $uuid = $build_structure{"APPS_${appname}_GUID"};
+ print F "$SLN_PRE";
+ my $vcxproj = $build_structure{"APPS_${appname}_VCXPROJ"};
+ $vcxproj =~ s/\//\\/g;
+ $appname =~ s/.*\///;
+ print F "\"${appname}\", \"${vcxproj}\", \"${uuid}\"";
+ print F "$SLN_POST";
+ }
+ foreach (@libs) {
+ my $libname = $_;
+ my $uuid = $build_structure{"LIBS_${libname}_GUID"};
+ print F "$SLN_PRE";
+ my $vcxproj = $build_structure{"LIBS_${libname}_VCXPROJ"};
+ $vcxproj =~ s/\//\\/g;
+ $libname =~ s/\//_/g;
+ print F "\"${libname}\", \"${vcxproj}\", \"${uuid}\"";
+ print F "$SLN_POST";
+ }
+
+ print F << "EOM";
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+EOM
+ print F << "EOM";
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+EOM
+ foreach (@apps) {
+ my $appname = $_;
+ my $uuid = $build_structure{"APPS_${appname}_GUID"};
+ print F "\t\t${uuid}.Debug|x64.ActiveCfg = Debug|x64\n";
+ print F "\t\t${uuid}.Debug|x64.Build.0 = Debug|x64\n";
+ print F "\t\t${uuid}.Debug|x86.ActiveCfg = Debug|Win32\n";
+ print F "\t\t${uuid}.Debug|x86.Build.0 = Debug|Win32\n";
+ print F "\t\t${uuid}.Release|x64.ActiveCfg = Release|x64\n";
+ print F "\t\t${uuid}.Release|x64.Build.0 = Release|x64\n";
+ print F "\t\t${uuid}.Release|x86.ActiveCfg = Release|Win32\n";
+ print F "\t\t${uuid}.Release|x86.Build.0 = Release|Win32\n";
+ }
+ foreach (@libs) {
+ my $libname = $_;
+ my $uuid = $build_structure{"LIBS_${libname}_GUID"};
+ print F "\t\t${uuid}.Debug|x64.ActiveCfg = Debug|x64\n";
+ print F "\t\t${uuid}.Debug|x64.Build.0 = Debug|x64\n";
+ print F "\t\t${uuid}.Debug|x86.ActiveCfg = Debug|Win32\n";
+ print F "\t\t${uuid}.Debug|x86.Build.0 = Debug|Win32\n";
+ print F "\t\t${uuid}.Release|x64.ActiveCfg = Release|x64\n";
+ print F "\t\t${uuid}.Release|x64.Build.0 = Release|x64\n";
+ print F "\t\t${uuid}.Release|x86.ActiveCfg = Release|Win32\n";
+ print F "\t\t${uuid}.Release|x86.Build.0 = Release|Win32\n";
+ }
+
+ print F << "EOM";
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
+EOM
+ close F;
+}
+
+1;
diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl
index 23da787dc5..ed6c45988a 100644..100755
--- a/contrib/buildsystems/engine.pl
+++ b/contrib/buildsystems/engine.pl
@@ -12,6 +12,7 @@ use File::Basename;
use File::Spec;
use Cwd;
use Generators;
+use Text::ParseWords;
my (%build_structure, %compile_options, @makedry);
my $out_dir = getcwd();
@@ -31,6 +32,7 @@ generate usage:
-g <GENERATOR> --gen <GENERATOR> Specify the buildsystem generator (default: $gen)
Available: $genlist
-o <PATH> --out <PATH> Specify output directory generation (default: .)
+ --make-out <PATH> Write the output of GNU Make into a file
-i <FILE> --in <FILE> Specify input file, instead of running GNU Make
-h,-? --help This help
EOM
@@ -38,6 +40,7 @@ EOM
}
# Parse command-line options
+my $make_out;
while (@ARGV) {
my $arg = shift @ARGV;
if ("$arg" eq "-h" || "$arg" eq "--help" || "$arg" eq "-?") {
@@ -45,6 +48,8 @@ while (@ARGV) {
exit(0);
} elsif("$arg" eq "--out" || "$arg" eq "-o") {
$out_dir = shift @ARGV;
+ } elsif("$arg" eq "--make-out") {
+ $make_out = shift @ARGV;
} elsif("$arg" eq "--gen" || "$arg" eq "-g") {
$gen = shift @ARGV;
} elsif("$arg" eq "--in" || "$arg" eq "-i") {
@@ -52,6 +57,8 @@ while (@ARGV) {
open(F, "<$infile") || die "Couldn't open file $infile";
@makedry = <F>;
close(F);
+ } else {
+ die "Unknown option: " . $arg;
}
}
@@ -72,7 +79,19 @@ Running GNU Make to figure out build structure...
EOM
# Pipe a make --dry-run into a variable, if not already loaded from file
-@makedry = `cd $git_dir && make -n MSVC=1 V=1 2>/dev/null` if !@makedry;
+# Capture the make dry stderr to file for review (will be empty for a release build).
+
+my $ErrsFile = "msvc-build-makedryerrors.txt";
+@makedry = `make -C $git_dir -n MSVC=1 SKIP_VCPKG=1 V=1 2>$ErrsFile`
+if !@makedry;
+# test for an empty Errors file and remove it
+unlink $ErrsFile if -f -z $ErrsFile;
+
+if (defined $make_out) {
+ open OUT, ">" . $make_out;
+ print OUT @makedry;
+ close OUT;
+}
# Parse the make output into usable info
parseMakeOutput();
@@ -140,6 +159,12 @@ sub parseMakeOutput
next;
}
+ if ($text =~ /^(mkdir|msgfmt) /) {
+ # options to the Portable Object translations
+ # the line "mkdir ... && msgfmt ..." contains no linker options
+ next;
+ }
+
if($text =~ / -c /) {
# compilation
handleCompileLine($text, $line);
@@ -231,7 +256,7 @@ sub removeDuplicates
sub handleCompileLine
{
my ($line, $lineno) = @_;
- my @parts = split(' ', $line);
+ my @parts = shellwords($line);
my $sourcefile;
shift(@parts); # ignore cmd
while (my $part = shift @parts) {
@@ -265,7 +290,7 @@ sub handleLibLine
my (@objfiles, @lflags, $libout, $part);
# kill cmd and rm 'prefix'
$line =~ s/^rm -f .* && .* rcs //;
- my @parts = split(' ', $line);
+ my @parts = shellwords($line);
while ($part = shift @parts) {
if ($part =~ /^-/) {
push(@lflags, $part);
@@ -282,7 +307,7 @@ sub handleLibLine
# exit(1);
foreach (@objfiles) {
my $sourcefile = $_;
- $sourcefile =~ s/\.o/.c/;
+ $sourcefile =~ s/\.o$/.c/;
push(@sources, $sourcefile);
push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});
push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}});
@@ -306,7 +331,7 @@ sub handleLinkLine
{
my ($line, $lineno) = @_;
my (@objfiles, @lflags, @libs, $appout, $part);
- my @parts = split(' ', $line);
+ my @parts = shellwords($line);
shift(@parts); # ignore cmd
while ($part = shift @parts) {
if ($part =~ /^-IGNORE/) {
@@ -317,26 +342,36 @@ sub handleLinkLine
$appout = shift @parts;
} elsif ("$part" eq "-lz") {
push(@libs, "zlib.lib");
- } elsif ("$part" eq "-lcrypto") {
- push(@libs, "libeay32.lib");
+ } elsif ("$part" eq "-lcrypto") {
+ push(@libs, "libcrypto.lib");
} elsif ("$part" eq "-lssl") {
- push(@libs, "ssleay32.lib");
- } elsif ($part =~ /^-/) {
+ push(@libs, "libssl.lib");
+ } elsif ("$part" eq "-lcurl") {
+ push(@libs, "libcurl.lib");
+ } elsif ("$part" eq "-lexpat") {
+ push(@libs, "libexpat.lib");
+ } elsif ("$part" eq "-liconv") {
+ push(@libs, "iconv.lib");
+ } elsif ($part =~ /^[-\/]/) {
push(@lflags, $part);
} elsif ($part =~ /\.(a|lib)$/) {
$part =~ s/\.a$/.lib/;
push(@libs, $part);
- } elsif ($part =~ /\.(o|obj)$/) {
+ } elsif ($part eq 'invalidcontinue.obj') {
+ # ignore - known to MSVC
+ } elsif ($part =~ /\.o$/) {
push(@objfiles, $part);
+ } elsif ($part =~ /\.obj$/) {
+ # do nothing, 'make' should not be producing .obj, only .o files
} else {
- die "Unhandled lib option @ line $lineno: $part";
+ die "Unhandled link option @ line $lineno: $part";
}
}
# print "AppOut: '$appout'\nLFlags: @lflags\nLibs : @libs\nOfiles: @objfiles\n";
# exit(1);
foreach (@objfiles) {
my $sourcefile = $_;
- $sourcefile =~ s/\.o/.c/;
+ $sourcefile =~ s/\.o$/.c/;
push(@sources, $sourcefile);
push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});
push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}});
diff --git a/contrib/buildsystems/generate b/contrib/buildsystems/generate
index bc10f25ff2..bc10f25ff2 100644..100755
--- a/contrib/buildsystems/generate
+++ b/contrib/buildsystems/generate
diff --git a/contrib/buildsystems/parse.pl b/contrib/buildsystems/parse.pl
index c9656ece99..c9656ece99 100644..100755
--- a/contrib/buildsystems/parse.pl
+++ b/contrib/buildsystems/parse.pl
diff --git a/contrib/ciabot/INSTALL b/contrib/ciabot/INSTALL
deleted file mode 100644
index 7222961d35..0000000000
--- a/contrib/ciabot/INSTALL
+++ /dev/null
@@ -1,54 +0,0 @@
-= Installation instructions =
-
-Two scripts are included. The Python one (ciabot.py) is faster and
-more capable; the shell one (ciabot.sh) is a fallback in case Python
-gives your git hosting site indigestion. (I know of no such sites.)
-
-It is no longer necessary to modify the script in order to put it
-in place; in fact, this is now discouraged. It is entirely
-configurable with the following git config variables:
-
-ciabot.project = name of the project
-ciabot.repo = name of the project repo for gitweb/cgit purposes
-ciabot.xmlrpc = if true, ship notifications via XML-RPC
-ciabot.revformat = format in which the revision is shown
-
-The revformat variable may have the following values
-raw -> full hex ID of commit
-short -> first 12 chars of hex ID
-describe -> describe relative to last tag, falling back to short
-
-ciabot.project defaults to the directory name of the repository toplevel.
-ciabot.repo defaults to ciabot.project lowercased.
-ciabot.xmlrpc defaults to True
-ciabot.revformat defaults to 'describe'.
-
-This means that in the normal case you need not do any configuration at all,
-however setting ciabot.project will allow the hook to run slightly faster.
-
-Once you've set these variables, try your script with -n to see the
-notification message dumped to stdout and verify that it looks sane.
-
-To live-test these scripts, your project needs to have been registered with
-the CIA site. Here are the steps:
-
-1. Open an IRC window on irc://freenode/commits or your registered
- project IRC channel.
-
-2. Run ciabot.py and/or ciabot.sh from any directory under git
- control.
-
-You should see a notification on the channel for your most recent commit.
-
-After verifying correct function, install one of these scripts either
-in a post-commit hook or in an update hook.
-
-In post-commit, run it without arguments. 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 because it lists
-from most recent to oldest.
-
-/path/to/ciabot.py ${refname} $(git rev-list ${oldhead}..${newhead} | tac)
diff --git a/contrib/ciabot/README b/contrib/ciabot/README
deleted file mode 100644
index 2dfe1f91f5..0000000000
--- a/contrib/ciabot/README
+++ /dev/null
@@ -1,11 +0,0 @@
-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.
-
-See the file INSTALL for installation instructions.
diff --git a/contrib/ciabot/ciabot.py b/contrib/ciabot/ciabot.py
deleted file mode 100755
index bd24395d4c..0000000000
--- a/contrib/ciabot/ciabot.py
+++ /dev/null
@@ -1,249 +0,0 @@
-#!/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. 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. 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 because it lists
-# from most recent to oldest.
-#
-# /path/to/ciabot.py ${refname} $(git rev-list ${oldhead}..${newhead} | tac)
-#
-# Configuration variables affecting this script:
-#
-# ciabot.project = name of the project
-# ciabot.repo = name of the project repo for gitweb/cgit purposes
-# ciabot.xmlrpc = if true (default), ship notifications via XML-RPC
-# ciabot.revformat = format in which the revision is shown
-#
-# ciabot.project defaults to the directory name of the repository toplevel.
-# ciabot.repo defaults to ciabot.project lowercased.
-#
-# This means that in the normal case you need not do any configuration at all,
-# but setting the project name will speed it up slightly.
-#
-# The revformat variable may have the following values
-# raw -> full hex ID of commit
-# short -> first 12 chars of hex ID
-# describe = -> describe relative to last tag, falling back to short
-# The default is 'describe'.
-#
-# Note: the CIA project now says only XML-RPC is reliable, so
-# we default to that.
-#
-
-import os, sys, commands, socket, urllib
-from xml.sax.saxutils import escape
-
-# 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>%(version)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:
-#
-
-# Where to ship e-mail notifications.
-toaddr = "cia@cia.vc"
-
-# 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"
-version = "3.6"
-
-def do(command):
- return commands.getstatusoutput(command)[1]
-
-def report(refname, merged, xmlrpc=True):
- "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 description for the revision
- if revformat == 'raw':
- rev = merged
- elif revformat == 'short':
- rev = ''
- else: # revformat == 'describe'
- rev = do("git describe %s 2>/dev/null" % merged)
- if not rev:
- rev = merged[:12]
-
- # Extract the meta-information for the commit
- files=do("git diff-tree -r --name-only '"+ merged +"' | sed -e '1d' -e 's-.*-<file>&</file>-'")
- metainfo = do("git log -1 '--pretty=format:%an <%ae>%n%at%n%s' " + merged)
- (author, ts, logmsg) = metainfo.split("\n")
- logmsg = escape(logmsg)
-
- # This discards the part of the author's address after @.
- # Might be 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.
- author = escape(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
- mail = '''\
-Message-ID: <%(merged)s.%(author)s@%(project)s>
-From: %(fromaddr)s
-To: %(toaddr)s
-Content-type: text/xml
-Subject: DeliverXML
-
-%(out)s''' % locals()
-
- if xmlrpc:
- return out
- else:
- return mail
-
-if __name__ == "__main__":
- import getopt
-
- # Get all config variables
- revformat = do("git config --get ciabot.revformat")
- project = do("git config --get ciabot.project")
- repo = do("git config --get ciabot.repo")
- xmlrpc = do("git config --get ciabot.xmlrpc")
- xmlrpc = not (xmlrpc and xmlrpc == "false")
-
- host = socket.getfqdn()
- fromaddr = "CIABOT-NOREPLY@" + host
-
- try:
- (options, arguments) = getopt.getopt(sys.argv[1:], "np:xV")
- except getopt.GetoptError, msg:
- print "ciabot.py: " + str(msg)
- raise SystemExit, 1
-
- notify = True
- for (switch, val) in options:
- if switch == '-p':
- project = val
- elif switch == '-n':
- notify = False
- elif switch == '-x':
- xmlrpc = True
- elif switch == '-V':
- print "ciabot.py: version", version
- sys.exit(0)
-
- # The project variable defaults to the name of the repository toplevel.
- if not project:
- here = os.getcwd()
- while True:
- if os.path.exists(os.path.join(here, ".git")):
- project = os.path.basename(here)
- break
- elif here == '/':
- sys.stderr.write("ciabot.py: no .git below root!\n")
- sys.exit(1)
- here = os.path.dirname(here)
-
- if not repo:
- repo = project.lower()
-
- 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 notify:
- if xmlrpc:
- import xmlrpclib
- server = xmlrpclib.Server('http://cia.vc/RPC2');
- else:
- import smtplib
- server = smtplib.SMTP('localhost')
-
- for merged in merges:
- message = report(refname, merged, xmlrpc)
- if not notify:
- print message
- elif xmlrpc:
- try:
- # RPC server is flaky, this can fail due to timeout.
- server.hub.deliver(message)
- except socket.error, e:
- sys.stderr.write("%s\n" % e)
- else:
- server.sendmail(fromaddr, [toaddr], message)
-
- if notify:
- if not xmlrpc:
- server.quit()
-
-#End
diff --git a/contrib/ciabot/ciabot.sh b/contrib/ciabot/ciabot.sh
deleted file mode 100755
index 3fbbc534ae..0000000000
--- a/contrib/ciabot/ciabot.sh
+++ /dev/null
@@ -1,233 +0,0 @@
-#!/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>
-# Assistance and review by Petr Baudis, author of ciabot.pl,
-# is gratefully acknowledged.
-#
-# 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.
-# It requires: git(1), hostname(1), cut(1), sendmail(1), and wget(1).
-#
-# 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. 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. 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.sh ${refname} ${merged}
-# done
-#
-# The reason for the tac call is that git rev-list emits commits from
-# most recent to least - better to ship notifactions from oldest to newest.
-#
-# Configuration variables affecting this script:
-#
-# ciabot.project = name of the project
-# ciabot.repo = name of the project repo for gitweb/cgit purposes
-# ciabot.revformat = format in which the revision is shown
-#
-# ciabot.project defaults to the directory name of the repository toplevel.
-# ciabot.repo defaults to ciabot.project lowercased.
-#
-# This means that in the normal case you need not do any configuration at all,
-# but setting the project name will speed it up slightly.
-#
-# The revformat variable may have the following values
-# raw -> full hex ID of commit
-# short -> first 12 chars of hex ID
-# describe = -> describe relative to last tag, falling back to short
-# The default is 'describe'.
-#
-# Note: the shell ancestors of this script used mail, not XML-RPC, in
-# order to avoid stalling until timeout when the CIA XML-RPC server is
-# down. It is unknown whether this is still an issue in 2010, but
-# XML-RPC would be annoying to do from sh in any case. (XML-RPC does
-# have the advantage that it guarantees notification of multiple commits
-# shpped from an update in their actual order.)
-#
-
-# The project as known to CIA. You can set this with a -p option,
-# or let it default to the directory name of the repo toplevel.
-project=$(git config --get ciabot.project)
-
-if [ -z $project ]
-then
- here=`pwd`;
- while :; do
- if [ -d $here/.git ]
- then
- project=`basename $here`
- break
- elif [ $here = '/' ]
- then
- echo "ciabot.sh: no .git below root!"
- exit 1
- fi
- here=`dirname $here`
- done
-fi
-
-# Name of the repo for gitweb/cgit purposes
-repo=$(git config --get ciabot.repo)
-[ -z $repo] && repo=$(echo "${project}" | tr '[A-Z]' '[a-z]')
-
-# What revision format do we want in the summary?
-revformat=$(git config --get ciabot.revformat)
-
-# Fully qualified domain name of the repo host. You can hardwire this
-# to make the script faster. The -f option works under Linux and FreeBSD,
-# but not OpenBSD and NetBSD. But under OpenBSD and NetBSD,
-# hostname without options gives the FQDN.
-if hostname -f >/dev/null 2>&1
-then
- hostname=`hostname -f`
-else
- hostname=`hostname`
-fi
-
-# 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. The 'generator' variable should change only
-# when the script itself gets a new home and maintainer.
-generator="http://www.catb.org/~esr/ciabot/ciabot.sh"
-version=3.5
-
-# Addresses for the e-mail
-from="CIABOT-NOREPLY@${hostname}"
-to="cia@cia.vc"
-
-# 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 $version"; 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/}
-
-case $revformat in
-raw) rev=$merged ;;
-short) rev='' ;;
-*) rev=$(git describe ${merged} 2>/dev/null) ;;
-esac
-[ -z ${rev} ] && rev=$(echo "$merged" | cut -c 1-12)
-
-# We discard 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.
-author=$(git log -1 '--pretty=format:%an <%ae>' $merged)
-author=$(echo "$author" | sed -n -e '/^.*<\([^@]*\).*$/s--\1-p')
-
-logmessage=$(git log -1 '--pretty=format:%s' $merged)
-ts=$(git log -1 '--pretty=format:%at' $merged)
-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>${version}</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/coccinelle/.gitignore b/contrib/coccinelle/.gitignore
new file mode 100644
index 0000000000..d3f29646dc
--- /dev/null
+++ b/contrib/coccinelle/.gitignore
@@ -0,0 +1 @@
+*.patch*
diff --git a/contrib/coccinelle/README b/contrib/coccinelle/README
new file mode 100644
index 0000000000..f0e80bd7f0
--- /dev/null
+++ b/contrib/coccinelle/README
@@ -0,0 +1,43 @@
+This directory provides examples of Coccinelle (http://coccinelle.lip6.fr/)
+semantic patches that might be useful to developers.
+
+There are two types of semantic patches:
+
+ * Using the semantic transformation to check for bad patterns in the code;
+ The target 'make coccicheck' is designed to check for these patterns and
+ it is expected that any resulting patch indicates a regression.
+ The patches resulting from 'make coccicheck' are small and infrequent,
+ so once they are found, they can be sent to the mailing list as per usual.
+
+ Example for introducing new patterns:
+ 67947c34ae (convert "hashcmp() != 0" to "!hasheq()", 2018-08-28)
+ b84c783882 (fsck: s/++i > 1/i++/, 2018-10-24)
+
+ Example of fixes using this approach:
+ 248f66ed8e (run-command: use strbuf_addstr() for adding a string to
+ a strbuf, 2018-03-25)
+ f919ffebed (Use MOVE_ARRAY, 2018-01-22)
+
+ These types of semantic patches are usually part of testing, c.f.
+ 0860a7641b (travis-ci: fail if Coccinelle static analysis found something
+ to transform, 2018-07-23)
+
+ * Using semantic transformations in large scale refactorings throughout
+ the code base.
+
+ When applying the semantic patch into a real patch, sending it to the
+ mailing list in the usual way, such a patch would be expected to have a
+ lot of textual and semantic conflicts as such large scale refactorings
+ change function signatures that are used widely in the code base.
+ A textual conflict would arise if surrounding code near any call of such
+ function changes. A semantic conflict arises when other patch series in
+ flight introduce calls to such functions.
+
+ So to aid these large scale refactorings, semantic patches can be used.
+ However we do not want to store them in the same place as the checks for
+ bad patterns, as then automated builds would fail.
+ That is why semantic patches 'contrib/coccinelle/*.pending.cocci'
+ are ignored for checks, and can be applied using 'make coccicheck-pending'.
+
+ This allows to expose plans of pending large scale refactorings without
+ impacting the bad pattern checks.
diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci
new file mode 100644
index 0000000000..9a4f00cb1b
--- /dev/null
+++ b/contrib/coccinelle/array.cocci
@@ -0,0 +1,98 @@
+@@
+expression dst, src, n, E;
+@@
+ memcpy(dst, src, n * sizeof(
+- E[...]
++ *(E)
+ ))
+
+@@
+type T;
+T *ptr;
+T[] arr;
+expression E, n;
+@@
+(
+ memcpy(ptr, E,
+- n * sizeof(*(ptr))
++ n * sizeof(T)
+ )
+|
+ memcpy(arr, E,
+- n * sizeof(*(arr))
++ n * sizeof(T)
+ )
+|
+ memcpy(E, ptr,
+- n * sizeof(*(ptr))
++ n * sizeof(T)
+ )
+|
+ memcpy(E, arr,
+- n * sizeof(*(arr))
++ n * sizeof(T)
+ )
+)
+
+@@
+type T;
+T *dst_ptr;
+T *src_ptr;
+T[] dst_arr;
+T[] src_arr;
+expression n;
+@@
+(
+- memcpy(dst_ptr, src_ptr, (n) * sizeof(T))
++ COPY_ARRAY(dst_ptr, src_ptr, n)
+|
+- memcpy(dst_ptr, src_arr, (n) * sizeof(T))
++ COPY_ARRAY(dst_ptr, src_arr, n)
+|
+- memcpy(dst_arr, src_ptr, (n) * sizeof(T))
++ COPY_ARRAY(dst_arr, src_ptr, n)
+|
+- memcpy(dst_arr, src_arr, (n) * sizeof(T))
++ COPY_ARRAY(dst_arr, src_arr, n)
+)
+
+@@
+type T;
+T *dst;
+T *src;
+expression n;
+@@
+(
+- memmove(dst, src, (n) * sizeof(*dst));
++ MOVE_ARRAY(dst, src, n);
+|
+- memmove(dst, src, (n) * sizeof(*src));
++ MOVE_ARRAY(dst, src, n);
+|
+- memmove(dst, src, (n) * sizeof(T));
++ MOVE_ARRAY(dst, src, n);
+)
+
+@@
+type T;
+T *ptr;
+expression n;
+@@
+- ptr = xmalloc((n) * sizeof(*ptr));
++ ALLOC_ARRAY(ptr, n);
+
+@@
+type T;
+T *ptr;
+expression n;
+@@
+- ptr = xmalloc((n) * sizeof(T));
++ ALLOC_ARRAY(ptr, n);
+
+@@
+type T;
+T *ptr;
+expression n != 1;
+@@
+- ptr = xcalloc(n, \( sizeof(*ptr) \| sizeof(T) \) )
++ CALLOC_ARRAY(ptr, n)
diff --git a/contrib/coccinelle/commit.cocci b/contrib/coccinelle/commit.cocci
new file mode 100644
index 0000000000..af6dd4c20c
--- /dev/null
+++ b/contrib/coccinelle/commit.cocci
@@ -0,0 +1,52 @@
+@@
+expression c;
+@@
+- &c->maybe_tree->object.oid
++ get_commit_tree_oid(c)
+
+@@
+expression c;
+@@
+- c->maybe_tree->object.oid.hash
++ get_commit_tree_oid(c)->hash
+
+@@
+identifier f !~ "^set_commit_tree$";
+expression c;
+expression s;
+@@
+ f(...) {<...
+- c->maybe_tree = s
++ set_commit_tree(c, s)
+ ...>}
+
+// These excluded functions must access c->maybe_tree directly.
+// Note that if c->maybe_tree is written somewhere outside of these
+// functions, then the recommended transformation will be bogus with
+// repo_get_commit_tree() on the LHS.
+@@
+identifier f !~ "^(repo_get_commit_tree|get_commit_tree_in_graph_one|load_tree_for_commit|set_commit_tree)$";
+expression c;
+@@
+ f(...) {<...
+- c->maybe_tree
++ repo_get_commit_tree(specify_the_right_repo_here, c)
+ ...>}
+
+@@
+struct commit *c;
+expression E;
+@@
+(
+- c->generation = E;
++ commit_graph_data_at(c)->generation = E;
+|
+- c->graph_pos = E;
++ commit_graph_data_at(c)->graph_pos = E;
+|
+- c->generation
++ commit_graph_generation(c)
+|
+- c->graph_pos
++ commit_graph_position(c)
+)
diff --git a/contrib/coccinelle/flex_alloc.cocci b/contrib/coccinelle/flex_alloc.cocci
new file mode 100644
index 0000000000..e9f7f6d861
--- /dev/null
+++ b/contrib/coccinelle/flex_alloc.cocci
@@ -0,0 +1,13 @@
+@@
+expression str;
+identifier x, flexname;
+@@
+- FLEX_ALLOC_MEM(x, flexname, str, strlen(str));
++ FLEX_ALLOC_STR(x, flexname, str);
+
+@@
+expression str;
+identifier x, ptrname;
+@@
+- FLEXPTR_ALLOC_MEM(x, ptrname, str, strlen(str));
++ FLEXPTR_ALLOC_STR(x, ptrname, str);
diff --git a/contrib/coccinelle/free.cocci b/contrib/coccinelle/free.cocci
new file mode 100644
index 0000000000..4490069df9
--- /dev/null
+++ b/contrib/coccinelle/free.cocci
@@ -0,0 +1,18 @@
+@@
+expression E;
+@@
+- if (E)
+ free(E);
+
+@@
+expression E;
+@@
+- if (!E)
+ free(E);
+
+@@
+expression E;
+@@
+- free(E);
++ FREE_AND_NULL(E);
+- E = NULL;
diff --git a/contrib/coccinelle/hashmap.cocci b/contrib/coccinelle/hashmap.cocci
new file mode 100644
index 0000000000..d69e120ccf
--- /dev/null
+++ b/contrib/coccinelle/hashmap.cocci
@@ -0,0 +1,16 @@
+@ hashmap_entry_init_usage @
+expression E;
+struct hashmap_entry HME;
+@@
+- HME.hash = E;
++ hashmap_entry_init(&HME, E);
+
+@@
+identifier f !~ "^hashmap_entry_init$";
+expression E;
+struct hashmap_entry *HMEP;
+@@
+ f(...) {<...
+- HMEP->hash = E;
++ hashmap_entry_init(HMEP, E);
+ ...>}
diff --git a/contrib/coccinelle/object_id.cocci b/contrib/coccinelle/object_id.cocci
new file mode 100644
index 0000000000..ddf4f22bd7
--- /dev/null
+++ b/contrib/coccinelle/object_id.cocci
@@ -0,0 +1,87 @@
+@@
+struct object_id OID;
+@@
+- is_null_sha1(OID.hash)
++ is_null_oid(&OID)
+
+@@
+struct object_id *OIDPTR;
+@@
+- is_null_sha1(OIDPTR->hash)
++ is_null_oid(OIDPTR)
+
+@@
+struct object_id OID;
+@@
+- hashclr(OID.hash)
++ oidclr(&OID)
+
+@@
+identifier f != oidclr;
+struct object_id *OIDPTR;
+@@
+ f(...) {<...
+- hashclr(OIDPTR->hash)
++ oidclr(OIDPTR)
+ ...>}
+
+@@
+struct object_id OID1, OID2;
+@@
+- hashcmp(OID1.hash, OID2.hash)
++ oidcmp(&OID1, &OID2)
+
+@@
+identifier f != oidcmp;
+struct object_id *OIDPTR1, OIDPTR2;
+@@
+ f(...) {<...
+- hashcmp(OIDPTR1->hash, OIDPTR2->hash)
++ oidcmp(OIDPTR1, OIDPTR2)
+ ...>}
+
+@@
+struct object_id *OIDPTR;
+struct object_id OID;
+@@
+- hashcmp(OIDPTR->hash, OID.hash)
++ oidcmp(OIDPTR, &OID)
+
+@@
+struct object_id *OIDPTR;
+struct object_id OID;
+@@
+- hashcmp(OID.hash, OIDPTR->hash)
++ oidcmp(&OID, OIDPTR)
+
+@@
+struct object_id *OIDPTR1;
+struct object_id *OIDPTR2;
+@@
+- oidcmp(OIDPTR1, OIDPTR2) == 0
++ oideq(OIDPTR1, OIDPTR2)
+
+@@
+identifier f != hasheq;
+expression E1, E2;
+@@
+ f(...) {<...
+- hashcmp(E1, E2) == 0
++ hasheq(E1, E2)
+ ...>}
+
+@@
+struct object_id *OIDPTR1;
+struct object_id *OIDPTR2;
+@@
+- oidcmp(OIDPTR1, OIDPTR2) != 0
++ !oideq(OIDPTR1, OIDPTR2)
+
+@@
+identifier f != hasheq;
+expression E1, E2;
+@@
+ f(...) {<...
+- hashcmp(E1, E2) != 0
++ !hasheq(E1, E2)
+ ...>}
diff --git a/contrib/coccinelle/preincr.cocci b/contrib/coccinelle/preincr.cocci
new file mode 100644
index 0000000000..7fe1e8d2d9
--- /dev/null
+++ b/contrib/coccinelle/preincr.cocci
@@ -0,0 +1,5 @@
+@ preincrement @
+identifier i;
+@@
+- ++i > 1
++ i++
diff --git a/contrib/coccinelle/qsort.cocci b/contrib/coccinelle/qsort.cocci
new file mode 100644
index 0000000000..22b93a9966
--- /dev/null
+++ b/contrib/coccinelle/qsort.cocci
@@ -0,0 +1,37 @@
+@@
+expression base, nmemb, compar;
+@@
+- qsort(base, nmemb, sizeof(*base), compar);
++ QSORT(base, nmemb, compar);
+
+@@
+expression base, nmemb, compar;
+@@
+- qsort(base, nmemb, sizeof(base[0]), compar);
++ QSORT(base, nmemb, compar);
+
+@@
+type T;
+T *base;
+expression nmemb, compar;
+@@
+- qsort(base, nmemb, sizeof(T), compar);
++ QSORT(base, nmemb, compar);
+
+@@
+expression base, nmemb, compar;
+@@
+- if (nmemb)
+ QSORT(base, nmemb, compar);
+
+@@
+expression base, nmemb, compar;
+@@
+- if (nmemb > 0)
+ QSORT(base, nmemb, compar);
+
+@@
+expression base, nmemb, compar;
+@@
+- if (nmemb > 1)
+ QSORT(base, nmemb, compar);
diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci
new file mode 100644
index 0000000000..d9ada69b43
--- /dev/null
+++ b/contrib/coccinelle/strbuf.cocci
@@ -0,0 +1,62 @@
+@ strbuf_addf_with_format_only @
+expression E;
+constant fmt !~ "%";
+@@
+- strbuf_addf
++ strbuf_addstr
+ (E,
+(
+ fmt
+|
+ _(fmt)
+)
+ );
+
+@@
+expression E;
+struct strbuf SB;
+format F =~ "s";
+@@
+- strbuf_addf(E, "%@F@", SB.buf);
++ strbuf_addbuf(E, &SB);
+
+@@
+expression E;
+struct strbuf *SBP;
+format F =~ "s";
+@@
+- strbuf_addf(E, "%@F@", SBP->buf);
++ strbuf_addbuf(E, SBP);
+
+@@
+expression E;
+struct strbuf SB;
+@@
+- strbuf_addstr(E, SB.buf);
++ strbuf_addbuf(E, &SB);
+
+@@
+expression E;
+struct strbuf *SBP;
+@@
+- strbuf_addstr(E, SBP->buf);
++ strbuf_addbuf(E, SBP);
+
+@@
+expression E1, E2;
+format F =~ "s";
+@@
+- strbuf_addf(E1, "%@F@", E2);
++ strbuf_addstr(E1, E2);
+
+@@
+expression E1, E2, E3;
+@@
+- strbuf_addstr(E1, find_unique_abbrev(E2, E3));
++ strbuf_add_unique_abbrev(E1, E2, E3);
+
+@@
+expression E1, E2;
+@@
+- strbuf_addstr(E1, real_path(E2));
++ strbuf_add_real_path(E1, E2);
diff --git a/contrib/coccinelle/swap.cocci b/contrib/coccinelle/swap.cocci
new file mode 100644
index 0000000000..a0934d1fda
--- /dev/null
+++ b/contrib/coccinelle/swap.cocci
@@ -0,0 +1,28 @@
+@ swap_with_declaration @
+type T;
+identifier tmp;
+T a, b;
+@@
+- T tmp = a;
++ T tmp;
++ tmp = a;
+ a = b;
+ b = tmp;
+
+@ swap @
+type T;
+T tmp, a, b;
+@@
+- tmp = a;
+- a = b;
+- b = tmp;
++ SWAP(a, b);
+
+@ extends swap @
+identifier unused;
+@@
+ {
+ ...
+- T unused;
+ ... when != unused
+ }
diff --git a/contrib/coccinelle/the_repository.pending.cocci b/contrib/coccinelle/the_repository.pending.cocci
new file mode 100644
index 0000000000..2ee702ecf7
--- /dev/null
+++ b/contrib/coccinelle/the_repository.pending.cocci
@@ -0,0 +1,144 @@
+// This file is used for the ongoing refactoring of
+// bringing the index or repository struct in all of
+// our code base.
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+- read_object_file(
++ repo_read_object_file(the_repository,
+ E, F, G)
+
+@@
+expression E;
+@@
+- has_sha1_file(
++ repo_has_sha1_file(the_repository,
+ E)
+
+@@
+expression E;
+expression F;
+@@
+- has_sha1_file_with_flags(
++ repo_has_sha1_file_with_flags(the_repository,
+ E)
+
+@@
+expression E;
+@@
+- has_object_file(
++ repo_has_object_file(the_repository,
+ E)
+
+@@
+expression E;
+expression F;
+@@
+- has_object_file_with_flags(
++ repo_has_object_file_with_flags(the_repository,
+ E)
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+- parse_commit_internal(
++ repo_parse_commit_internal(the_repository,
+ E, F, G)
+
+@@
+expression E;
+expression F;
+@@
+- parse_commit_gently(
++ repo_parse_commit_gently(the_repository,
+ E, F)
+
+@@
+expression E;
+@@
+- parse_commit(
++ repo_parse_commit(the_repository,
+ E)
+
+@@
+expression E;
+expression F;
+@@
+- get_merge_bases(
++ repo_get_merge_bases(the_repository,
+ E, F);
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+- get_merge_bases_many(
++ repo_get_merge_bases_many(the_repository,
+ E, F, G);
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+- get_merge_bases_many_dirty(
++ repo_get_merge_bases_many_dirty(the_repository,
+ E, F, G);
+
+@@
+expression E;
+expression F;
+@@
+- in_merge_bases(
++ repo_in_merge_bases(the_repository,
+ E, F);
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+- in_merge_bases_many(
++ repo_in_merge_bases_many(the_repository,
+ E, F, G);
+
+@@
+expression E;
+expression F;
+@@
+- get_commit_buffer(
++ repo_get_commit_buffer(the_repository,
+ E, F);
+
+@@
+expression E;
+expression F;
+@@
+- unuse_commit_buffer(
++ repo_unuse_commit_buffer(the_repository,
+ E, F);
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+- logmsg_reencode(
++ repo_logmsg_reencode(the_repository,
+ E, F, G);
+
+@@
+expression E;
+expression F;
+expression G;
+expression H;
+@@
+- format_commit_message(
++ repo_format_commit_message(the_repository,
+ E, F, G, H);
diff --git a/contrib/coccinelle/xcalloc.cocci b/contrib/coccinelle/xcalloc.cocci
new file mode 100644
index 0000000000..c291011607
--- /dev/null
+++ b/contrib/coccinelle/xcalloc.cocci
@@ -0,0 +1,10 @@
+@@
+type T;
+T *ptr;
+expression n;
+@@
+ xcalloc(
++ n,
+ \( sizeof(T) \| sizeof(*ptr) \)
+- , n
+ )
diff --git a/contrib/coccinelle/xopen.cocci b/contrib/coccinelle/xopen.cocci
new file mode 100644
index 0000000000..b71db67019
--- /dev/null
+++ b/contrib/coccinelle/xopen.cocci
@@ -0,0 +1,19 @@
+@@
+identifier fd;
+identifier die_fn =~ "^(die|die_errno)$";
+@@
+ int fd =
+- open
++ xopen
+ (...);
+- if ( \( fd < 0 \| fd == -1 \) ) { die_fn(...); }
+
+@@
+expression fd;
+identifier die_fn =~ "^(die|die_errno)$";
+@@
+ fd =
+- open
++ xopen
+ (...);
+- if ( \( fd < 0 \| fd == -1 \) ) { die_fn(...); }
diff --git a/contrib/coccinelle/xstrdup_or_null.cocci b/contrib/coccinelle/xstrdup_or_null.cocci
new file mode 100644
index 0000000000..8e05d1ca4b
--- /dev/null
+++ b/contrib/coccinelle/xstrdup_or_null.cocci
@@ -0,0 +1,13 @@
+@@
+expression E;
+expression V;
+@@
+- if (E)
+- V = xstrdup(E);
++ V = xstrdup_or_null(E);
+
+@@
+expression E;
+@@
+- xstrdup(absolute_path(E))
++ absolute_pathdup(E)
diff --git a/contrib/completion/.gitattributes b/contrib/completion/.gitattributes
new file mode 100644
index 0000000000..19116944c1
--- /dev/null
+++ b/contrib/completion/.gitattributes
@@ -0,0 +1 @@
+*.bash eol=lf
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 2186b4b77e..7c3a75373a 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1,5 +1,3 @@
-#!bash
-#
# bash/zsh completion support for core Git.
#
# Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org>
@@ -12,39 +10,90 @@
# *) local and remote tag names
# *) .git/remotes file names
# *) git 'subcommands'
+# *) git email aliases for git-send-email
# *) tree paths within 'ref:path/to/file' expressions
+# *) file paths within current working directory and index
# *) common --long-options
#
# To use these routines:
#
-# 1) Copy this file to somewhere (e.g. ~/.git-completion.sh).
+# 1) Copy this file to somewhere (e.g. ~/.git-completion.bash).
# 2) Add the following line to your .bashrc/.zshrc:
-# source ~/.git-completion.sh
+# source ~/.git-completion.bash
# 3) Consider changing your PS1 to also show the current branch,
# see git-prompt.sh for details.
+#
+# If you use complex aliases of form '!f() { ... }; f', you can use the null
+# command ':' as the first command in the function body to declare the desired
+# completion style. For example '!f() { : git commit ; ... }; f' will
+# tell the completion to use commit completion. This also works with aliases
+# of form "!sh -c '...'". For example, "!sh -c ': git commit ; ... '".
+#
+# If you have a command that is not part of git, but you would still
+# like completion, you can use __git_complete:
+#
+# __git_complete gl git_log
+#
+# Or if it's a main command (i.e. git or gitk):
+#
+# __git_complete gk gitk
+#
+# Compatible with bash 3.2.57.
+#
+# You can set the following environment variables to influence the behavior of
+# the completion routines:
+#
+# GIT_COMPLETION_CHECKOUT_NO_GUESS
+#
+# When set to "1", do not include "DWIM" suggestions in git-checkout
+# and git-switch completion (e.g., completing "foo" when "origin/foo"
+# exists).
+#
+# GIT_COMPLETION_SHOW_ALL
+#
+# When set to "1" suggest all options, including options which are
+# typically hidden (e.g. '--allow-empty' for 'git commit').
case "$COMP_WORDBREAKS" in
*:*) : great ;;
*) COMP_WORDBREAKS="$COMP_WORDBREAKS:"
esac
+# Discovers the path to the git repository taking any '--git-dir=<path>' and
+# '-C <path>' options into account and stores it in the $__git_repo_path
+# variable.
+__git_find_repo_path ()
+{
+ if [ -n "${__git_repo_path-}" ]; then
+ # we already know where it is
+ return
+ fi
+
+ if [ -n "${__git_C_args-}" ]; then
+ __git_repo_path="$(git "${__git_C_args[@]}" \
+ ${__git_dir:+--git-dir="$__git_dir"} \
+ rev-parse --absolute-git-dir 2>/dev/null)"
+ elif [ -n "${__git_dir-}" ]; then
+ test -d "$__git_dir" &&
+ __git_repo_path="$__git_dir"
+ elif [ -n "${GIT_DIR-}" ]; then
+ test -d "$GIT_DIR" &&
+ __git_repo_path="$GIT_DIR"
+ elif [ -d .git ]; then
+ __git_repo_path=.git
+ else
+ __git_repo_path="$(git rev-parse --git-dir 2>/dev/null)"
+ fi
+}
+
+# Deprecated: use __git_find_repo_path() and $__git_repo_path instead
# __gitdir accepts 0 or 1 arguments (i.e., location)
# returns location of .git repo
__gitdir ()
{
- # Note: this function is duplicated in git-prompt.sh
- # When updating it, make sure you update the other one to match.
if [ -z "${1-}" ]; then
- if [ -n "${__git_dir-}" ]; then
- echo "$__git_dir"
- elif [ -n "${GIT_DIR-}" ]; then
- test -d "${GIT_DIR-}" || return 1
- echo "$GIT_DIR"
- elif [ -d .git ]; then
- echo .git
- else
- git rev-parse --git-dir 2>/dev/null
- fi
+ __git_find_repo_path || return 1
+ echo "$__git_repo_path"
elif [ -d "$1/.git" ]; then
echo "$1/.git"
else
@@ -52,16 +101,75 @@ __gitdir ()
fi
}
-__gitcomp_1 ()
+# Runs git with all the options given as argument, respecting any
+# '--git-dir=<path>' and '-C <path>' options present on the command line
+__git ()
{
- local c IFS=$' \t\n'
- for c in $1; do
- c="$c$2"
- case $c in
- --*=*|*.) ;;
- *) c="$c " ;;
+ git ${__git_C_args:+"${__git_C_args[@]}"} \
+ ${__git_dir:+--git-dir="$__git_dir"} "$@" 2>/dev/null
+}
+
+# Removes backslash escaping, single quotes and double quotes from a word,
+# stores the result in the variable $dequoted_word.
+# 1: The word to dequote.
+__git_dequote ()
+{
+ local rest="$1" len ch
+
+ dequoted_word=""
+
+ while test -n "$rest"; do
+ len=${#dequoted_word}
+ dequoted_word="$dequoted_word${rest%%[\\\'\"]*}"
+ rest="${rest:$((${#dequoted_word}-$len))}"
+
+ case "${rest:0:1}" in
+ \\)
+ ch="${rest:1:1}"
+ case "$ch" in
+ $'\n')
+ ;;
+ *)
+ dequoted_word="$dequoted_word$ch"
+ ;;
+ esac
+ rest="${rest:2}"
+ ;;
+ \')
+ rest="${rest:1}"
+ len=${#dequoted_word}
+ dequoted_word="$dequoted_word${rest%%\'*}"
+ rest="${rest:$((${#dequoted_word}-$len+1))}"
+ ;;
+ \")
+ rest="${rest:1}"
+ while test -n "$rest" ; do
+ len=${#dequoted_word}
+ dequoted_word="$dequoted_word${rest%%[\\\"]*}"
+ rest="${rest:$((${#dequoted_word}-$len))}"
+ case "${rest:0:1}" in
+ \\)
+ ch="${rest:1:1}"
+ case "$ch" in
+ \"|\\|\$|\`)
+ dequoted_word="$dequoted_word$ch"
+ ;;
+ $'\n')
+ ;;
+ *)
+ dequoted_word="$dequoted_word\\$ch"
+ ;;
+ esac
+ rest="${rest:2}"
+ ;;
+ \")
+ rest="${rest:1}"
+ break
+ ;;
+ esac
+ done
+ ;;
esac
- printf '%s\n' "$c"
done
}
@@ -84,8 +192,7 @@ __gitcomp_1 ()
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
#
# The latest version of this software can be obtained here:
#
@@ -194,8 +301,51 @@ _get_comp_words_by_ref ()
}
fi
-# Generates completion reply with compgen, appending a space to possible
-# completion words, if necessary.
+# Fills the COMPREPLY array with prefiltered words without any additional
+# processing.
+# Callers must take care of providing only words that match the current word
+# to be completed and adding any prefix and/or suffix (trailing space!), if
+# necessary.
+# 1: List of newline-separated matching completion words, complete with
+# prefix and suffix.
+__gitcomp_direct ()
+{
+ local IFS=$'\n'
+
+ COMPREPLY=($1)
+}
+
+# Similar to __gitcomp_direct, but appends to COMPREPLY instead.
+# Callers must take care of providing only words that match the current word
+# to be completed and adding any prefix and/or suffix (trailing space!), if
+# necessary.
+# 1: List of newline-separated matching completion words, complete with
+# prefix and suffix.
+__gitcomp_direct_append ()
+{
+ local IFS=$'\n'
+
+ COMPREPLY+=($1)
+}
+
+__gitcompappend ()
+{
+ local x i=${#COMPREPLY[@]}
+ for x in $1; do
+ if [[ "$x" == "$3"* ]]; then
+ COMPREPLY[i++]="$2$x$4"
+ fi
+ done
+}
+
+__gitcompadd ()
+{
+ COMPREPLY=()
+ __gitcompappend "$@"
+}
+
+# Generates completion reply, appending a space to possible completion words,
+# if necessary.
# It accepts 1 to 4 arguments:
# 1: List of possible completion words.
# 2: A prefix to be added to each possible completion word (optional).
@@ -206,20 +356,105 @@ __gitcomp ()
local cur_="${3-$cur}"
case "$cur_" in
- --*=)
- COMPREPLY=()
+ *=)
+ ;;
+ --no-*)
+ local c i=0 IFS=$' \t\n'
+ for c in $1; do
+ if [[ $c == "--" ]]; then
+ continue
+ fi
+ c="$c${4-}"
+ if [[ $c == "$cur_"* ]]; then
+ case $c in
+ --*=|*.) ;;
+ *) c="$c " ;;
+ esac
+ COMPREPLY[i++]="${2-}$c"
+ fi
+ done
;;
*)
- local IFS=$'\n'
- COMPREPLY=($(compgen -P "${2-}" \
- -W "$(__gitcomp_1 "${1-}" "${4-}")" \
- -- "$cur_"))
+ local c i=0 IFS=$' \t\n'
+ for c in $1; do
+ if [[ $c == "--" ]]; then
+ c="--no-...${4-}"
+ if [[ $c == "$cur_"* ]]; then
+ COMPREPLY[i++]="${2-}$c "
+ fi
+ break
+ fi
+ c="$c${4-}"
+ if [[ $c == "$cur_"* ]]; then
+ case $c in
+ *=|*.) ;;
+ *) c="$c " ;;
+ esac
+ COMPREPLY[i++]="${2-}$c"
+ fi
+ done
;;
esac
}
-# Generates completion reply with compgen from newline-separated possible
-# completion words by appending a space to all of them.
+# Clear the variables caching builtins' options when (re-)sourcing
+# the completion script.
+if [[ -n ${ZSH_VERSION-} ]]; then
+ unset ${(M)${(k)parameters[@]}:#__gitcomp_builtin_*} 2>/dev/null
+else
+ unset $(compgen -v __gitcomp_builtin_)
+fi
+
+# This function is equivalent to
+#
+# __gitcomp "$(git xxx --git-completion-helper) ..."
+#
+# except that the output is cached. Accept 1-3 arguments:
+# 1: the git command to execute, this is also the cache key
+# 2: extra options to be added on top (e.g. negative forms)
+# 3: options to be excluded
+__gitcomp_builtin ()
+{
+ # spaces must be replaced with underscore for multi-word
+ # commands, e.g. "git remote add" becomes remote_add.
+ local cmd="$1"
+ local incl="${2-}"
+ local excl="${3-}"
+
+ local var=__gitcomp_builtin_"${cmd//-/_}"
+ local options
+ eval "options=\${$var-}"
+
+ if [ -z "$options" ]; then
+ local completion_helper
+ if [ "${GIT_COMPLETION_SHOW_ALL-}" = "1" ]; then
+ completion_helper="--git-completion-helper-all"
+ else
+ completion_helper="--git-completion-helper"
+ fi
+ # leading and trailing spaces are significant to make
+ # option removal work correctly.
+ options=" $incl $(__git ${cmd/_/ } $completion_helper) " || return
+
+ for i in $excl; do
+ options="${options/ $i / }"
+ done
+ eval "$var=\"$options\""
+ fi
+
+ __gitcomp "$options"
+}
+
+# Variation of __gitcomp_nl () that appends to the existing list of
+# completion candidates, COMPREPLY.
+__gitcomp_nl_append ()
+{
+ local IFS=$'\n'
+ __gitcompappend "$1" "${2-}" "${3-$cur}" "${4- }"
+}
+
+# Generates completion reply from newline-separated possible completion words
+# by appending a space to all of them.
# It accepts 1 to 4 arguments:
# 1: List of possible completion words, separated by a single newline.
# 2: A prefix to be added to each possible completion word (optional).
@@ -229,95 +464,400 @@ __gitcomp ()
# appended.
__gitcomp_nl ()
{
+ COMPREPLY=()
+ __gitcomp_nl_append "$@"
+}
+
+# Fills the COMPREPLY array with prefiltered paths without any additional
+# processing.
+# Callers must take care of providing only paths that match the current path
+# to be completed and adding any prefix path components, if necessary.
+# 1: List of newline-separated matching paths, complete with all prefix
+# path components.
+__gitcomp_file_direct ()
+{
local IFS=$'\n'
- COMPREPLY=($(compgen -P "${2-}" -S "${4- }" -W "$1" -- "${3-$cur}"))
+
+ COMPREPLY=($1)
+
+ # use a hack to enable file mode in bash < 4
+ compopt -o filenames +o nospace 2>/dev/null ||
+ compgen -f /non-existing-dir/ >/dev/null ||
+ true
}
-__git_heads ()
+# Generates completion reply with compgen from newline-separated possible
+# completion filenames.
+# It accepts 1 to 3 arguments:
+# 1: List of possible completion filenames, separated by a single newline.
+# 2: A directory prefix to be added to each possible completion filename
+# (optional).
+# 3: Generate possible completion matches for this word (optional).
+__gitcomp_file ()
{
- local dir="$(__gitdir)"
- if [ -d "$dir" ]; then
- git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
- refs/heads
- return
- fi
+ local IFS=$'\n'
+
+ # XXX does not work when the directory prefix contains a tilde,
+ # since tilde expansion is not applied.
+ # This means that COMPREPLY will be empty and Bash default
+ # completion will be used.
+ __gitcompadd "$1" "${2-}" "${3-$cur}" ""
+
+ # use a hack to enable file mode in bash < 4
+ compopt -o filenames +o nospace 2>/dev/null ||
+ compgen -f /non-existing-dir/ >/dev/null ||
+ true
}
-__git_tags ()
+# Execute 'git ls-files', unless the --committable option is specified, in
+# which case it runs 'git diff-index' to find out the files that can be
+# committed. It return paths relative to the directory specified in the first
+# argument, and using the options specified in the second argument.
+__git_ls_files_helper ()
{
- local dir="$(__gitdir)"
- if [ -d "$dir" ]; then
- git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
- refs/tags
- return
+ if [ "$2" = "--committable" ]; then
+ __git -C "$1" -c core.quotePath=false diff-index \
+ --name-only --relative HEAD -- "${3//\\/\\\\}*"
+ else
+ # NOTE: $2 is not quoted in order to support multiple options
+ __git -C "$1" -c core.quotePath=false ls-files \
+ --exclude-standard $2 -- "${3//\\/\\\\}*"
fi
}
-# __git_refs accepts 0, 1 (to pass to __gitdir), or 2 arguments
-# presence of 2nd argument means use the guess heuristic employed
-# by checkout for tracking branches
+
+# __git_index_files accepts 1 or 2 arguments:
+# 1: Options to pass to ls-files (required).
+# 2: A directory path (optional).
+# If provided, only files within the specified directory are listed.
+# Sub directories are never recursed. Path must have a trailing
+# slash.
+# 3: List only paths matching this path component (optional).
+__git_index_files ()
+{
+ local root="$2" match="$3"
+
+ __git_ls_files_helper "$root" "$1" "${match:-?}" |
+ awk -F / -v pfx="${2//\\/\\\\}" '{
+ paths[$1] = 1
+ }
+ END {
+ for (p in paths) {
+ if (substr(p, 1, 1) != "\"") {
+ # No special characters, easy!
+ print pfx p
+ continue
+ }
+
+ # The path is quoted.
+ p = dequote(p)
+ if (p == "")
+ continue
+
+ # Even when a directory name itself does not contain
+ # any special characters, it will still be quoted if
+ # any of its (stripped) trailing path components do.
+ # Because of this we may have seen the same directory
+ # both quoted and unquoted.
+ if (p in paths)
+ # We have seen the same directory unquoted,
+ # skip it.
+ continue
+ else
+ print pfx p
+ }
+ }
+ function dequote(p, bs_idx, out, esc, esc_idx, dec) {
+ # Skip opening double quote.
+ p = substr(p, 2)
+
+ # Interpret backslash escape sequences.
+ while ((bs_idx = index(p, "\\")) != 0) {
+ out = out substr(p, 1, bs_idx - 1)
+ esc = substr(p, bs_idx + 1, 1)
+ p = substr(p, bs_idx + 2)
+
+ if ((esc_idx = index("abtvfr\"\\", esc)) != 0) {
+ # C-style one-character escape sequence.
+ out = out substr("\a\b\t\v\f\r\"\\",
+ esc_idx, 1)
+ } else if (esc == "n") {
+ # Uh-oh, a newline character.
+ # We cannot reliably put a pathname
+ # containing a newline into COMPREPLY,
+ # and the newline would create a mess.
+ # Skip this path.
+ return ""
+ } else {
+ # Must be a \nnn octal value, then.
+ dec = esc * 64 + \
+ substr(p, 1, 1) * 8 + \
+ substr(p, 2, 1)
+ out = out sprintf("%c", dec)
+ p = substr(p, 3)
+ }
+ }
+ # Drop closing double quote, if there is one.
+ # (There is not any if this is a directory, as it was
+ # already stripped with the trailing path components.)
+ if (substr(p, length(p), 1) == "\"")
+ out = out substr(p, 1, length(p) - 1)
+ else
+ out = out p
+
+ return out
+ }'
+}
+
+# __git_complete_index_file requires 1 argument:
+# 1: the options to pass to ls-file
+#
+# The exception is --committable, which finds the files appropriate commit.
+__git_complete_index_file ()
+{
+ local dequoted_word pfx="" cur_
+
+ __git_dequote "$cur"
+
+ case "$dequoted_word" in
+ ?*/*)
+ pfx="${dequoted_word%/*}/"
+ cur_="${dequoted_word##*/}"
+ ;;
+ *)
+ cur_="$dequoted_word"
+ esac
+
+ __gitcomp_file_direct "$(__git_index_files "$1" "$pfx" "$cur_")"
+}
+
+# Lists branches from the local repository.
+# 1: A prefix to be added to each listed branch (optional).
+# 2: List only branches matching this word (optional; list all branches if
+# unset or empty).
+# 3: A suffix to be appended to each listed branch (optional).
+__git_heads ()
+{
+ local pfx="${1-}" cur_="${2-}" sfx="${3-}"
+
+ __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \
+ "refs/heads/$cur_*" "refs/heads/$cur_*/**"
+}
+
+# Lists branches from remote repositories.
+# 1: A prefix to be added to each listed branch (optional).
+# 2: List only branches matching this word (optional; list all branches if
+# unset or empty).
+# 3: A suffix to be appended to each listed branch (optional).
+__git_remote_heads ()
+{
+ local pfx="${1-}" cur_="${2-}" sfx="${3-}"
+
+ __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \
+ "refs/remotes/$cur_*" "refs/remotes/$cur_*/**"
+}
+
+# Lists tags from the local repository.
+# Accepts the same positional parameters as __git_heads() above.
+__git_tags ()
+{
+ local pfx="${1-}" cur_="${2-}" sfx="${3-}"
+
+ __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \
+ "refs/tags/$cur_*" "refs/tags/$cur_*/**"
+}
+
+# List unique branches from refs/remotes used for 'git checkout' and 'git
+# switch' tracking DWIMery.
+# 1: A prefix to be added to each listed branch (optional)
+# 2: List only branches matching this word (optional; list all branches if
+# unset or empty).
+# 3: A suffix to be appended to each listed branch (optional).
+__git_dwim_remote_heads ()
+{
+ local pfx="${1-}" cur_="${2-}" sfx="${3-}"
+ local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers
+
+ # employ the heuristic used by git checkout and git switch
+ # Try to find a remote branch that cur_es the completion word
+ # but only output if the branch name is unique
+ __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \
+ --sort="refname:strip=3" \
+ "refs/remotes/*/$cur_*" "refs/remotes/*/$cur_*/**" | \
+ uniq -u
+}
+
+# Lists refs from the local (by default) or from a remote repository.
+# It accepts 0, 1 or 2 arguments:
+# 1: The remote to list refs from (optional; ignored, if set but empty).
+# Can be the name of a configured remote, a path, or a URL.
+# 2: In addition to local refs, list unique branches from refs/remotes/ for
+# 'git checkout's tracking DWIMery (optional; ignored, if set but empty).
+# 3: A prefix to be added to each listed ref (optional).
+# 4: List only refs matching this word (optional; list all refs if unset or
+# empty).
+# 5: A suffix to be appended to each listed ref (optional; ignored, if set
+# but empty).
+#
+# Use __git_complete_refs() instead.
__git_refs ()
{
- local i hash dir="$(__gitdir "${1-}")" track="${2-}"
+ local i hash dir track="${2-}"
+ local list_refs_from=path remote="${1-}"
local format refs
- if [ -d "$dir" ]; then
- case "$cur" in
+ local pfx="${3-}" cur_="${4-$cur}" sfx="${5-}"
+ local match="${4-}"
+ local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers
+
+ __git_find_repo_path
+ dir="$__git_repo_path"
+
+ if [ -z "$remote" ]; then
+ if [ -z "$dir" ]; then
+ return
+ fi
+ else
+ if __git_is_configured_remote "$remote"; then
+ # configured remote takes precedence over a
+ # local directory with the same name
+ list_refs_from=remote
+ elif [ -d "$remote/.git" ]; then
+ dir="$remote/.git"
+ elif [ -d "$remote" ]; then
+ dir="$remote"
+ else
+ list_refs_from=url
+ fi
+ fi
+
+ if [ "$list_refs_from" = path ]; then
+ if [[ "$cur_" == ^* ]]; then
+ pfx="$pfx^"
+ fer_pfx="$fer_pfx^"
+ cur_=${cur_#^}
+ match=${match#^}
+ fi
+ case "$cur_" in
refs|refs/*)
format="refname"
- refs="${cur%/*}"
+ refs=("$match*" "$match*/**")
track=""
;;
*)
- for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do
- if [ -e "$dir/$i" ]; then echo $i; fi
+ for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD CHERRY_PICK_HEAD; do
+ case "$i" in
+ $match*)
+ if [ -e "$dir/$i" ]; then
+ echo "$pfx$i$sfx"
+ fi
+ ;;
+ esac
done
- format="refname:short"
- refs="refs/tags refs/heads refs/remotes"
+ format="refname:strip=2"
+ refs=("refs/tags/$match*" "refs/tags/$match*/**"
+ "refs/heads/$match*" "refs/heads/$match*/**"
+ "refs/remotes/$match*" "refs/remotes/$match*/**")
;;
esac
- git --git-dir="$dir" for-each-ref --format="%($format)" \
- $refs
+ __git_dir="$dir" __git for-each-ref --format="$fer_pfx%($format)$sfx" \
+ "${refs[@]}"
if [ -n "$track" ]; then
- # employ the heuristic used by git checkout
- # Try to find a remote branch that matches the completion word
- # but only output if the branch name is unique
- local ref entry
- git --git-dir="$dir" for-each-ref --shell --format="ref=%(refname:short)" \
- "refs/remotes/" | \
- while read -r entry; do
- eval "$entry"
- ref="${ref#*/}"
- if [[ "$ref" == "$cur"* ]]; then
- echo "$ref"
- fi
- done | sort | uniq -u
+ __git_dwim_remote_heads "$pfx" "$match" "$sfx"
fi
return
fi
- case "$cur" in
+ case "$cur_" in
refs|refs/*)
- git ls-remote "$dir" "$cur*" 2>/dev/null | \
+ __git ls-remote "$remote" "$match*" | \
while read -r hash i; do
case "$i" in
*^{}) ;;
- *) echo "$i" ;;
+ *) echo "$pfx$i$sfx" ;;
esac
done
;;
*)
- git ls-remote "$dir" HEAD ORIG_HEAD 'refs/tags/*' 'refs/heads/*' 'refs/remotes/*' 2>/dev/null | \
- while read -r hash i; do
- case "$i" in
- *^{}) ;;
- refs/*) echo "${i#refs/*/}" ;;
- *) echo "$i" ;;
+ if [ "$list_refs_from" = remote ]; then
+ case "HEAD" in
+ $match*) echo "${pfx}HEAD$sfx" ;;
esac
- done
+ __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \
+ "refs/remotes/$remote/$match*" \
+ "refs/remotes/$remote/$match*/**"
+ else
+ local query_symref
+ case "HEAD" in
+ $match*) query_symref="HEAD" ;;
+ esac
+ __git ls-remote "$remote" $query_symref \
+ "refs/tags/$match*" "refs/heads/$match*" \
+ "refs/remotes/$match*" |
+ while read -r hash i; do
+ case "$i" in
+ *^{}) ;;
+ refs/*) echo "$pfx${i#refs/*/}$sfx" ;;
+ *) echo "$pfx$i$sfx" ;; # symbolic refs
+ esac
+ done
+ fi
;;
esac
}
+# Completes refs, short and long, local and remote, symbolic and pseudo.
+#
+# Usage: __git_complete_refs [<option>]...
+# --remote=<remote>: The remote to list refs from, can be the name of a
+# configured remote, a path, or a URL.
+# --dwim: List unique remote branches for 'git switch's tracking DWIMery.
+# --pfx=<prefix>: A prefix to be added to each ref.
+# --cur=<word>: The current ref to be completed. Defaults to the current
+# word to be completed.
+# --sfx=<suffix>: A suffix to be appended to each ref instead of the default
+# space.
+# --mode=<mode>: What set of refs to complete, one of 'refs' (the default) to
+# complete all refs, 'heads' to complete only branches, or
+# 'remote-heads' to complete only remote branches. Note that
+# --remote is only compatible with --mode=refs.
+__git_complete_refs ()
+{
+ local remote= dwim= pfx= cur_="$cur" sfx=" " mode="refs"
+
+ while test $# != 0; do
+ case "$1" in
+ --remote=*) remote="${1##--remote=}" ;;
+ --dwim) dwim="yes" ;;
+ # --track is an old spelling of --dwim
+ --track) dwim="yes" ;;
+ --pfx=*) pfx="${1##--pfx=}" ;;
+ --cur=*) cur_="${1##--cur=}" ;;
+ --sfx=*) sfx="${1##--sfx=}" ;;
+ --mode=*) mode="${1##--mode=}" ;;
+ *) return 1 ;;
+ esac
+ shift
+ done
+
+ # complete references based on the specified mode
+ case "$mode" in
+ refs)
+ __gitcomp_direct "$(__git_refs "$remote" "" "$pfx" "$cur_" "$sfx")" ;;
+ heads)
+ __gitcomp_direct "$(__git_heads "$pfx" "$cur_" "$sfx")" ;;
+ remote-heads)
+ __gitcomp_direct "$(__git_remote_heads "$pfx" "$cur_" "$sfx")" ;;
+ *)
+ return 1 ;;
+ esac
+
+ # Append DWIM remote branch names if requested
+ if [ "$dwim" = "yes" ]; then
+ __gitcomp_direct_append "$(__git_dwim_remote_heads "$pfx" "$cur_" "$sfx")"
+ fi
+}
+
# __git_refs2 requires 1 argument (to pass to __git_refs)
+# Deprecated: use __git_complete_fetch_refspecs() instead.
__git_refs2 ()
{
local i
@@ -326,11 +866,29 @@ __git_refs2 ()
done
}
+# Completes refspecs for fetching from a remote repository.
+# 1: The remote repository.
+# 2: A prefix to be added to each listed refspec (optional).
+# 3: The ref to be completed as a refspec instead of the current word to be
+# completed (optional)
+# 4: A suffix to be appended to each listed refspec instead of the default
+# space (optional).
+__git_complete_fetch_refspecs ()
+{
+ local i remote="$1" pfx="${2-}" cur_="${3-$cur}" sfx="${4- }"
+
+ __gitcomp_direct "$(
+ for i in $(__git_refs "$remote" "" "" "$cur_") ; do
+ echo "$pfx$i:$i$sfx"
+ done
+ )"
+}
+
# __git_refs_remotes requires 1 argument (to pass to ls-remote)
__git_refs_remotes ()
{
local i hash
- git ls-remote "$1" 'refs/heads/*' 2>/dev/null | \
+ __git ls-remote "$1" 'refs/heads/*' | \
while read -r hash i; do
echo "$i:refs/remotes/$1/${i#refs/heads/}"
done
@@ -338,17 +896,26 @@ __git_refs_remotes ()
__git_remotes ()
{
- local i IFS=$'\n' d="$(__gitdir)"
- test -d "$d/remotes" && ls -1 "$d/remotes"
- for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do
- i="${i#remote.}"
- echo "${i/.url*/}"
+ __git_find_repo_path
+ test -d "$__git_repo_path/remotes" && ls -1 "$__git_repo_path/remotes"
+ __git remote
+}
+
+# Returns true if $1 matches the name of a configured remote, false otherwise.
+__git_is_configured_remote ()
+{
+ local remote
+ for remote in $(__git_remotes); do
+ if [ "$remote" = "$1" ]; then
+ return 0
+ fi
done
+ return 1
}
__git_list_merge_strategies ()
{
- git merge -s help 2>&1 |
+ LANG=C LC_ALL=C git merge -s help 2>&1 |
sed -n -e '/[Aa]vailable strategies are: /,/^$/{
s/\.$//
s/.*://
@@ -370,9 +937,14 @@ __git_compute_merge_strategies ()
__git_merge_strategies=$(__git_list_merge_strategies)
}
+__git_merge_strategy_options="ours theirs subtree subtree= patience
+ histogram diff-algorithm= ignore-space-change ignore-all-space
+ ignore-space-at-eol renormalize no-renormalize no-renames
+ find-renames find-renames= rename-threshold="
+
__git_complete_revlist_file ()
{
- local pfx ls ref cur_="$cur"
+ local dequoted_word pfx ls ref cur_="$cur"
case "$cur_" in
*..?*:*)
return
@@ -380,14 +952,18 @@ __git_complete_revlist_file ()
?*:*)
ref="${cur_%%:*}"
cur_="${cur_#*:}"
- case "$cur_" in
+
+ __git_dequote "$cur_"
+
+ case "$dequoted_word" in
?*/*)
- pfx="${cur_%/*}"
- cur_="${cur_##*/}"
+ pfx="${dequoted_word%/*}"
+ cur_="${dequoted_word##*/}"
ls="$ref:$pfx"
pfx="$pfx/"
;;
*)
+ cur_="$dequoted_word"
ls="$ref"
;;
esac
@@ -397,39 +973,27 @@ __git_complete_revlist_file ()
*) pfx="$ref:$pfx" ;;
esac
- __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" 2>/dev/null \
- | sed '/^100... blob /{
- s,^.* ,,
- s,$, ,
- }
- /^120000 blob /{
- s,^.* ,,
- s,$, ,
- }
- /^040000 tree /{
- s,^.* ,,
- s,$,/,
- }
- s/^.* //')" \
- "$pfx" "$cur_" ""
+ __gitcomp_file "$(__git ls-tree "$ls" \
+ | sed 's/^.* //
+ s/$//')" \
+ "$pfx" "$cur_"
;;
*...*)
pfx="${cur_%...*}..."
cur_="${cur_#*...}"
- __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
+ __git_complete_refs --pfx="$pfx" --cur="$cur_"
;;
*..*)
pfx="${cur_%..*}.."
cur_="${cur_#*..}"
- __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
+ __git_complete_refs --pfx="$pfx" --cur="$cur_"
;;
*)
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
;;
esac
}
-
__git_complete_file ()
{
__git_complete_revlist_file
@@ -442,8 +1006,8 @@ __git_complete_revlist ()
__git_complete_remote_or_refspec ()
{
- local cur_="$cur" cmd="${words[1]}"
- local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0
+ local cur_="$cur" cmd="${words[__git_cmd_idx]}"
+ local i c=$((__git_cmd_idx+1)) remote="" pfx="" lhs=1 no_complete_refspec=0
if [ "$cmd" = "remote" ]; then
((c++))
fi
@@ -451,16 +1015,17 @@ __git_complete_remote_or_refspec ()
i="${words[c]}"
case "$i" in
--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
+ -d|--delete) [ "$cmd" = "push" ] && lhs=0 ;;
--all)
case "$cmd" in
push) no_complete_refspec=1 ;;
fetch)
- COMPREPLY=()
return
;;
*) ;;
esac
;;
+ --multiple) no_complete_refspec=1; break ;;
-*) ;;
*) remote="$i"; break ;;
esac
@@ -471,7 +1036,6 @@ __git_complete_remote_or_refspec ()
return
fi
if [ $no_complete_refspec = 1 ]; then
- COMPREPLY=()
return
fi
[ "$remote" = "." ] && remote=
@@ -492,23 +1056,23 @@ __git_complete_remote_or_refspec ()
case "$cmd" in
fetch)
if [ $lhs = 1 ]; then
- __gitcomp_nl "$(__git_refs2 "$remote")" "$pfx" "$cur_"
+ __git_complete_fetch_refspecs "$remote" "$pfx" "$cur_"
else
- __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
+ __git_complete_refs --pfx="$pfx" --cur="$cur_"
fi
;;
pull|remote)
if [ $lhs = 1 ]; then
- __gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_"
+ __git_complete_refs --remote="$remote" --pfx="$pfx" --cur="$cur_"
else
- __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
+ __git_complete_refs --pfx="$pfx" --cur="$cur_"
fi
;;
push)
if [ $lhs = 1 ]; then
- __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
+ __git_complete_refs --pfx="$pfx" --cur="$cur_"
else
- __gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_"
+ __git_complete_refs --remote="$remote" --pfx="$pfx" --cur="$cur_"
fi
;;
esac
@@ -521,202 +1085,199 @@ __git_complete_strategy ()
-s|--strategy)
__gitcomp "$__git_merge_strategies"
return 0
+ ;;
+ -X)
+ __gitcomp "$__git_merge_strategy_options"
+ return 0
+ ;;
esac
case "$cur" in
--strategy=*)
__gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}"
return 0
;;
+ --strategy-option=*)
+ __gitcomp "$__git_merge_strategy_options" "" "${cur##--strategy-option=}"
+ return 0
+ ;;
esac
return 1
}
-__git_commands () {
- if test -n "${GIT_TESTING_COMMAND_COMPLETION:-}"
- then
- printf "%s" "${GIT_TESTING_COMMAND_COMPLETION}"
- else
- git help -a|egrep '^ [a-zA-Z0-9]'
- fi
-}
-
-__git_list_all_commands ()
-{
- local i IFS=" "$'\n'
- for i in $(__git_commands)
- do
- case $i in
- *--*) : helper pattern;;
- *) echo $i;;
- esac
- done
-}
-
__git_all_commands=
__git_compute_all_commands ()
{
test -n "$__git_all_commands" ||
- __git_all_commands=$(__git_list_all_commands)
-}
-
-__git_list_porcelain_commands ()
-{
- local i IFS=" "$'\n'
- __git_compute_all_commands
- for i in $__git_all_commands
- do
- case $i in
- *--*) : helper pattern;;
- applymbox) : ask gittus;;
- applypatch) : ask gittus;;
- archimport) : import;;
- cat-file) : plumbing;;
- check-attr) : plumbing;;
- check-ref-format) : plumbing;;
- checkout-index) : plumbing;;
- commit-tree) : plumbing;;
- count-objects) : infrequent;;
- credential-cache) : credentials helper;;
- credential-store) : credentials helper;;
- cvsexportcommit) : export;;
- cvsimport) : import;;
- cvsserver) : daemon;;
- daemon) : daemon;;
- diff-files) : plumbing;;
- diff-index) : plumbing;;
- diff-tree) : plumbing;;
- fast-import) : import;;
- fast-export) : export;;
- fsck-objects) : plumbing;;
- fetch-pack) : plumbing;;
- fmt-merge-msg) : plumbing;;
- for-each-ref) : plumbing;;
- hash-object) : plumbing;;
- http-*) : transport;;
- index-pack) : plumbing;;
- init-db) : deprecated;;
- local-fetch) : plumbing;;
- lost-found) : infrequent;;
- ls-files) : plumbing;;
- ls-remote) : plumbing;;
- ls-tree) : plumbing;;
- mailinfo) : plumbing;;
- mailsplit) : plumbing;;
- merge-*) : plumbing;;
- mktree) : plumbing;;
- mktag) : plumbing;;
- pack-objects) : plumbing;;
- pack-redundant) : plumbing;;
- pack-refs) : plumbing;;
- parse-remote) : plumbing;;
- patch-id) : plumbing;;
- peek-remote) : plumbing;;
- prune) : plumbing;;
- prune-packed) : plumbing;;
- quiltimport) : import;;
- read-tree) : plumbing;;
- receive-pack) : plumbing;;
- remote-*) : transport;;
- repo-config) : deprecated;;
- rerere) : plumbing;;
- rev-list) : plumbing;;
- rev-parse) : plumbing;;
- runstatus) : plumbing;;
- sh-setup) : internal;;
- shell) : daemon;;
- show-ref) : plumbing;;
- send-pack) : plumbing;;
- show-index) : plumbing;;
- ssh-*) : transport;;
- stripspace) : plumbing;;
- symbolic-ref) : plumbing;;
- tar-tree) : deprecated;;
- unpack-file) : plumbing;;
- unpack-objects) : plumbing;;
- update-index) : plumbing;;
- update-ref) : plumbing;;
- update-server-info) : daemon;;
- upload-archive) : plumbing;;
- upload-pack) : plumbing;;
- write-tree) : plumbing;;
- var) : infrequent;;
- verify-pack) : infrequent;;
- verify-tag) : plumbing;;
- *) echo $i;;
- esac
- done
+ __git_all_commands=$(__git --list-cmds=main,others,alias,nohelpers)
}
-__git_porcelain_commands=
-__git_compute_porcelain_commands ()
+# Lists all set config variables starting with the given section prefix,
+# with the prefix removed.
+__git_get_config_variables ()
{
- __git_compute_all_commands
- test -n "$__git_porcelain_commands" ||
- __git_porcelain_commands=$(__git_list_porcelain_commands)
+ local section="$1" i IFS=$'\n'
+ for i in $(__git config --name-only --get-regexp "^$section\..*"); do
+ echo "${i#$section.}"
+ done
}
__git_pretty_aliases ()
{
- local i IFS=$'\n'
- for i in $(git --git-dir="$(__gitdir)" config --get-regexp "pretty\..*" 2>/dev/null); do
- case "$i" in
- pretty.*)
- i="${i#pretty.}"
- echo "${i/ */}"
- ;;
- esac
+ __git_get_config_variables "pretty"
+}
+
+# __git_aliased_command requires 1 argument
+__git_aliased_command ()
+{
+ local cur=$1 last list= word cmdline
+
+ while [[ -n "$cur" ]]; do
+ if [[ "$list" == *" $cur "* ]]; then
+ # loop detected
+ return
+ fi
+
+ cmdline=$(__git config --get "alias.$cur")
+ list=" $cur $list"
+ last=$cur
+ cur=
+
+ for word in $cmdline; do
+ case "$word" in
+ \!gitk|gitk)
+ cur="gitk"
+ break
+ ;;
+ \!*) : shell command alias ;;
+ -*) : option ;;
+ *=*) : setting env ;;
+ git) : git itself ;;
+ \(\)) : skip parens of shell function definition ;;
+ {) : skip start of shell helper function ;;
+ :) : skip null command ;;
+ \'*) : skip opening quote after sh -c ;;
+ *)
+ cur="$word"
+ break
+ esac
+ done
done
+
+ cur=$last
+ if [[ "$cur" != "$1" ]]; then
+ echo "$cur"
+ fi
}
-__git_aliases ()
+# Check whether one of the given words is present on the command line,
+# and print the first word found.
+#
+# Usage: __git_find_on_cmdline [<option>]... "<wordlist>"
+# --show-idx: Optionally show the index of the found word in the $words array.
+__git_find_on_cmdline ()
{
- local i IFS=$'\n'
- for i in $(git --git-dir="$(__gitdir)" config --get-regexp "alias\..*" 2>/dev/null); do
- case "$i" in
- alias.*)
- i="${i#alias.}"
- echo "${i/ */}"
- ;;
+ local word c="$__git_cmd_idx" show_idx
+
+ while test $# -gt 1; do
+ case "$1" in
+ --show-idx) show_idx=y ;;
+ *) return 1 ;;
esac
+ shift
+ done
+ local wordlist="$1"
+
+ while [ $c -lt $cword ]; do
+ for word in $wordlist; do
+ if [ "$word" = "${words[c]}" ]; then
+ if [ -n "${show_idx-}" ]; then
+ echo "$c $word"
+ else
+ echo "$word"
+ fi
+ return
+ fi
+ done
+ ((c++))
done
}
-# __git_aliased_command requires 1 argument
-__git_aliased_command ()
+# Similar to __git_find_on_cmdline, except that it loops backwards and thus
+# prints the *last* word found. Useful for finding which of two options that
+# supersede each other came last, such as "--guess" and "--no-guess".
+#
+# Usage: __git_find_last_on_cmdline [<option>]... "<wordlist>"
+# --show-idx: Optionally show the index of the found word in the $words array.
+__git_find_last_on_cmdline ()
{
- local word cmdline=$(git --git-dir="$(__gitdir)" \
- config --get "alias.$1")
- for word in $cmdline; do
- case "$word" in
- \!gitk|gitk)
- echo "gitk"
- return
- ;;
- \!*) : shell command alias ;;
- -*) : option ;;
- *=*) : setting env ;;
- git) : git itself ;;
- *)
- echo "$word"
- return
+ local word c=$cword show_idx
+
+ while test $# -gt 1; do
+ case "$1" in
+ --show-idx) show_idx=y ;;
+ *) return 1 ;;
esac
+ shift
+ done
+ local wordlist="$1"
+
+ while [ $c -gt "$__git_cmd_idx" ]; do
+ ((c--))
+ for word in $wordlist; do
+ if [ "$word" = "${words[c]}" ]; then
+ if [ -n "$show_idx" ]; then
+ echo "$c $word"
+ else
+ echo "$word"
+ fi
+ return
+ fi
+ done
done
}
-# __git_find_on_cmdline requires 1 argument
-__git_find_on_cmdline ()
+# Echo the value of an option set on the command line or config
+#
+# $1: short option name
+# $2: long option name including =
+# $3: list of possible values
+# $4: config string (optional)
+#
+# example:
+# result="$(__git_get_option_value "-d" "--do-something=" \
+# "yes no" "core.doSomething")"
+#
+# result is then either empty (no option set) or "yes" or "no"
+#
+# __git_get_option_value requires 3 arguments
+__git_get_option_value ()
{
- local word subcommand c=1
- while [ $c -lt $cword ]; do
+ local c short_opt long_opt val
+ local result= values config_key word
+
+ short_opt="$1"
+ long_opt="$2"
+ values="$3"
+ config_key="$4"
+
+ ((c = $cword - 1))
+ while [ $c -ge 0 ]; do
word="${words[c]}"
- for subcommand in $1; do
- if [ "$subcommand" = "$word" ]; then
- echo "$subcommand"
- return
+ for val in $values; do
+ if [ "$short_opt$val" = "$word" ] ||
+ [ "$long_opt$val" = "$word" ]; then
+ result="$val"
+ break 2
fi
done
- ((c++))
+ ((c--))
done
+
+ if [ -n "$config_key" ] && [ -z "$result" ]; then
+ result="$(__git config "$config_key")"
+ fi
+
+ echo "$result"
}
__git_has_doubledash ()
@@ -731,13 +1292,54 @@ __git_has_doubledash ()
return 1
}
+# Try to count non option arguments passed on the command line for the
+# specified git command.
+# When options are used, it is necessary to use the special -- option to
+# tell the implementation were non option arguments begin.
+# XXX this can not be improved, since options can appear everywhere, as
+# an example:
+# git mv x -n y
+#
+# __git_count_arguments requires 1 argument: the git command executed.
+__git_count_arguments ()
+{
+ local word i c=0
+
+ # Skip "git" (first argument)
+ for ((i=$__git_cmd_idx; i < ${#words[@]}; i++)); do
+ word="${words[i]}"
+
+ case "$word" in
+ --)
+ # Good; we can assume that the following are only non
+ # option arguments.
+ ((c = 0))
+ ;;
+ "$1")
+ # Skip the specified git command and discard git
+ # main options
+ ((c = 0))
+ ;;
+ ?*)
+ ((c++))
+ ;;
+ esac
+ done
+
+ printf "%d" $c
+}
+
__git_whitespacelist="nowarn warn error error-all fix"
+__git_patchformat="mbox stgit stgit-series hg mboxrd"
+__git_showcurrentpatch="diff raw"
+__git_am_inprogress_options="--skip --continue --resolved --abort --quit --show-current-patch"
+__git_quoted_cr="nowarn warn strip"
_git_am ()
{
- local dir="$(__gitdir)"
- if [ -d "$dir"/rebase-apply ]; then
- __gitcomp "--skip --continue --resolved --abort"
+ __git_find_repo_path
+ if [ -d "$__git_repo_path"/rebase-apply ]; then
+ __gitcomp "$__git_am_inprogress_options"
return
fi
case "$cur" in
@@ -745,16 +1347,23 @@ _git_am ()
__gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}"
return
;;
+ --patch-format=*)
+ __gitcomp "$__git_patchformat" "" "${cur##--patch-format=}"
+ return
+ ;;
+ --show-current-patch=*)
+ __gitcomp "$__git_showcurrentpatch" "" "${cur##--show-current-patch=}"
+ return
+ ;;
+ --quoted-cr=*)
+ __gitcomp "$__git_quoted_cr" "" "${cur##--quoted-cr=}"
+ return
+ ;;
--*)
- __gitcomp "
- --3way --committer-date-is-author-date --ignore-date
- --ignore-whitespace --ignore-space-change
- --interactive --keep --no-utf8 --signoff --utf8
- --whitespace= --scissors
- "
+ __gitcomp_builtin am "" \
+ "$__git_am_inprogress_options"
return
esac
- COMPREPLY=()
}
_git_apply ()
@@ -765,31 +1374,29 @@ _git_apply ()
return
;;
--*)
- __gitcomp "
- --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
- "
+ __gitcomp_builtin apply
return
esac
- COMPREPLY=()
}
_git_add ()
{
- __git_has_doubledash && return
-
case "$cur" in
+ --chmod=*)
+ __gitcomp "+x -x" "" "${cur##--chmod=}"
+ return
+ ;;
--*)
- __gitcomp "
- --interactive --refresh --patch --update --dry-run
- --ignore-errors --intent-to-add
- "
+ __gitcomp_builtin add
return
esac
- COMPREPLY=()
+
+ local complete_opt="--others --modified --directory --no-empty-directory"
+ if test -n "$(__git_find_on_cmdline "-u --update")"
+ then
+ complete_opt="--modified"
+ fi
+ __git_complete_index_file "$complete_opt"
}
_git_archive ()
@@ -804,10 +1411,7 @@ _git_archive ()
return
;;
--*)
- __gitcomp "
- --format= --list --verbose
- --prefix= --remote= --exec=
- "
+ __gitcomp_builtin archive "--format= --list --verbose --prefix= --worktree-attributes"
return
;;
esac
@@ -821,7 +1425,8 @@ _git_bisect ()
local subcommands="start bad good skip reset visualize replay log run"
local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
- if [ -f "$(__gitdir)"/BISECT_START ]; then
+ __git_find_repo_path
+ if [ -f "$__git_repo_path"/BISECT_START ]; then
__gitcomp "$subcommands"
else
__gitcomp "replay start"
@@ -831,44 +1436,42 @@ _git_bisect ()
case "$subcommand" in
bad|good|reset|skip|start)
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
;;
*)
- COMPREPLY=()
;;
esac
}
+__git_ref_fieldlist="refname objecttype objectsize objectname upstream push HEAD symref"
+
_git_branch ()
{
- local i c=1 only_local_ref="n" has_r="n"
+ local i c="$__git_cmd_idx" only_local_ref="n" has_r="n"
while [ $c -lt $cword ]; do
i="${words[c]}"
case "$i" in
- -d|-m) only_local_ref="y" ;;
- -r) has_r="y" ;;
+ -d|-D|--delete|-m|-M|--move|-c|-C|--copy)
+ only_local_ref="y" ;;
+ -r|--remotes)
+ has_r="y" ;;
esac
((c++))
done
case "$cur" in
--set-upstream-to=*)
- __gitcomp "$(__git_refs)" "" "${cur##--set-upstream-to=}"
+ __git_complete_refs --cur="${cur##--set-upstream-to=}"
;;
--*)
- __gitcomp "
- --color --no-color --verbose --abbrev= --no-abbrev
- --track --no-track --contains --merged --no-merged
- --set-upstream-to= --edit-description --list
- --unset-upstream
- "
+ __gitcomp_builtin branch
;;
*)
if [ $only_local_ref = "y" -a $has_r = "n" ]; then
- __gitcomp_nl "$(__git_heads)"
+ __gitcomp_direct "$(__git_heads "" "$cur" " ")"
else
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
fi
;;
esac
@@ -876,12 +1479,12 @@ _git_branch ()
_git_bundle ()
{
- local cmd="${words[2]}"
+ local cmd="${words[__git_cmd_idx+1]}"
case "$cword" in
- 2)
+ $((__git_cmd_idx+1)))
__gitcomp "create list-heads verify unbundle"
;;
- 3)
+ $((__git_cmd_idx+2)))
# looking for a file
;;
*)
@@ -894,176 +1497,277 @@ _git_bundle ()
esac
}
+# Helper function to decide whether or not we should enable DWIM logic for
+# git-switch and git-checkout.
+#
+# To decide between the following rules in decreasing priority order:
+# - the last provided of "--guess" or "--no-guess" explicitly enable or
+# disable completion of DWIM logic respectively.
+# - If checkout.guess is false, disable completion of DWIM logic.
+# - If the --no-track option is provided, take this as a hint to disable the
+# DWIM completion logic
+# - If GIT_COMPLETION_CHECKOUT_NO_GUESS is set, disable the DWIM completion
+# logic, as requested by the user.
+# - Enable DWIM logic otherwise.
+#
+__git_checkout_default_dwim_mode ()
+{
+ local last_option dwim_opt="--dwim"
+
+ if [ "${GIT_COMPLETION_CHECKOUT_NO_GUESS-}" = "1" ]; then
+ dwim_opt=""
+ fi
+
+ # --no-track disables DWIM, but with lower priority than
+ # --guess/--no-guess/checkout.guess
+ if [ -n "$(__git_find_on_cmdline "--no-track")" ]; then
+ dwim_opt=""
+ fi
+
+ # checkout.guess = false disables DWIM, but with lower priority than
+ # --guess/--no-guess
+ if [ "$(__git config --type=bool checkout.guess)" = "false" ]; then
+ dwim_opt=""
+ fi
+
+ # Find the last provided --guess or --no-guess
+ last_option="$(__git_find_last_on_cmdline "--guess --no-guess")"
+ case "$last_option" in
+ --guess)
+ dwim_opt="--dwim"
+ ;;
+ --no-guess)
+ dwim_opt=""
+ ;;
+ esac
+
+ echo "$dwim_opt"
+}
+
_git_checkout ()
{
__git_has_doubledash && return
+ local dwim_opt="$(__git_checkout_default_dwim_mode)"
+
+ case "$prev" in
+ -b|-B|--orphan)
+ # Complete local branches (and DWIM branch
+ # remote branch names) for an option argument
+ # specifying a new branch name. This is for
+ # convenience, assuming new branches are
+ # possibly based on pre-existing branch names.
+ __git_complete_refs $dwim_opt --mode="heads"
+ return
+ ;;
+ *)
+ ;;
+ esac
+
case "$cur" in
--conflict=*)
__gitcomp "diff3 merge" "" "${cur##--conflict=}"
;;
--*)
- __gitcomp "
- --quiet --ours --theirs --track --no-track --merge
- --conflict= --orphan --patch
- "
+ __gitcomp_builtin checkout
;;
*)
- # check if --track, --no-track, or --no-guess was specified
- # if so, disable DWIM mode
- local flags="--track --no-track --no-guess" track=1
- if [ -n "$(__git_find_on_cmdline "$flags")" ]; then
- track=''
+ # At this point, we've already handled special completion for
+ # the arguments to -b/-B, and --orphan. There are 3 main
+ # things left we can possibly complete:
+ # 1) a start-point for -b/-B, -d/--detach, or --orphan
+ # 2) a remote head, for --track
+ # 3) an arbitrary reference, possibly including DWIM names
+ #
+
+ if [ -n "$(__git_find_on_cmdline "-b -B -d --detach --orphan")" ]; then
+ __git_complete_refs --mode="refs"
+ elif [ -n "$(__git_find_on_cmdline "--track")" ]; then
+ __git_complete_refs --mode="remote-heads"
+ else
+ __git_complete_refs $dwim_opt --mode="refs"
fi
- __gitcomp_nl "$(__git_refs '' $track)"
;;
esac
}
-_git_cherry ()
-{
- __gitcomp "$(__git_refs)"
-}
+__git_sequencer_inprogress_options="--continue --quit --abort --skip"
+
+__git_cherry_pick_inprogress_options=$__git_sequencer_inprogress_options
_git_cherry_pick ()
{
+ __git_find_repo_path
+ if [ -f "$__git_repo_path"/CHERRY_PICK_HEAD ]; then
+ __gitcomp "$__git_cherry_pick_inprogress_options"
+ return
+ fi
+
+ __git_complete_strategy && return
+
case "$cur" in
--*)
- __gitcomp "--edit --no-commit"
+ __gitcomp_builtin cherry-pick "" \
+ "$__git_cherry_pick_inprogress_options"
;;
*)
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
;;
esac
}
_git_clean ()
{
- __git_has_doubledash && return
-
case "$cur" in
--*)
- __gitcomp "--dry-run --quiet"
+ __gitcomp_builtin clean
return
;;
esac
- COMPREPLY=()
+
+ # XXX should we check for -x option ?
+ __git_complete_index_file "--others --directory"
}
_git_clone ()
{
+ case "$prev" in
+ -c|--config)
+ __git_complete_config_variable_name_and_value
+ return
+ ;;
+ esac
case "$cur" in
+ --config=*)
+ __git_complete_config_variable_name_and_value \
+ --cur="${cur##--config=}"
+ return
+ ;;
--*)
- __gitcomp "
- --local
- --no-hardlinks
- --shared
- --reference
- --quiet
- --no-checkout
- --bare
- --mirror
- --origin
- --upload-pack
- --template=
- --depth
- --single-branch
- --branch
- "
+ __gitcomp_builtin clone
return
;;
esac
- COMPREPLY=()
}
+__git_untracked_file_modes="all no normal"
+
_git_commit ()
{
- __git_has_doubledash && return
-
case "$prev" in
-c|-C)
- __gitcomp_nl "$(__git_refs)" "" "${cur}"
+ __git_complete_refs
return
;;
esac
case "$cur" in
--cleanup=*)
- __gitcomp "default strip verbatim whitespace
+ __gitcomp "default scissors strip verbatim whitespace
" "" "${cur##--cleanup=}"
return
;;
--reuse-message=*|--reedit-message=*|\
--fixup=*|--squash=*)
- __gitcomp_nl "$(__git_refs)" "" "${cur#*=}"
+ __git_complete_refs --cur="${cur#*=}"
return
;;
--untracked-files=*)
- __gitcomp "all no normal" "" "${cur##--untracked-files=}"
+ __gitcomp "$__git_untracked_file_modes" "" "${cur##--untracked-files=}"
return
;;
--*)
- __gitcomp "
- --all --author= --signoff --verify --no-verify
- --edit --no-edit
- --amend --include --only --interactive
- --dry-run --reuse-message= --reedit-message=
- --reset-author --file= --message= --template=
- --cleanup= --untracked-files --untracked-files=
- --verbose --quiet --fixup= --squash=
- "
+ __gitcomp_builtin commit
return
esac
- COMPREPLY=()
+
+ if __git rev-parse --verify --quiet HEAD >/dev/null; then
+ __git_complete_index_file "--committable"
+ else
+ # This is the first commit
+ __git_complete_index_file "--cached"
+ fi
}
_git_describe ()
{
case "$cur" in
--*)
- __gitcomp "
- --all --tags --contains --abbrev= --candidates=
- --exact-match --debug --long --match --always
- "
+ __gitcomp_builtin describe
return
esac
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
}
+__git_diff_algorithms="myers minimal patience histogram"
+
+__git_diff_submodule_formats="diff log short"
+
+__git_color_moved_opts="no default plain blocks zebra dimmed-zebra"
+
+__git_color_moved_ws_opts="no ignore-space-at-eol ignore-space-change
+ ignore-all-space allow-indentation-change"
+
__git_diff_common_options="--stat --numstat --shortstat --summary
--patch-with-stat --name-only --name-status --color
--no-color --color-words --no-renames --check
+ --color-moved --color-moved= --no-color-moved
+ --color-moved-ws= --no-color-moved-ws
--full-index --binary --abbrev --diff-filter=
- --find-copies-harder
+ --find-copies-harder --ignore-cr-at-eol
--text --ignore-space-at-eol --ignore-space-change
- --ignore-all-space --exit-code --quiet --ext-diff
- --no-ext-diff
+ --ignore-all-space --ignore-blank-lines --exit-code
+ --quiet --ext-diff --no-ext-diff
--no-prefix --src-prefix= --dst-prefix=
--inter-hunk-context=
- --patience
- --raw
+ --patience --histogram --minimal
+ --raw --word-diff --word-diff-regex=
--dirstat --dirstat= --dirstat-by-file
--dirstat-by-file= --cumulative
+ --diff-algorithm=
+ --submodule --submodule= --ignore-submodules
+ --indent-heuristic --no-indent-heuristic
+ --textconv --no-textconv
+ --patch --no-patch
+ --anchored=
"
+__git_diff_difftool_options="--cached --staged --pickaxe-all --pickaxe-regex
+ --base --ours --theirs --no-index --relative --merge-base
+ $__git_diff_common_options"
+
_git_diff ()
{
__git_has_doubledash && return
case "$cur" in
+ --diff-algorithm=*)
+ __gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
+ return
+ ;;
+ --submodule=*)
+ __gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
+ return
+ ;;
+ --color-moved=*)
+ __gitcomp "$__git_color_moved_opts" "" "${cur##--color-moved=}"
+ return
+ ;;
+ --color-moved-ws=*)
+ __gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}"
+ return
+ ;;
--*)
- __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
- --base --ours --theirs --no-index
- $__git_diff_common_options
- "
+ __gitcomp "$__git_diff_difftool_options"
return
;;
esac
__git_complete_revlist_file
}
-__git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff
- tkdiff vimdiff gvimdiff xxdiff araxis p4merge bc3 codecompare
+__git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
+ tkdiff vimdiff nvimdiff gvimdiff xxdiff araxis p4merge
+ bc codecompare smerge
"
_git_difftool ()
@@ -1076,39 +1780,37 @@ _git_difftool ()
return
;;
--*)
- __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
- --base --ours --theirs
- --no-renames --diff-filter= --find-copies-harder
- --relative --ignore-submodules
- --tool="
+ __gitcomp_builtin difftool "$__git_diff_difftool_options"
return
;;
esac
- __git_complete_file
+ __git_complete_revlist_file
}
-__git_fetch_options="
- --quiet --verbose --append --upload-pack --force --keep --depth=
- --tags --no-tags --all --prune --dry-run
-"
+__git_fetch_recurse_submodules="yes on-demand no"
_git_fetch ()
{
case "$cur" in
+ --recurse-submodules=*)
+ __gitcomp "$__git_fetch_recurse_submodules" "" "${cur##--recurse-submodules=}"
+ return
+ ;;
+ --filter=*)
+ __gitcomp "blob:none blob:limit= sparse:oid=" "" "${cur##--filter=}"
+ return
+ ;;
--*)
- __gitcomp "$__git_fetch_options"
+ __gitcomp_builtin fetch
return
;;
esac
__git_complete_remote_or_refspec
}
-__git_format_patch_options="
- --stdout --attach --no-attach --thread --thread= --output-directory
- --numbered --start-number --numbered-files --keep-subject --signoff
- --signature --no-signature --in-reply-to= --cc= --full-index --binary
- --not --all --cover-letter --no-prefix --src-prefix= --dst-prefix=
- --inline --suffix= --ignore-if-in-upstream --subject-prefix=
+__git_format_patch_extra_options="
+ --full-index --not --all --no-prefix --src-prefix=
+ --dst-prefix= --notes
"
_git_format_patch ()
@@ -1120,46 +1822,70 @@ _git_format_patch ()
" "" "${cur##--thread=}"
return
;;
- --*)
- __gitcomp "$__git_format_patch_options"
+ --base=*|--interdiff=*|--range-diff=*)
+ __git_complete_refs --cur="${cur#--*=}"
return
;;
- esac
- __git_complete_revlist
-}
-
-_git_fsck ()
-{
- case "$cur" in
--*)
- __gitcomp "
- --tags --root --unreachable --cache --no-reflogs --full
- --strict --verbose --lost-found
- "
+ __gitcomp_builtin format-patch "$__git_format_patch_extra_options"
return
;;
esac
- COMPREPLY=()
+ __git_complete_revlist
}
-_git_gc ()
+_git_fsck ()
{
case "$cur" in
--*)
- __gitcomp "--prune --aggressive"
+ __gitcomp_builtin fsck
return
;;
esac
- COMPREPLY=()
}
_git_gitk ()
{
- _gitk
-}
+ __gitk_main
+}
+
+# Lists matching symbol names from a tag (as in ctags) file.
+# 1: List symbol names matching this word.
+# 2: The tag file to list symbol names from.
+# 3: A prefix to be added to each listed symbol name (optional).
+# 4: A suffix to be appended to each listed symbol name (optional).
+__git_match_ctag () {
+ awk -v pfx="${3-}" -v sfx="${4-}" "
+ /^${1//\//\\/}/ { print pfx \$1 sfx }
+ " "$2"
+}
+
+# Complete symbol names from a tag file.
+# Usage: __git_complete_symbol [<option>]...
+# --tags=<file>: The tag file to list symbol names from instead of the
+# default "tags".
+# --pfx=<prefix>: A prefix to be added to each symbol name.
+# --cur=<word>: The current symbol name to be completed. Defaults to
+# the current word to be completed.
+# --sfx=<suffix>: A suffix to be appended to each symbol name instead
+# of the default space.
+__git_complete_symbol () {
+ local tags=tags pfx="" cur_="${cur-}" sfx=" "
+
+ while test $# != 0; do
+ case "$1" in
+ --tags=*) tags="${1##--tags=}" ;;
+ --pfx=*) pfx="${1##--pfx=}" ;;
+ --cur=*) cur_="${1##--cur=}" ;;
+ --sfx=*) sfx="${1##--sfx=}" ;;
+ *) return 1 ;;
+ esac
+ shift
+ done
-__git_match_ctag() {
- awk "/^${1////\\/}/ { print \$1 }" "$2"
+ if test -r "$tags"; then
+ __gitcomp_direct "$(__git_match_ctag "$cur_" "$tags" "$pfx" "$sfx")"
+ fi
}
_git_grep ()
@@ -1168,49 +1894,34 @@ _git_grep ()
case "$cur" in
--*)
- __gitcomp "
- --cached
- --text --ignore-case --word-regexp --invert-match
- --full-name --line-number
- --extended-regexp --basic-regexp --fixed-strings
- --perl-regexp
- --files-with-matches --name-only
- --files-without-match
- --max-depth
- --count
- --and --or --not --all-match
- "
+ __gitcomp_builtin grep
return
;;
esac
case "$cword,$prev" in
- 2,*|*,-*)
- if test -r tags; then
- __gitcomp_nl "$(__git_match_ctag "$cur" tags)"
- return
- fi
+ $((__git_cmd_idx+1)),*|*,-*)
+ __git_complete_symbol && return
;;
esac
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
}
_git_help ()
{
case "$cur" in
--*)
- __gitcomp "--all --info --man --web"
+ __gitcomp_builtin help
return
;;
esac
- __git_compute_all_commands
- __gitcomp "$__git_all_commands $(__git_aliases)
- attributes cli core-tutorial cvs-migration
- diffcore gitk glossary hooks ignore modules
- namespaces repository-layout tutorial tutorial-2
- workflows
- "
+ if test -n "${GIT_TESTING_ALL_COMMAND_LIST-}"
+ then
+ __gitcomp "$GIT_TESTING_ALL_COMMAND_LIST $(__git --list-cmds=alias,list-guide) gitk"
+ else
+ __gitcomp "$(__git --list-cmds=main,nohelpers,alias,list-guide) gitk"
+ fi
}
_git_init ()
@@ -1223,39 +1934,46 @@ _git_init ()
return
;;
--*)
- __gitcomp "--quiet --bare --template= --shared --shared="
+ __gitcomp_builtin init
return
;;
esac
- COMPREPLY=()
}
_git_ls_files ()
{
- __git_has_doubledash && return
-
case "$cur" in
--*)
- __gitcomp "--cached --deleted --modified --others --ignored
- --stage --directory --no-empty-directory --unmerged
- --killed --exclude= --exclude-from=
- --exclude-per-directory= --exclude-standard
- --error-unmatch --with-tree= --full-name
- --abbrev --ignored --exclude-per-directory
- "
+ __gitcomp_builtin ls-files
return
;;
esac
- COMPREPLY=()
+
+ # XXX ignore options like --modified and always suggest all cached
+ # files.
+ __git_complete_index_file "--cached"
}
_git_ls_remote ()
{
+ case "$cur" in
+ --*)
+ __gitcomp_builtin ls-remote
+ return
+ ;;
+ esac
__gitcomp_nl "$(__git_remotes)"
}
_git_ls_tree ()
{
+ case "$cur" in
+ --*)
+ __gitcomp_builtin ls-tree
+ return
+ ;;
+ esac
+
__git_complete_file
}
@@ -1279,21 +1997,34 @@ __git_log_gitk_options="
# Options that go well for log and shortlog (not gitk)
__git_log_shortlog_options="
--author= --committer= --grep=
- --all-match
+ --all-match --invert-grep
"
-__git_log_pretty_formats="oneline short medium full fuller email raw format:"
-__git_log_date_formats="relative iso8601 rfc2822 short local default raw"
+__git_log_pretty_formats="oneline short medium full fuller reference email raw format: tformat: mboxrd"
+__git_log_date_formats="relative iso8601 iso8601-strict rfc2822 short local default raw unix format:"
_git_log ()
{
__git_has_doubledash && return
+ __git_find_repo_path
- local g="$(git rev-parse --git-dir 2>/dev/null)"
local merge=""
- if [ -f "$g/MERGE_HEAD" ]; then
+ if [ -f "$__git_repo_path/MERGE_HEAD" ]; then
merge="--merge"
fi
+ case "$prev,$cur" in
+ -L,:*:*)
+ return # fall back to Bash filename completion
+ ;;
+ -L,:*)
+ __git_complete_symbol --cur="${cur#:}" --sfx=":"
+ return
+ ;;
+ -G,*|-S,*)
+ __git_complete_symbol
+ return
+ ;;
+ esac
case "$cur" in
--pretty=*|--format=*)
__gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
@@ -1305,7 +2036,19 @@ _git_log ()
return
;;
--decorate=*)
- __gitcomp "long short" "" "${cur##--decorate=}"
+ __gitcomp "full short no" "" "${cur##--decorate=}"
+ return
+ ;;
+ --diff-algorithm=*)
+ __gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
+ return
+ ;;
+ --submodule=*)
+ __gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
+ return
+ ;;
+ --no-walk=*)
+ __gitcomp "sorted unsorted" "" "${cur##--no-walk=}"
return
;;
--*)
@@ -1315,39 +2058,53 @@ _git_log ()
$__git_log_gitk_options
--root --topo-order --date-order --reverse
--follow --full-diff
- --abbrev-commit --abbrev=
+ --abbrev-commit --no-abbrev-commit --abbrev=
--relative-date --date=
--pretty= --format= --oneline
+ --show-signature
+ --cherry-mark
--cherry-pick
--graph
- --decorate --decorate=
+ --decorate --decorate= --no-decorate
--walk-reflogs
+ --no-walk --no-walk= --do-walk
--parents --children
+ --expand-tabs --expand-tabs= --no-expand-tabs
$merge
$__git_diff_common_options
--pickaxe-all --pickaxe-regex
"
return
;;
+ -L:*:*)
+ return # fall back to Bash filename completion
+ ;;
+ -L:*)
+ __git_complete_symbol --cur="${cur#-L:}" --sfx=":"
+ return
+ ;;
+ -G*)
+ __git_complete_symbol --pfx="-G" --cur="${cur#-G}"
+ return
+ ;;
+ -S*)
+ __git_complete_symbol --pfx="-S" --cur="${cur#-S}"
+ return
+ ;;
esac
__git_complete_revlist
}
-__git_merge_options="
- --no-commit --no-stat --log --no-log --squash --strategy
- --commit --stat --no-squash --ff --no-ff --ff-only --edit --no-edit
-"
-
_git_merge ()
{
__git_complete_strategy && return
case "$cur" in
--*)
- __gitcomp "$__git_merge_options"
+ __gitcomp_builtin merge
return
esac
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
}
_git_mergetool ()
@@ -1358,75 +2115,75 @@ _git_mergetool ()
return
;;
--*)
- __gitcomp "--tool="
+ __gitcomp "--tool= --prompt --no-prompt --gui --no-gui"
return
;;
esac
- COMPREPLY=()
}
_git_merge_base ()
{
- __gitcomp_nl "$(__git_refs)"
+ case "$cur" in
+ --*)
+ __gitcomp_builtin merge-base
+ return
+ ;;
+ esac
+ __git_complete_refs
}
_git_mv ()
{
case "$cur" in
--*)
- __gitcomp "--dry-run"
+ __gitcomp_builtin mv
return
;;
esac
- COMPREPLY=()
-}
-_git_name_rev ()
-{
- __gitcomp "--tags --all --stdin"
+ if [ $(__git_count_arguments "mv") -gt 0 ]; then
+ # We need to show both cached and untracked files (including
+ # empty directories) since this may not be the last argument.
+ __git_complete_index_file "--cached --others --directory"
+ else
+ __git_complete_index_file "--cached"
+ fi
}
_git_notes ()
{
- local subcommands='add append copy edit list prune remove show'
+ local subcommands='add append copy edit get-ref list merge prune remove show'
local subcommand="$(__git_find_on_cmdline "$subcommands")"
case "$subcommand,$cur" in
,--*)
- __gitcomp '--ref'
+ __gitcomp_builtin notes
;;
,*)
case "$prev" in
--ref)
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
;;
*)
__gitcomp "$subcommands --ref"
;;
esac
;;
- add,--reuse-message=*|append,--reuse-message=*|\
- add,--reedit-message=*|append,--reedit-message=*)
- __gitcomp_nl "$(__git_refs)" "" "${cur#*=}"
+ *,--reuse-message=*|*,--reedit-message=*)
+ __git_complete_refs --cur="${cur#*=}"
;;
- add,--*|append,--*)
- __gitcomp '--file= --message= --reedit-message=
- --reuse-message='
+ *,--*)
+ __gitcomp_builtin notes_$subcommand
;;
- copy,--*)
- __gitcomp '--stdin'
- ;;
- prune,--*)
- __gitcomp '--dry-run --verbose'
- ;;
- prune,*)
+ prune,*|get-ref,*)
+ # this command does not take a ref, do not complete it
;;
*)
case "$prev" in
-m|-F)
;;
*)
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
;;
esac
;;
@@ -1438,46 +2195,96 @@ _git_pull ()
__git_complete_strategy && return
case "$cur" in
+ --recurse-submodules=*)
+ __gitcomp "$__git_fetch_recurse_submodules" "" "${cur##--recurse-submodules=}"
+ return
+ ;;
--*)
- __gitcomp "
- --rebase --no-rebase
- $__git_merge_options
- $__git_fetch_options
- "
+ __gitcomp_builtin pull
+
return
;;
esac
__git_complete_remote_or_refspec
}
+__git_push_recurse_submodules="check on-demand only"
+
+__git_complete_force_with_lease ()
+{
+ local cur_=$1
+
+ case "$cur_" in
+ --*=)
+ ;;
+ *:*)
+ __git_complete_refs --cur="${cur_#*:}"
+ ;;
+ *)
+ __git_complete_refs --cur="$cur_"
+ ;;
+ esac
+}
+
_git_push ()
{
case "$prev" in
--repo)
__gitcomp_nl "$(__git_remotes)"
return
+ ;;
+ --recurse-submodules)
+ __gitcomp "$__git_push_recurse_submodules"
+ return
+ ;;
esac
case "$cur" in
--repo=*)
__gitcomp_nl "$(__git_remotes)" "" "${cur##--repo=}"
return
;;
+ --recurse-submodules=*)
+ __gitcomp "$__git_push_recurse_submodules" "" "${cur##--recurse-submodules=}"
+ return
+ ;;
+ --force-with-lease=*)
+ __git_complete_force_with_lease "${cur##--force-with-lease=}"
+ return
+ ;;
+ --*)
+ __gitcomp_builtin push
+ return
+ ;;
+ esac
+ __git_complete_remote_or_refspec
+}
+
+_git_range_diff ()
+{
+ case "$cur" in
--*)
__gitcomp "
- --all --mirror --tags --dry-run --force --verbose
- --receive-pack= --repo= --set-upstream
+ --creation-factor= --no-dual-color
+ $__git_diff_common_options
"
return
;;
esac
- __git_complete_remote_or_refspec
+ __git_complete_revlist
}
+__git_rebase_inprogress_options="--continue --skip --abort --quit --show-current-patch"
+__git_rebase_interactive_inprogress_options="$__git_rebase_inprogress_options --edit-todo"
+
_git_rebase ()
{
- local dir="$(__gitdir)"
- if [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then
- __gitcomp "--continue --skip --abort"
+ __git_find_repo_path
+ if [ -f "$__git_repo_path"/rebase-merge/interactive ]; then
+ __gitcomp "$__git_rebase_interactive_inprogress_options"
+ return
+ elif [ -d "$__git_repo_path"/rebase-apply ] || \
+ [ -d "$__git_repo_path"/rebase-merge ]; then
+ __gitcomp "$__git_rebase_inprogress_options"
return
fi
__git_complete_strategy && return
@@ -1486,18 +2293,17 @@ _git_rebase ()
__gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}"
return
;;
+ --onto=*)
+ __git_complete_refs --cur="${cur##--onto=}"
+ return
+ ;;
--*)
- __gitcomp "
- --onto --merge --strategy --interactive
- --preserve-merges --stat --no-stat
- --committer-date-is-author-date --ignore-date
- --ignore-whitespace --whitespace=
- --autosquash
- "
+ __gitcomp_builtin rebase "" \
+ "$__git_rebase_interactive_inprogress_options"
return
esac
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
}
_git_reflog ()
@@ -1508,7 +2314,7 @@ _git_reflog ()
if [ -z "$subcommand" ]; then
__gitcomp "$subcommands"
else
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
fi
}
@@ -1517,6 +2323,13 @@ __git_send_email_suppresscc_options="author self cc bodycc sob cccmd body all"
_git_send_email ()
{
+ case "$prev" in
+ --to|--cc|--bcc|--from)
+ __gitcomp "$(__git send-email --dump-aliases)"
+ return
+ ;;
+ esac
+
case "$cur" in
--confirm=*)
__gitcomp "
@@ -1541,17 +2354,21 @@ _git_send_email ()
" "" "${cur##--thread=}"
return
;;
+ --to=*|--cc=*|--bcc=*|--from=*)
+ __gitcomp "$(__git send-email --dump-aliases)" "" "${cur#--*=}"
+ return
+ ;;
--*)
- __gitcomp "--annotate --bcc --cc --cc-cmd --chain-reply-to
+ __gitcomp_builtin send-email "--annotate --bcc --cc --cc-cmd --chain-reply-to
--compose --confirm= --dry-run --envelope-sender
--from --identity
--in-reply-to --no-chain-reply-to --no-signed-off-by-cc
- --no-suppress-from --no-thread --quiet
+ --no-suppress-from --no-thread --quiet --reply-to
--signed-off-by-cc --smtp-pass --smtp-server
--smtp-server-port --smtp-encryption= --smtp-user
--subject --suppress-cc= --suppress-from --thread --to
--validate --no-validate
- $__git_format_patch_options"
+ $__git_format_patch_extra_options"
return
;;
esac
@@ -1563,13 +2380,110 @@ _git_stage ()
_git_add
}
+_git_status ()
+{
+ local complete_opt
+ local untracked_state
+
+ case "$cur" in
+ --ignore-submodules=*)
+ __gitcomp "none untracked dirty all" "" "${cur##--ignore-submodules=}"
+ return
+ ;;
+ --untracked-files=*)
+ __gitcomp "$__git_untracked_file_modes" "" "${cur##--untracked-files=}"
+ return
+ ;;
+ --column=*)
+ __gitcomp "
+ always never auto column row plain dense nodense
+ " "" "${cur##--column=}"
+ return
+ ;;
+ --*)
+ __gitcomp_builtin status
+ return
+ ;;
+ esac
+
+ untracked_state="$(__git_get_option_value "-u" "--untracked-files=" \
+ "$__git_untracked_file_modes" "status.showUntrackedFiles")"
+
+ case "$untracked_state" in
+ no)
+ # --ignored option does not matter
+ complete_opt=
+ ;;
+ all|normal|*)
+ complete_opt="--cached --directory --no-empty-directory --others"
+
+ if [ -n "$(__git_find_on_cmdline "--ignored")" ]; then
+ complete_opt="$complete_opt --ignored --exclude=*"
+ fi
+ ;;
+ esac
+
+ __git_complete_index_file "$complete_opt"
+}
+
+_git_switch ()
+{
+ local dwim_opt="$(__git_checkout_default_dwim_mode)"
+
+ case "$prev" in
+ -c|-C|--orphan)
+ # Complete local branches (and DWIM branch
+ # remote branch names) for an option argument
+ # specifying a new branch name. This is for
+ # convenience, assuming new branches are
+ # possibly based on pre-existing branch names.
+ __git_complete_refs $dwim_opt --mode="heads"
+ return
+ ;;
+ *)
+ ;;
+ esac
+
+ case "$cur" in
+ --conflict=*)
+ __gitcomp "diff3 merge" "" "${cur##--conflict=}"
+ ;;
+ --*)
+ __gitcomp_builtin switch
+ ;;
+ *)
+ # Unlike in git checkout, git switch --orphan does not take
+ # a start point. Thus we really have nothing to complete after
+ # the branch name.
+ if [ -n "$(__git_find_on_cmdline "--orphan")" ]; then
+ return
+ fi
+
+ # At this point, we've already handled special completion for
+ # -c/-C, and --orphan. There are 3 main things left to
+ # complete:
+ # 1) a start-point for -c/-C or -d/--detach
+ # 2) a remote head, for --track
+ # 3) a branch name, possibly including DWIM remote branches
+
+ if [ -n "$(__git_find_on_cmdline "-c -C -d --detach")" ]; then
+ __git_complete_refs --mode="refs"
+ elif [ -n "$(__git_find_on_cmdline "--track")" ]; then
+ __git_complete_refs --mode="remote-heads"
+ else
+ __git_complete_refs $dwim_opt --mode="heads"
+ fi
+ ;;
+ esac
+}
+
__git_config_get_set_variables ()
{
local prevword word config_file= c=$cword
- while [ $c -gt 1 ]; do
+ while [ $c -gt "$__git_cmd_idx" ]; do
word="${words[c]}"
case "$word" in
- --global|--system|--file=*)
+ --system|--global|--local|--file=*)
config_file="$word"
break
;;
@@ -1582,480 +2496,370 @@ __git_config_get_set_variables ()
c=$((--c))
done
- git --git-dir="$(__gitdir)" config $config_file --list 2>/dev/null |
- while read -r line
- do
- case "$line" in
- *.*=*)
- echo "${line/=*/}"
- ;;
- esac
- done
+ __git config $config_file --name-only --list
}
-_git_config ()
+__git_config_vars=
+__git_compute_config_vars ()
{
- case "$prev" in
- branch.*.remote)
- __gitcomp_nl "$(__git_remotes)"
+ test -n "$__git_config_vars" ||
+ __git_config_vars="$(git help --config-for-completion)"
+}
+
+__git_config_sections=
+__git_compute_config_sections ()
+{
+ test -n "$__git_config_sections" ||
+ __git_config_sections="$(git help --config-sections-for-completion)"
+}
+
+# Completes possible values of various configuration variables.
+#
+# Usage: __git_complete_config_variable_value [<option>]...
+# --varname=<word>: The name of the configuration variable whose value is
+# to be completed. Defaults to the previous word on the
+# command line.
+# --cur=<word>: The current value to be completed. Defaults to the current
+# word to be completed.
+__git_complete_config_variable_value ()
+{
+ local varname="$prev" cur_="$cur"
+
+ while test $# != 0; do
+ case "$1" in
+ --varname=*) varname="${1##--varname=}" ;;
+ --cur=*) cur_="${1##--cur=}" ;;
+ *) return 1 ;;
+ esac
+ shift
+ done
+
+ if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
+ varname="${varname,,}"
+ else
+ varname="$(echo "$varname" |tr A-Z a-z)"
+ fi
+
+ case "$varname" in
+ branch.*.remote|branch.*.pushremote)
+ __gitcomp_nl "$(__git_remotes)" "" "$cur_"
return
;;
branch.*.merge)
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs --cur="$cur_"
+ return
+ ;;
+ branch.*.rebase)
+ __gitcomp "false true merges interactive" "" "$cur_"
+ return
+ ;;
+ remote.pushdefault)
+ __gitcomp_nl "$(__git_remotes)" "" "$cur_"
return
;;
remote.*.fetch)
- local remote="${prev#remote.}"
+ local remote="${varname#remote.}"
remote="${remote%.fetch}"
- if [ -z "$cur" ]; then
- COMPREPLY=("refs/heads/")
+ if [ -z "$cur_" ]; then
+ __gitcomp_nl "refs/heads/" "" "" ""
return
fi
- __gitcomp_nl "$(__git_refs_remotes "$remote")"
+ __gitcomp_nl "$(__git_refs_remotes "$remote")" "" "$cur_"
return
;;
remote.*.push)
- local remote="${prev#remote.}"
+ local remote="${varname#remote.}"
remote="${remote%.push}"
- __gitcomp_nl "$(git --git-dir="$(__gitdir)" \
- for-each-ref --format='%(refname):%(refname)' \
- refs/heads)"
+ __gitcomp_nl "$(__git for-each-ref \
+ --format='%(refname):%(refname)' refs/heads)" "" "$cur_"
return
;;
pull.twohead|pull.octopus)
__git_compute_merge_strategies
- __gitcomp "$__git_merge_strategies"
- return
- ;;
- color.branch|color.diff|color.interactive|\
- color.showbranch|color.status|color.ui)
- __gitcomp "always never auto"
+ __gitcomp "$__git_merge_strategies" "" "$cur_"
return
;;
color.pager)
- __gitcomp "false true"
+ __gitcomp "false true" "" "$cur_"
return
;;
color.*.*)
__gitcomp "
normal black red green yellow blue magenta cyan white
bold dim ul blink reverse
- "
+ " "" "$cur_"
+ return
+ ;;
+ color.*)
+ __gitcomp "false true always never auto" "" "$cur_"
+ return
+ ;;
+ diff.submodule)
+ __gitcomp "$__git_diff_submodule_formats" "" "$cur_"
return
;;
help.format)
- __gitcomp "man info web html"
+ __gitcomp "man info web html" "" "$cur_"
return
;;
log.date)
- __gitcomp "$__git_log_date_formats"
+ __gitcomp "$__git_log_date_formats" "" "$cur_"
return
;;
- sendemail.aliasesfiletype)
- __gitcomp "mutt mailrc pine elm gnus"
+ sendemail.aliasfiletype)
+ __gitcomp "mutt mailrc pine elm gnus" "" "$cur_"
return
;;
sendemail.confirm)
- __gitcomp "$__git_send_email_confirm_options"
+ __gitcomp "$__git_send_email_confirm_options" "" "$cur_"
return
;;
sendemail.suppresscc)
- __gitcomp "$__git_send_email_suppresscc_options"
+ __gitcomp "$__git_send_email_suppresscc_options" "" "$cur_"
return
;;
- --get|--get-all|--unset|--unset-all)
- __gitcomp_nl "$(__git_config_get_set_variables)"
+ sendemail.transferencoding)
+ __gitcomp "7bit 8bit quoted-printable base64" "" "$cur_"
return
;;
*.*)
- COMPREPLY=()
return
;;
esac
- case "$cur" in
- --*)
- __gitcomp "
- --global --system --file=
- --list --replace-all
- --get --get-all --get-regexp
- --add --unset --unset-all
- --remove-section --rename-section
- "
- return
- ;;
+}
+
+# Completes configuration sections, subsections, variable names.
+#
+# Usage: __git_complete_config_variable_name [<option>]...
+# --cur=<word>: The current configuration section/variable name to be
+# completed. Defaults to the current word to be completed.
+# --sfx=<suffix>: A suffix to be appended to each fully completed
+# configuration variable name (but not to sections or
+# subsections) instead of the default space.
+__git_complete_config_variable_name ()
+{
+ local cur_="$cur" sfx
+
+ while test $# != 0; do
+ case "$1" in
+ --cur=*) cur_="${1##--cur=}" ;;
+ --sfx=*) sfx="${1##--sfx=}" ;;
+ *) return 1 ;;
+ esac
+ shift
+ done
+
+ case "$cur_" in
branch.*.*)
- local pfx="${cur%.*}." cur_="${cur##*.}"
- __gitcomp "remote merge mergeoptions rebase" "$pfx" "$cur_"
+ local pfx="${cur_%.*}."
+ cur_="${cur_##*.}"
+ __gitcomp "remote pushRemote merge mergeOptions rebase" "$pfx" "$cur_" "$sfx"
return
;;
branch.*)
- local pfx="${cur%.*}." cur_="${cur#*.}"
- __gitcomp_nl "$(__git_heads)" "$pfx" "$cur_" "."
+ local pfx="${cur_%.*}."
+ cur_="${cur_#*.}"
+ __gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")"
+ __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "${sfx- }"
return
;;
guitool.*.*)
- local pfx="${cur%.*}." cur_="${cur##*.}"
+ local pfx="${cur_%.*}."
+ cur_="${cur_##*.}"
__gitcomp "
- argprompt cmd confirm needsfile noconsole norescan
- prompt revprompt revunmerged title
- " "$pfx" "$cur_"
+ argPrompt cmd confirm needsFile noConsole noRescan
+ prompt revPrompt revUnmerged title
+ " "$pfx" "$cur_" "$sfx"
return
;;
difftool.*.*)
- local pfx="${cur%.*}." cur_="${cur##*.}"
- __gitcomp "cmd path" "$pfx" "$cur_"
+ local pfx="${cur_%.*}."
+ cur_="${cur_##*.}"
+ __gitcomp "cmd path" "$pfx" "$cur_" "$sfx"
return
;;
man.*.*)
- local pfx="${cur%.*}." cur_="${cur##*.}"
- __gitcomp "cmd path" "$pfx" "$cur_"
+ local pfx="${cur_%.*}."
+ cur_="${cur_##*.}"
+ __gitcomp "cmd path" "$pfx" "$cur_" "$sfx"
return
;;
mergetool.*.*)
- local pfx="${cur%.*}." cur_="${cur##*.}"
- __gitcomp "cmd path trustExitCode" "$pfx" "$cur_"
+ local pfx="${cur_%.*}."
+ cur_="${cur_##*.}"
+ __gitcomp "cmd path trustExitCode" "$pfx" "$cur_" "$sfx"
return
;;
pager.*)
- local pfx="${cur%.*}." cur_="${cur#*.}"
+ local pfx="${cur_%.*}."
+ cur_="${cur_#*.}"
__git_compute_all_commands
- __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_"
+ __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "${sfx- }"
return
;;
remote.*.*)
- local pfx="${cur%.*}." cur_="${cur##*.}"
+ local pfx="${cur_%.*}."
+ cur_="${cur_##*.}"
__gitcomp "
url proxy fetch push mirror skipDefaultUpdate
- receivepack uploadpack tagopt pushurl
- " "$pfx" "$cur_"
+ receivepack uploadpack tagOpt pushurl
+ " "$pfx" "$cur_" "$sfx"
return
;;
remote.*)
- local pfx="${cur%.*}." cur_="${cur#*.}"
+ local pfx="${cur_%.*}."
+ cur_="${cur_#*.}"
__gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
+ __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "${sfx- }"
return
;;
url.*.*)
- local pfx="${cur%.*}." cur_="${cur##*.}"
- __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_"
- return
- ;;
- esac
- __gitcomp "
- add.ignoreErrors
- advice.commitBeforeMerge
- advice.detachedHead
- advice.implicitIdentity
- advice.pushNonFastForward
- advice.resolveConflict
- advice.statusHints
- alias.
- am.keepcr
- apply.ignorewhitespace
- apply.whitespace
- branch.autosetupmerge
- branch.autosetuprebase
- browser.
- clean.requireForce
- color.branch
- color.branch.current
- color.branch.local
- color.branch.plain
- color.branch.remote
- color.decorate.HEAD
- color.decorate.branch
- color.decorate.remoteBranch
- color.decorate.stash
- color.decorate.tag
- color.diff
- color.diff.commit
- color.diff.frag
- color.diff.func
- color.diff.meta
- color.diff.new
- color.diff.old
- color.diff.plain
- color.diff.whitespace
- color.grep
- color.grep.context
- color.grep.filename
- color.grep.function
- color.grep.linenumber
- color.grep.match
- color.grep.selected
- color.grep.separator
- color.interactive
- color.interactive.error
- color.interactive.header
- color.interactive.help
- color.interactive.prompt
- color.pager
- color.showbranch
- color.status
- color.status.added
- color.status.changed
- color.status.header
- color.status.nobranch
- color.status.untracked
- color.status.updated
- color.ui
- commit.status
- commit.template
- core.abbrev
- core.askpass
- core.attributesfile
- core.autocrlf
- core.bare
- core.bigFileThreshold
- core.compression
- core.createObject
- core.deltaBaseCacheLimit
- core.editor
- core.eol
- core.excludesfile
- core.fileMode
- core.fsyncobjectfiles
- core.gitProxy
- core.ignoreCygwinFSTricks
- core.ignoreStat
- core.ignorecase
- core.logAllRefUpdates
- core.loosecompression
- core.notesRef
- core.packedGitLimit
- core.packedGitWindowSize
- core.pager
- core.preferSymlinkRefs
- core.preloadindex
- core.quotepath
- core.repositoryFormatVersion
- core.safecrlf
- core.sharedRepository
- core.sparseCheckout
- core.symlinks
- core.trustctime
- core.warnAmbiguousRefs
- core.whitespace
- core.worktree
- diff.autorefreshindex
- diff.statGraphWidth
- diff.external
- diff.ignoreSubmodules
- diff.mnemonicprefix
- diff.noprefix
- diff.renameLimit
- diff.renames
- diff.suppressBlankEmpty
- diff.tool
- diff.wordRegex
- difftool.
- difftool.prompt
- fetch.recurseSubmodules
- fetch.unpackLimit
- format.attach
- format.cc
- format.headers
- format.numbered
- format.pretty
- format.signature
- format.signoff
- format.subjectprefix
- format.suffix
- format.thread
- format.to
- gc.
- gc.aggressiveWindow
- gc.auto
- gc.autopacklimit
- gc.packrefs
- gc.pruneexpire
- gc.reflogexpire
- gc.reflogexpireunreachable
- gc.rerereresolved
- gc.rerereunresolved
- gitcvs.allbinary
- gitcvs.commitmsgannotation
- gitcvs.dbTableNamePrefix
- gitcvs.dbdriver
- gitcvs.dbname
- gitcvs.dbpass
- gitcvs.dbuser
- gitcvs.enabled
- gitcvs.logfile
- gitcvs.usecrlfattr
- guitool.
- gui.blamehistoryctx
- gui.commitmsgwidth
- gui.copyblamethreshold
- gui.diffcontext
- gui.encoding
- gui.fastcopyblame
- gui.matchtrackingbranch
- gui.newbranchtemplate
- gui.pruneduringfetch
- gui.spellingdictionary
- gui.trustmtime
- help.autocorrect
- help.browser
- help.format
- http.lowSpeedLimit
- http.lowSpeedTime
- http.maxRequests
- http.minSessions
- http.noEPSV
- http.postBuffer
- http.proxy
- http.sslCAInfo
- http.sslCAPath
- http.sslCert
- http.sslCertPasswordProtected
- http.sslKey
- http.sslVerify
- http.useragent
- i18n.commitEncoding
- i18n.logOutputEncoding
- imap.authMethod
- imap.folder
- imap.host
- imap.pass
- imap.port
- imap.preformattedHTML
- imap.sslverify
- imap.tunnel
- imap.user
- init.templatedir
- instaweb.browser
- instaweb.httpd
- instaweb.local
- instaweb.modulepath
- instaweb.port
- interactive.singlekey
- log.date
- log.decorate
- log.showroot
- mailmap.file
- man.
- man.viewer
- merge.
- merge.conflictstyle
- merge.log
- merge.renameLimit
- merge.renormalize
- merge.stat
- merge.tool
- merge.verbosity
- mergetool.
- mergetool.keepBackup
- mergetool.keepTemporaries
- mergetool.prompt
- notes.displayRef
- notes.rewrite.
- notes.rewrite.amend
- notes.rewrite.rebase
- notes.rewriteMode
- notes.rewriteRef
- pack.compression
- pack.deltaCacheLimit
- pack.deltaCacheSize
- pack.depth
- pack.indexVersion
- pack.packSizeLimit
- pack.threads
- pack.window
- pack.windowMemory
- pager.
- pretty.
- pull.octopus
- pull.twohead
- push.default
- rebase.autosquash
- rebase.stat
- receive.autogc
- receive.denyCurrentBranch
- receive.denyDeleteCurrent
- receive.denyDeletes
- receive.denyNonFastForwards
- receive.fsckObjects
- receive.unpackLimit
- receive.updateserverinfo
- remotes.
- repack.usedeltabaseoffset
- rerere.autoupdate
- rerere.enabled
- sendemail.
- sendemail.aliasesfile
- sendemail.aliasfiletype
- sendemail.bcc
- sendemail.cc
- sendemail.cccmd
- sendemail.chainreplyto
- sendemail.confirm
- sendemail.envelopesender
- sendemail.from
- sendemail.identity
- sendemail.multiedit
- sendemail.signedoffbycc
- sendemail.smtpdomain
- sendemail.smtpencryption
- sendemail.smtppass
- sendemail.smtpserver
- sendemail.smtpserveroption
- sendemail.smtpserverport
- sendemail.smtpuser
- sendemail.suppresscc
- sendemail.suppressfrom
- sendemail.thread
- sendemail.to
- sendemail.validate
- showbranch.default
- status.relativePaths
- status.showUntrackedFiles
- status.submodulesummary
- submodule.
- tar.umask
- transfer.unpackLimit
- url.
- user.email
- user.name
- user.signingkey
- web.browser
- branch. remote.
- "
+ local pfx="${cur_%.*}."
+ cur_="${cur_##*.}"
+ __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_" "$sfx"
+ return
+ ;;
+ *.*)
+ __git_compute_config_vars
+ __gitcomp "$__git_config_vars" "" "$cur_" "$sfx"
+ ;;
+ *)
+ __git_compute_config_sections
+ __gitcomp "$__git_config_sections" "" "$cur_" "."
+ ;;
+ esac
+}
+
+# Completes '='-separated configuration sections/variable names and values
+# for 'git -c section.name=value'.
+#
+# Usage: __git_complete_config_variable_name_and_value [<option>]...
+# --cur=<word>: The current configuration section/variable name/value to be
+# completed. Defaults to the current word to be completed.
+__git_complete_config_variable_name_and_value ()
+{
+ local cur_="$cur"
+
+ while test $# != 0; do
+ case "$1" in
+ --cur=*) cur_="${1##--cur=}" ;;
+ *) return 1 ;;
+ esac
+ shift
+ done
+
+ case "$cur_" in
+ *=*)
+ __git_complete_config_variable_value \
+ --varname="${cur_%%=*}" --cur="${cur_#*=}"
+ ;;
+ *)
+ __git_complete_config_variable_name --cur="$cur_" --sfx='='
+ ;;
+ esac
+}
+
+_git_config ()
+{
+ case "$prev" in
+ --get|--get-all|--unset|--unset-all)
+ __gitcomp_nl "$(__git_config_get_set_variables)"
+ return
+ ;;
+ *.*)
+ __git_complete_config_variable_value
+ return
+ ;;
+ esac
+ case "$cur" in
+ --*)
+ __gitcomp_builtin config
+ ;;
+ *)
+ __git_complete_config_variable_name
+ ;;
+ esac
}
_git_remote ()
{
- local subcommands="add rename remove set-head set-branches set-url show prune update"
+ local subcommands="
+ add rename remove set-head set-branches
+ get-url set-url show prune update
+ "
local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
- __gitcomp "$subcommands"
+ case "$cur" in
+ --*)
+ __gitcomp_builtin remote
+ ;;
+ *)
+ __gitcomp "$subcommands"
+ ;;
+ esac
return
fi
- case "$subcommand" in
- rename|remove|set-url|show|prune)
- __gitcomp_nl "$(__git_remotes)"
+ case "$subcommand,$cur" in
+ add,--*)
+ __gitcomp_builtin remote_add
+ ;;
+ add,*)
;;
- set-head|set-branches)
+ set-head,--*)
+ __gitcomp_builtin remote_set-head
+ ;;
+ set-branches,--*)
+ __gitcomp_builtin remote_set-branches
+ ;;
+ set-head,*|set-branches,*)
__git_complete_remote_or_refspec
;;
- update)
- local i c='' IFS=$'\n'
- for i in $(git --git-dir="$(__gitdir)" config --get-regexp "remotes\..*" 2>/dev/null); do
- i="${i#remotes.}"
- c="$c ${i/ */}"
- done
- __gitcomp "$c"
+ update,--*)
+ __gitcomp_builtin remote_update
+ ;;
+ update,*)
+ __gitcomp "$(__git_remotes) $(__git_get_config_variables "remotes")"
+ ;;
+ set-url,--*)
+ __gitcomp_builtin remote_set-url
+ ;;
+ get-url,--*)
+ __gitcomp_builtin remote_get-url
+ ;;
+ prune,--*)
+ __gitcomp_builtin remote_prune
;;
*)
- COMPREPLY=()
+ __gitcomp_nl "$(__git_remotes)"
;;
esac
}
_git_replace ()
{
- __gitcomp_nl "$(__git_refs)"
+ case "$cur" in
+ --format=*)
+ __gitcomp "short medium long" "" "${cur##--format=}"
+ return
+ ;;
+ --*)
+ __gitcomp_builtin replace
+ return
+ ;;
+ esac
+ __git_complete_refs
+}
+
+_git_rerere ()
+{
+ local subcommands="clear forget diff remaining status gc"
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
+ if test -z "$subcommand"
+ then
+ __gitcomp "$subcommands"
+ return
+ fi
}
_git_reset ()
@@ -2064,35 +2868,65 @@ _git_reset ()
case "$cur" in
--*)
- __gitcomp "--merge --mixed --hard --soft --patch"
+ __gitcomp_builtin reset
return
;;
esac
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
+}
+
+_git_restore ()
+{
+ case "$prev" in
+ -s)
+ __git_complete_refs
+ return
+ ;;
+ esac
+
+ case "$cur" in
+ --conflict=*)
+ __gitcomp "diff3 merge" "" "${cur##--conflict=}"
+ ;;
+ --source=*)
+ __git_complete_refs --cur="${cur##--source=}"
+ ;;
+ --*)
+ __gitcomp_builtin restore
+ ;;
+ esac
}
+__git_revert_inprogress_options=$__git_sequencer_inprogress_options
+
_git_revert ()
{
+ __git_find_repo_path
+ if [ -f "$__git_repo_path"/REVERT_HEAD ]; then
+ __gitcomp "$__git_revert_inprogress_options"
+ return
+ fi
+ __git_complete_strategy && return
case "$cur" in
--*)
- __gitcomp "--edit --mainline --no-edit --no-commit --signoff"
+ __gitcomp_builtin revert "" \
+ "$__git_revert_inprogress_options"
return
;;
esac
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
}
_git_rm ()
{
- __git_has_doubledash && return
-
case "$cur" in
--*)
- __gitcomp "--cached --dry-run --ignore-unmatch --quiet"
+ __gitcomp_builtin rm
return
;;
esac
- COMPREPLY=()
+
+ __git_complete_index_file "--cached"
}
_git_shortlog ()
@@ -2104,7 +2938,7 @@ _git_shortlog ()
__gitcomp "
$__git_log_common_options
$__git_log_shortlog_options
- --numbered --summary
+ --numbered --summary --email
"
return
;;
@@ -2122,81 +2956,122 @@ _git_show ()
" "" "${cur#*=}"
return
;;
+ --diff-algorithm=*)
+ __gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
+ return
+ ;;
+ --submodule=*)
+ __gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
+ return
+ ;;
+ --color-moved=*)
+ __gitcomp "$__git_color_moved_opts" "" "${cur##--color-moved=}"
+ return
+ ;;
+ --color-moved-ws=*)
+ __gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}"
+ return
+ ;;
--*)
- __gitcomp "--pretty= --format= --abbrev-commit --oneline
+ __gitcomp "--pretty= --format= --abbrev-commit --no-abbrev-commit
+ --oneline --show-signature
+ --expand-tabs --expand-tabs= --no-expand-tabs
$__git_diff_common_options
"
return
;;
esac
- __git_complete_file
+ __git_complete_revlist_file
}
_git_show_branch ()
{
case "$cur" in
--*)
- __gitcomp "
- --all --remotes --topo-order --current --more=
- --list --independent --merge-base --no-name
- --color --no-color
- --sha1-name --sparse --topics --reflog
- "
+ __gitcomp_builtin show-branch
return
;;
esac
__git_complete_revlist
}
-_git_stash ()
+_git_sparse_checkout ()
{
- local save_opts='--keep-index --no-keep-index --quiet --patch'
- local subcommands='save list show apply clear drop pop create branch'
+ local subcommands="list init set disable"
local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
- case "$cur" in
- --*)
- __gitcomp "$save_opts"
- ;;
- *)
- if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then
- __gitcomp "$subcommands"
- else
- COMPREPLY=()
- fi
- ;;
- esac
- else
- case "$subcommand,$cur" in
- save,--*)
- __gitcomp "$save_opts"
- ;;
- apply,--*|pop,--*)
- __gitcomp "--index --quiet"
- ;;
- show,--*|drop,--*|branch,--*)
- COMPREPLY=()
+ __gitcomp "$subcommands"
+ return
+ fi
+
+ case "$subcommand,$cur" in
+ init,--*)
+ __gitcomp "--cone"
+ ;;
+ set,--*)
+ __gitcomp "--stdin"
+ ;;
+ *)
+ ;;
+ esac
+}
+
+_git_stash ()
+{
+ local subcommands='push list show apply clear drop pop create branch'
+ local subcommand="$(__git_find_on_cmdline "$subcommands save")"
+
+ if [ -z "$subcommand" ]; then
+ case "$((cword - __git_cmd_idx)),$cur" in
+ *,--*)
+ __gitcomp_builtin stash_push
;;
- show,*|apply,*|drop,*|pop,*|branch,*)
- __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \
- | sed -n -e 's/:.*//p')"
+ 1,sa*)
+ __gitcomp "save"
;;
- *)
- COMPREPLY=()
+ 1,*)
+ __gitcomp "$subcommands"
;;
esac
+ return
fi
+
+ case "$subcommand,$cur" in
+ list,--*)
+ # NEEDSWORK: can we somehow unify this with the options in _git_log() and _git_show()
+ __gitcomp_builtin stash_list "$__git_log_common_options $__git_diff_common_options"
+ ;;
+ show,--*)
+ __gitcomp_builtin stash_show "$__git_diff_common_options"
+ ;;
+ *,--*)
+ __gitcomp_builtin "stash_$subcommand"
+ ;;
+ branch,*)
+ if [ $cword -eq $((__git_cmd_idx+2)) ]; then
+ __git_complete_refs
+ else
+ __gitcomp_nl "$(__git stash list \
+ | sed -n -e 's/:.*//p')"
+ fi
+ ;;
+ show,*|apply,*|drop,*|pop,*)
+ __gitcomp_nl "$(__git stash list \
+ | sed -n -e 's/:.*//p')"
+ ;;
+ esac
}
_git_submodule ()
{
__git_has_doubledash && return
- local subcommands="add status init update summary foreach sync"
- if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
+ local subcommands="add status init deinit update set-branch set-url summary foreach sync absorbgitdirs"
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
+ if [ -z "$subcommand" ]; then
case "$cur" in
--*)
- __gitcomp "--quiet --cached"
+ __gitcomp "--quiet"
;;
*)
__gitcomp "$subcommands"
@@ -2204,6 +3079,36 @@ _git_submodule ()
esac
return
fi
+
+ case "$subcommand,$cur" in
+ add,--*)
+ __gitcomp "--branch --force --name --reference --depth"
+ ;;
+ status,--*)
+ __gitcomp "--cached --recursive"
+ ;;
+ deinit,--*)
+ __gitcomp "--force --all"
+ ;;
+ update,--*)
+ __gitcomp "
+ --init --remote --no-fetch
+ --recommend-shallow --no-recommend-shallow
+ --force --rebase --merge --reference --depth --recursive --jobs
+ "
+ ;;
+ set-branch,--*)
+ __gitcomp "--default --branch"
+ ;;
+ summary,--*)
+ __gitcomp "--cached --files --summary-limit"
+ ;;
+ foreach,--*|sync,--*)
+ __gitcomp "--recursive"
+ ;;
+ *)
+ ;;
+ esac
}
_git_svn ()
@@ -2224,14 +3129,15 @@ _git_svn ()
--no-metadata --use-svm-props --use-svnsync-props
--log-window-size= --no-checkout --quiet
--repack-flags --use-log-author --localtime
- --ignore-paths= $remote_opts
+ --add-author-from
+ --recursive
+ --ignore-paths= --include-paths= $remote_opts
"
local init_opts="
--template= --shared= --trunk= --tags=
--branches= --stdlayout --minimize-url
--no-metadata --use-svm-props --use-svnsync-props
- --rewrite-root= --prefix= --use-log-author
- --add-author-from $remote_opts
+ --rewrite-root= --prefix= $remote_opts
"
local cmt_opts="
--edit --rmdir --find-copies-harder --copy-similarity=
@@ -2299,7 +3205,6 @@ _git_svn ()
__gitcomp "--revision= --parent"
;;
*)
- COMPREPLY=()
;;
esac
fi
@@ -2307,12 +3212,12 @@ _git_svn ()
_git_tag ()
{
- local i c=1 f=0
+ local i c="$__git_cmd_idx" f=0
while [ $c -lt $cword ]; do
i="${words[c]}"
case "$i" in
- -d|-v)
- __gitcomp_nl "$(__git_tags)"
+ -d|--delete|-v|--verify)
+ __gitcomp_direct "$(__git_tags "" "$cur" " ")"
return
;;
-f)
@@ -2324,17 +3229,20 @@ _git_tag ()
case "$prev" in
-m|-F)
- COMPREPLY=()
;;
-*|tag)
if [ $f = 1 ]; then
- __gitcomp_nl "$(__git_tags)"
- else
- COMPREPLY=()
+ __gitcomp_direct "$(__git_tags "" "$cur" " ")"
fi
;;
*)
- __gitcomp_nl "$(__git_refs)"
+ __git_complete_refs
+ ;;
+ esac
+
+ case "$cur" in
+ --*)
+ __gitcomp_builtin tag
;;
esac
}
@@ -2344,26 +3252,197 @@ _git_whatchanged ()
_git_log
}
+__git_complete_worktree_paths ()
+{
+ local IFS=$'\n'
+ # Generate completion reply from worktree list skipping the first
+ # entry: it's the path of the main worktree, which can't be moved,
+ # removed, locked, etc.
+ __gitcomp_nl "$(git worktree list --porcelain |
+ sed -n -e '2,$ s/^worktree //p')"
+}
+
+_git_worktree ()
+{
+ local subcommands="add list lock move prune remove unlock"
+ local subcommand subcommand_idx
+
+ subcommand="$(__git_find_on_cmdline --show-idx "$subcommands")"
+ subcommand_idx="${subcommand% *}"
+ subcommand="${subcommand#* }"
+
+ case "$subcommand,$cur" in
+ ,*)
+ __gitcomp "$subcommands"
+ ;;
+ *,--*)
+ __gitcomp_builtin worktree_$subcommand
+ ;;
+ add,*) # usage: git worktree add [<options>] <path> [<commit-ish>]
+ # Here we are not completing an --option, it's either the
+ # path or a ref.
+ case "$prev" in
+ -b|-B) # Complete refs for branch to be created/reseted.
+ __git_complete_refs
+ ;;
+ -*) # The previous word is an -o|--option without an
+ # unstuck argument: have to complete the path for
+ # the new worktree, so don't list anything, but let
+ # Bash fall back to filename completion.
+ ;;
+ *) # The previous word is not an --option, so it must
+ # be either the 'add' subcommand, the unstuck
+ # argument of an option (e.g. branch for -b|-B), or
+ # the path for the new worktree.
+ if [ $cword -eq $((subcommand_idx+1)) ]; then
+ # Right after the 'add' subcommand: have to
+ # complete the path, so fall back to Bash
+ # filename completion.
+ :
+ else
+ case "${words[cword-2]}" in
+ -b|-B) # After '-b <branch>': have to
+ # complete the path, so fall back
+ # to Bash filename completion.
+ ;;
+ *) # After the path: have to complete
+ # the ref to be checked out.
+ __git_complete_refs
+ ;;
+ esac
+ fi
+ ;;
+ esac
+ ;;
+ lock,*|remove,*|unlock,*)
+ __git_complete_worktree_paths
+ ;;
+ move,*)
+ if [ $cword -eq $((subcommand_idx+1)) ]; then
+ # The first parameter must be an existing working
+ # tree to be moved.
+ __git_complete_worktree_paths
+ else
+ # The second parameter is the destination: it could
+ # be any path, so don't list anything, but let Bash
+ # fall back to filename completion.
+ :
+ fi
+ ;;
+ esac
+}
+
+__git_complete_common () {
+ local command="$1"
+
+ case "$cur" in
+ --*)
+ __gitcomp_builtin "$command"
+ ;;
+ esac
+}
+
+__git_cmds_with_parseopt_helper=
+__git_support_parseopt_helper () {
+ test -n "$__git_cmds_with_parseopt_helper" ||
+ __git_cmds_with_parseopt_helper="$(__git --list-cmds=parseopt)"
+
+ case " $__git_cmds_with_parseopt_helper " in
+ *" $1 "*)
+ return 0
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+}
+
+__git_have_func () {
+ declare -f -- "$1" >/dev/null 2>&1
+}
+
+__git_complete_command () {
+ local command="$1"
+ local completion_func="_git_${command//-/_}"
+ if ! __git_have_func $completion_func &&
+ __git_have_func _completion_loader
+ then
+ _completion_loader "git-$command"
+ fi
+ if __git_have_func $completion_func
+ then
+ $completion_func
+ return 0
+ elif __git_support_parseopt_helper "$command"
+ then
+ __git_complete_common "$command"
+ return 0
+ else
+ return 1
+ fi
+}
+
__git_main ()
{
- local i c=1 command __git_dir
+ local i c=1 command __git_dir __git_repo_path
+ local __git_C_args C_args_count=0
+ local __git_cmd_idx
while [ $c -lt $cword ]; do
i="${words[c]}"
case "$i" in
- --git-dir=*) __git_dir="${i#--git-dir=}" ;;
- --bare) __git_dir="." ;;
- --help) command="help"; break ;;
- -c) c=$((++c)) ;;
- -*) ;;
- *) command="$i"; break ;;
+ --git-dir=*)
+ __git_dir="${i#--git-dir=}"
+ ;;
+ --git-dir)
+ ((c++))
+ __git_dir="${words[c]}"
+ ;;
+ --bare)
+ __git_dir="."
+ ;;
+ --help)
+ command="help"
+ break
+ ;;
+ -c|--work-tree|--namespace)
+ ((c++))
+ ;;
+ -C)
+ __git_C_args[C_args_count++]=-C
+ ((c++))
+ __git_C_args[C_args_count++]="${words[c]}"
+ ;;
+ -*)
+ ;;
+ *)
+ command="$i"
+ __git_cmd_idx="$c"
+ break
+ ;;
esac
((c++))
done
- if [ -z "$command" ]; then
+ if [ -z "${command-}" ]; then
+ case "$prev" in
+ --git-dir|-C|--work-tree)
+ # these need a path argument, let's fall back to
+ # Bash filename completion
+ return
+ ;;
+ -c)
+ __git_complete_config_variable_name_and_value
+ return
+ ;;
+ --namespace)
+ # we don't support completing these options' arguments
+ return
+ ;;
+ esac
case "$cur" in
- --*) __gitcomp "
+ --*)
+ __gitcomp "
--paginate
--no-pager
--git-dir=
@@ -2372,6 +3451,7 @@ __git_main ()
--exec-path
--exec-path=
--html-path
+ --man-path
--info-path
--work-tree=
--namespace=
@@ -2379,19 +3459,24 @@ __git_main ()
--help
"
;;
- *) __git_compute_porcelain_commands
- __gitcomp "$__git_porcelain_commands $(__git_aliases)" ;;
+ *)
+ if test -n "${GIT_TESTING_PORCELAIN_COMMAND_LIST-}"
+ then
+ __gitcomp "$GIT_TESTING_PORCELAIN_COMMAND_LIST"
+ else
+ __gitcomp "$(__git --list-cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config)"
+ fi
+ ;;
esac
return
fi
- local completion_func="_git_${command//-/_}"
- declare -f $completion_func >/dev/null && $completion_func && return
+ __git_complete_command "$command" && return
local expansion=$(__git_aliased_command "$command")
if [ -n "$expansion" ]; then
- completion_func="_git_${expansion//-/_}"
- declare -f $completion_func >/dev/null && $completion_func
+ words[1]=$expansion
+ __git_complete_command "$expansion"
fi
}
@@ -2399,9 +3484,11 @@ __gitk_main ()
{
__git_has_doubledash && return
- local g="$(__gitdir)"
+ local __git_repo_path
+ __git_find_repo_path
+
local merge=""
- if [ -f "$g/MERGE_HEAD" ]; then
+ if [ -f "$__git_repo_path/MERGE_HEAD" ]; then
merge="--merge"
fi
case "$cur" in
@@ -2417,80 +3504,20 @@ __gitk_main ()
__git_complete_revlist
}
-if [[ -n ${ZSH_VERSION-} ]]; then
- echo "WARNING: this script is deprecated, please see git-completion.zsh" 1>&2
-
- autoload -U +X compinit && compinit
-
- __gitcomp ()
- {
- emulate -L zsh
-
- local cur_="${3-$cur}"
-
- case "$cur_" in
- --*=)
- ;;
- *)
- local c IFS=$' \t\n'
- local -a array
- for c in ${=1}; do
- c="$c${4-}"
- case $c in
- --*=*|*.) ;;
- *) c="$c " ;;
- esac
- array[$#array+1]="$c"
- done
- compset -P '*[=:]'
- compadd -Q -S '' -p "${2-}" -a -- array && _ret=0
- ;;
- esac
- }
-
- __gitcomp_nl ()
- {
- emulate -L zsh
-
- local IFS=$'\n'
- compset -P '*[=:]'
- compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
- }
-
- __git_zsh_helper ()
- {
- emulate -L ksh
- local cur cword prev
- cur=${words[CURRENT-1]}
- prev=${words[CURRENT-2]}
- let cword=CURRENT-1
- __${service}_main
- }
-
- _git ()
- {
- emulate -L zsh
- local _ret=1
- __git_zsh_helper
- let _ret && _default -S '' && _ret=0
- return _ret
- }
-
- compdef _git git gitk
+if [[ -n ${ZSH_VERSION-} && -z ${GIT_SOURCING_ZSH_COMPLETION-} ]]; then
+ echo "ERROR: this script is obsolete, please see git-completion.zsh" 1>&2
return
fi
__git_func_wrap ()
{
local cur words cword prev
+ local __git_cmd_idx=0
_get_comp_words_by_ref -n =: cur words cword prev
$1
}
-# Setup completion for certain functions defined above by setting common
-# variables and workarounds.
-# This is NOT a public function; use at your own risk.
-__git_complete ()
+___git_complete ()
{
local wrapper="__git_wrap${2}"
eval "$wrapper () { __git_func_wrap $2 ; }"
@@ -2498,25 +3525,33 @@ __git_complete ()
|| complete -o default -o nospace -F $wrapper $1
}
-# wrapper for backwards compatibility
-_git ()
+# Setup the completion for git commands
+# 1: command or alias
+# 2: function to call (e.g. `git`, `gitk`, `git_fetch`)
+__git_complete ()
{
- __git_wrap__git_main
-}
+ local func
-# wrapper for backwards compatibility
-_gitk ()
-{
- __git_wrap__gitk_main
+ if __git_have_func $2; then
+ func=$2
+ elif __git_have_func __$2_main; then
+ func=__$2_main
+ elif __git_have_func _$2; then
+ func=_$2
+ else
+ echo "ERROR: could not find function '$2'" 1>&2
+ return 1
+ fi
+ ___git_complete $1 $func
}
-__git_complete git __git_main
-__git_complete gitk __gitk_main
+___git_complete git __git_main
+___git_complete gitk __gitk_main
# The following are necessary only for Cygwin, and only are needed
# when the user has tab-completed the executable name and consequently
# included the '.exe' suffix.
#
-if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
-__git_complete git.exe __git_main
+if [ "$OSTYPE" = cygwin ]; then
+ ___git_complete git.exe __git_main
fi
diff --git a/contrib/completion/git-completion.tcsh b/contrib/completion/git-completion.tcsh
index 3e3889f2b4..ba797e5b3c 100644
--- a/contrib/completion/git-completion.tcsh
+++ b/contrib/completion/git-completion.tcsh
@@ -1,5 +1,3 @@
-#!tcsh
-#
# tcsh completion support for core Git.
#
# Copyright (C) 2012 Marc Khouzam <marc.khouzam@gmail.com>
@@ -43,7 +41,7 @@ if ( ! -e ${__git_tcsh_completion_original_script} ) then
exit
endif
-cat << EOF > ${__git_tcsh_completion_script}
+cat << EOF >! ${__git_tcsh_completion_script}
#!bash
#
# This script is GENERATED and will be overwritten automatically.
@@ -52,6 +50,18 @@ cat << EOF > ${__git_tcsh_completion_script}
source ${__git_tcsh_completion_original_script}
+# Remove the colon as a completion separator because tcsh cannot handle it
+COMP_WORDBREAKS=\${COMP_WORDBREAKS//:}
+
+# For file completion, tcsh needs the '/' to be appended to directories.
+# By default, the bash script does not do that.
+# We can achieve this by using the below compatibility
+# method of the git-completion.bash script.
+__git_index_file_list_filter ()
+{
+ __git_index_file_list_filter_compat
+}
+
# Set COMP_WORDS in a way that can be handled by the bash script.
COMP_WORDS=(\$2)
@@ -70,8 +80,9 @@ else
COMP_CWORD=\$((\${#COMP_WORDS[@]}-1))
fi
-# Call _git() or _gitk() of the bash script, based on the first argument
-_\${1}
+# Call __git_wrap__git_main() or __git_wrap__gitk_main() of the bash script,
+# based on the first argument
+__git_wrap__\${1}_main
IFS=\$'\n'
if [ \${#COMPREPLY[*]} -eq 0 ]; then
diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh
index 45775021ff..cac6f61881 100644
--- a/contrib/completion/git-completion.zsh
+++ b/contrib/completion/git-completion.zsh
@@ -2,28 +2,51 @@
# zsh completion wrapper for git
#
-# You need git's bash completion script installed somewhere, by default on the
-# same directory as this script.
+# Copyright (c) 2012-2020 Felipe Contreras <felipe.contreras@gmail.com>
#
-# If your script is on ~/.git-completion.sh instead, you can configure it on
-# your ~/.zshrc:
+# The recommended way to install this script is to make a copy of it as a
+# file named '_git' inside any directory in your fpath.
#
-# zstyle ':completion:*:*:git:*' script ~/.git-completion.sh
+# For example, create a directory '~/.zsh/', copy this file to '~/.zsh/_git',
+# and then add the following to your ~/.zshrc file:
#
-# The recommended way to install this script is to copy to
-# '~/.zsh/completion/_git', and then add the following to your ~/.zshrc file:
+# fpath=(~/.zsh $fpath)
+#
+# You need git's bash completion script installed. By default bash-completion's
+# location will be used (e.g. pkg-config --variable=completionsdir bash-completion).
+#
+# If your bash completion script is somewhere else, you can specify the
+# location in your ~/.zshrc:
+#
+# zstyle ':completion:*:*:git:*' script ~/.git-completion.bash
#
-# fpath=(~/.zsh/completion $fpath)
-complete ()
-{
- # do nothing
- return 0
-}
+zstyle -T ':completion:*:*:git:*' tag-order && \
+ zstyle ':completion:*:*:git:*' tag-order 'common-commands'
zstyle -s ":completion:*:*:git:*" script script
-test -z "$script" && script="$(dirname ${funcsourcetrace[1]%:*})"/git-completion.bash
-ZSH_VERSION='' . "$script"
+if [ -z "$script" ]; then
+ local -a locations
+ local e bash_completion
+
+ bash_completion=$(pkg-config --variable=completionsdir bash-completion 2>/dev/null) ||
+ bash_completion='/usr/share/bash-completion/completions/'
+
+ locations=(
+ "$(dirname ${funcsourcetrace[1]%:*})"/git-completion.bash
+ "$HOME/.local/share/bash-completion/completions/git"
+ "$bash_completion/git"
+ '/etc/bash_completion.d/git' # old debian
+ )
+ for e in $locations; do
+ test -f $e && script="$e" && break
+ done
+fi
+
+local old_complete="$functions[complete]"
+functions[complete]=:
+GIT_SOURCING_ZSH_COMPLETION=y . "$script"
+functions[complete]="$old_complete"
__gitcomp ()
{
@@ -34,13 +57,35 @@ __gitcomp ()
case "$cur_" in
--*=)
;;
+ --no-*)
+ local c IFS=$' \t\n'
+ local -a array
+ for c in ${=1}; do
+ if [[ $c == "--" ]]; then
+ continue
+ fi
+ c="$c${4-}"
+ case $c in
+ --*=|*.) ;;
+ *) c="$c " ;;
+ esac
+ array+=("$c")
+ done
+ compset -P '*[=:]'
+ compadd -Q -S '' -p "${2-}" -a -- array && _ret=0
+ ;;
*)
local c IFS=$' \t\n'
local -a array
for c in ${=1}; do
+ if [[ $c == "--" ]]; then
+ c="--no-...${4-}"
+ array+=("$c ")
+ break
+ fi
c="$c${4-}"
case $c in
- --*=*|*.) ;;
+ --*=|*.) ;;
*) c="$c " ;;
esac
array+=("$c")
@@ -51,27 +96,198 @@ __gitcomp ()
esac
}
+__gitcomp_direct ()
+{
+ emulate -L zsh
+
+ compset -P '*[=:]'
+ compadd -Q -S '' -- ${(f)1} && _ret=0
+}
+
__gitcomp_nl ()
{
emulate -L zsh
- local IFS=$'\n'
compset -P '*[=:]'
- compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
+ compadd -Q -S "${4- }" -p "${2-}" -- ${(f)1} && _ret=0
+}
+
+__gitcomp_file ()
+{
+ emulate -L zsh
+
+ compset -P '*[=:]'
+ compadd -f -p "${2-}" -- ${(f)1} && _ret=0
+}
+
+__gitcomp_direct_append ()
+{
+ __gitcomp_direct "$@"
+}
+
+__gitcomp_nl_append ()
+{
+ __gitcomp_nl "$@"
+}
+
+__gitcomp_file_direct ()
+{
+ __gitcomp_file "$1" ""
+}
+
+_git_zsh ()
+{
+ __gitcomp "v1.1"
+}
+
+__git_complete_command ()
+{
+ emulate -L zsh
+
+ local command="$1"
+ local completion_func="_git_${command//-/_}"
+ if (( $+functions[$completion_func] )); then
+ emulate ksh -c $completion_func
+ return 0
+ else
+ return 1
+ fi
+}
+
+__git_zsh_bash_func ()
+{
+ emulate -L ksh
+
+ local command=$1
+
+ __git_complete_command "$command" && return
+
+ local expansion=$(__git_aliased_command "$command")
+ if [ -n "$expansion" ]; then
+ words[1]=$expansion
+ __git_complete_command "$expansion"
+ fi
+}
+
+__git_zsh_cmd_common ()
+{
+ local -a list
+ list=(
+ add:'add file contents to the index'
+ bisect:'find by binary search the change that introduced a bug'
+ branch:'list, create, or delete branches'
+ checkout:'checkout a branch or paths to the working tree'
+ clone:'clone a repository into a new directory'
+ commit:'record changes to the repository'
+ diff:'show changes between commits, commit and working tree, etc'
+ fetch:'download objects and refs from another repository'
+ grep:'print lines matching a pattern'
+ init:'create an empty Git repository or reinitialize an existing one'
+ log:'show commit logs'
+ merge:'join two or more development histories together'
+ mv:'move or rename a file, a directory, or a symlink'
+ pull:'fetch from and merge with another repository or a local branch'
+ push:'update remote refs along with associated objects'
+ rebase:'forward-port local commits to the updated upstream head'
+ reset:'reset current HEAD to the specified state'
+ restore:'restore working tree files'
+ rm:'remove files from the working tree and from the index'
+ show:'show various types of objects'
+ status:'show the working tree status'
+ switch:'switch branches'
+ tag:'create, list, delete or verify a tag object signed with GPG')
+ _describe -t common-commands 'common commands' list && _ret=0
+}
+
+__git_zsh_cmd_alias ()
+{
+ local -a list
+ list=(${${(0)"$(git config -z --get-regexp '^alias\.*')"}#alias.})
+ list=(${(f)"$(printf "%s:alias for '%s'\n" ${(f@)list})"})
+ _describe -t alias-commands 'aliases' list && _ret=0
+}
+
+__git_zsh_cmd_all ()
+{
+ local -a list
+ emulate ksh -c __git_compute_all_commands
+ list=( ${=__git_all_commands} )
+ _describe -t all-commands 'all commands' list && _ret=0
+}
+
+__git_zsh_main ()
+{
+ local curcontext="$curcontext" state state_descr line
+ typeset -A opt_args
+ local -a orig_words
+
+ orig_words=( ${words[@]} )
+
+ _arguments -C \
+ '(-p --paginate --no-pager)'{-p,--paginate}'[pipe all output into ''less'']' \
+ '(-p --paginate)--no-pager[do not pipe git output into a pager]' \
+ '--git-dir=-[set the path to the repository]: :_directories' \
+ '--bare[treat the repository as a bare repository]' \
+ '(- :)--version[prints the git suite version]' \
+ '--exec-path=-[path to where your core git programs are installed]:: :_directories' \
+ '--html-path[print the path where git''s HTML documentation is installed]' \
+ '--info-path[print the path where the Info files are installed]' \
+ '--man-path[print the manpath (see `man(1)`) for the man pages]' \
+ '--work-tree=-[set the path to the working tree]: :_directories' \
+ '--namespace=-[set the git namespace]' \
+ '--no-replace-objects[do not use replacement refs to replace git objects]' \
+ '(- :)--help[prints the synopsis and a list of the most commonly used commands]: :->arg' \
+ '(-): :->command' \
+ '(-)*:: :->arg' && return
+
+ case $state in
+ (command)
+ _tags common-commands alias-commands all-commands
+ while _tags; do
+ _requested common-commands && __git_zsh_cmd_common
+ _requested alias-commands && __git_zsh_cmd_alias
+ _requested all-commands && __git_zsh_cmd_all
+ let _ret || break
+ done
+ ;;
+ (arg)
+ local command="${words[1]}" __git_dir __git_cmd_idx=1
+
+ if (( $+opt_args[--bare] )); then
+ __git_dir='.'
+ else
+ __git_dir=${opt_args[--git-dir]}
+ fi
+
+ (( $+opt_args[--help] )) && command='help'
+
+ words=( ${orig_words[@]} )
+
+ __git_zsh_bash_func $command
+ ;;
+ esac
}
_git ()
{
local _ret=1
- () {
- emulate -L ksh
- local cur cword prev
- cur=${words[CURRENT-1]}
- prev=${words[CURRENT-2]}
- let cword=CURRENT-1
- __${service}_main
- }
- let _ret && _default -S '' && _ret=0
+ local cur cword prev
+
+ cur=${words[CURRENT]}
+ prev=${words[CURRENT-1]}
+ let cword=CURRENT-1
+
+ if (( $+functions[__${service}_zsh_main] )); then
+ __${service}_zsh_main
+ elif (( $+functions[__${service}_main] )); then
+ emulate ksh -c __${service}_main
+ elif (( $+functions[_${service}] )); then
+ emulate ksh -c _${service}
+ elif (( $+functions[_${service//-/_}] )); then
+ emulate ksh -c _${service//-/_}
+ fi
+
+ let _ret && _default && _ret=0
return _ret
}
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 9bef0531c5..db7c0068fb 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -3,7 +3,7 @@
# Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org>
# Distributed under the GNU General Public License, version 2.0.
#
-# This script allows you to see the current branch in your prompt.
+# This script allows you to see repository status in your prompt.
#
# To enable:
#
@@ -13,23 +13,27 @@
# 3a) Change your PS1 to call __git_ps1 as
# command-substitution:
# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
-# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
+# ZSH: setopt PROMPT_SUBST ; PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
# the optional argument will be used as format string.
-# 3b) Alternatively, if you are using bash, __git_ps1 can be
-# used for PROMPT_COMMAND with two parameters, <pre> and
-# <post>, which are strings you would put in $PS1 before
-# and after the status string generated by the git-prompt
-# machinery. e.g.
-# PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'
-# will show username, at-sign, host, colon, cwd, then
-# various status string, followed by dollar and SP, as
-# your prompt.
+# 3b) Alternatively, for a slightly faster prompt, __git_ps1 can
+# be used for PROMPT_COMMAND in Bash or for precmd() in Zsh
+# with two parameters, <pre> and <post>, which are strings
+# you would put in $PS1 before and after the status string
+# generated by the git-prompt machinery. e.g.
+# Bash: PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'
+# will show username, at-sign, host, colon, cwd, then
+# various status string, followed by dollar and SP, as
+# your prompt.
+# ZSH: precmd () { __git_ps1 "%n" ":%~$ " "|%s" }
+# will show username, pipe, then various status string,
+# followed by colon, cwd, dollar and SP, as your prompt.
# Optionally, you can supply a third argument with a printf
# format string to finetune the output of the branch status
#
-# The argument to __git_ps1 will be displayed only if you are currently
-# in a git repository. The %s token will be the name of the current
-# branch.
+# The repository status will be displayed only if you are currently in a
+# git repository. The %s token is the placeholder for the shown status.
+#
+# The prompt status always includes the current branch name.
#
# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value,
# unstaged (*) and staged (+) changes will be shown next to the branch
@@ -43,7 +47,10 @@
#
# If you would like to see if there're untracked files, then you can set
# GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're untracked
-# files, then a '%' will be shown next to the branch name.
+# files, then a '%' will be shown next to the branch name. You can
+# configure this per-repository with the bash.showUntrackedFiles
+# variable, which defaults to true once GIT_PS1_SHOWUNTRACKEDFILES is
+# enabled.
#
# If you would like to see the difference between HEAD and its upstream,
# set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates you are behind, ">"
@@ -53,11 +60,25 @@
# of values:
#
# verbose show number of commits ahead/behind (+/-) upstream
+# name if verbose, then also show the upstream abbrev name
# legacy don't use the '--count' option available in recent
# versions of git-rev-list
# git always compare HEAD to @{upstream}
# svn always compare HEAD to your SVN upstream
#
+# You can change the separator between the branch name and the above
+# state symbols by setting GIT_PS1_STATESEPARATOR. The default separator
+# is SP.
+#
+# When there is an in-progress operation such as a merge, rebase,
+# revert, cherry-pick, or bisect, the prompt will include information
+# related to the operation, often in the form "|<OPERATION-NAME>".
+#
+# When the repository has a sparse-checkout, a notification of the form
+# "|SPARSE" will be included in the prompt. This can be shortened to a
+# single '?' character by setting GIT_PS1_COMPRESSSPARSESTATE, or omitted
+# by setting GIT_PS1_OMITSPARSESTATE.
+#
# 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
@@ -70,35 +91,23 @@
# contains relative to newer annotated tag (v1.6.3.2~35)
# branch relative to newer tag or branch (master~4)
# describe relative to older annotated tag (v1.6.3.1-13-gdd42c2f)
+# tag relative to any older tag (v1.6.3.1-13-gdd42c2f)
# default exactly matching tag
#
# If you would like a colored hint about the current dirty state, set
# GIT_PS1_SHOWCOLORHINTS to a nonempty value. The colors are based on
-# the colored output of "git status -sb".
+# the colored output of "git status -sb" and are available only when
+# using __git_ps1 for PROMPT_COMMAND or precmd in Bash,
+# but always available in Zsh.
+#
+# If you would like __git_ps1 to do nothing in the case when the current
+# directory is set up to be ignored by git, then set
+# GIT_PS1_HIDE_IF_PWD_IGNORED to a nonempty value. Override this on the
+# repository level by setting bash.hideIfPwdIgnored to "false".
-# __gitdir accepts 0 or 1 arguments (i.e., location)
-# returns location of .git repo
-__gitdir ()
-{
- # Note: this function is duplicated in git-completion.bash
- # When updating it, make sure you update the other one to match.
- if [ -z "${1-}" ]; then
- if [ -n "${__git_dir-}" ]; then
- echo "$__git_dir"
- elif [ -n "${GIT_DIR-}" ]; then
- test -d "${GIT_DIR-}" || return 1
- echo "$GIT_DIR"
- elif [ -d .git ]; then
- echo .git
- else
- git rev-parse --git-dir 2>/dev/null
- fi
- elif [ -d "$1/.git" ]; then
- echo "$1/.git"
- else
- echo "$1"
- fi
-}
+# check whether printf supports -v
+__git_printf_supports_v=
+printf -v __git_printf_supports_v -- '%s' yes >/dev/null 2>&1
# stores the divergence from upstream in $p
# used by GIT_PS1_SHOWUPSTREAM
@@ -106,7 +115,7 @@ __git_ps1_show_upstream ()
{
local key value
local svn_remote svn_url_pattern count n
- local upstream=git legacy="" verbose=""
+ local upstream=git legacy="" verbose="" name=""
svn_remote=()
# get some config options from git-config
@@ -121,19 +130,21 @@ __git_ps1_show_upstream ()
fi
;;
svn-remote.*.url)
- svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
- svn_url_pattern+="\\|$value"
+ svn_remote[$((${#svn_remote[@]} + 1))]="$value"
+ svn_url_pattern="$svn_url_pattern\\|$value"
upstream=svn+git # default upstream is SVN if available, else git
;;
esac
done <<< "$output"
# parse configuration values
+ local option
for option in ${GIT_PS1_SHOWUPSTREAM}; do
case "$option" in
git|svn) upstream="$option" ;;
verbose) verbose=1 ;;
legacy) legacy=1 ;;
+ name) name=1 ;;
esac
done
@@ -143,10 +154,11 @@ __git_ps1_show_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 \
+ local -a svn_upstream
+ svn_upstream=($(git log --first-parent -1 \
--grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
if [[ 0 -ne ${#svn_upstream[@]} ]]; then
- svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
+ svn_upstream=${svn_upstream[${#svn_upstream[@]} - 2]}
svn_upstream=${svn_upstream%@*}
local n_stop="${#svn_remote[@]}"
for ((n=1; n <= n_stop; n++)); do
@@ -215,10 +227,106 @@ __git_ps1_show_upstream ()
*) # diverged from upstream
p=" u+${count#* }-${count% *}" ;;
esac
+ if [[ -n "$count" && -n "$name" ]]; then
+ __git_ps1_upstream_name=$(git rev-parse \
+ --abbrev-ref "$upstream" 2>/dev/null)
+ if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
+ p="$p \${__git_ps1_upstream_name}"
+ else
+ p="$p ${__git_ps1_upstream_name}"
+ # not needed anymore; keep user's
+ # environment clean
+ unset __git_ps1_upstream_name
+ fi
+ fi
fi
}
+# Helper function that is meant to be called from __git_ps1. It
+# injects color codes into the appropriate gitstring variables used
+# to build a gitstring.
+__git_ps1_colorize_gitstring ()
+{
+ if [[ -n ${ZSH_VERSION-} ]]; then
+ local c_red='%F{red}'
+ local c_green='%F{green}'
+ local c_lblue='%F{blue}'
+ local c_clear='%f'
+ else
+ # Using \[ and \] around colors is necessary to prevent
+ # issues with command line editing/browsing/completion!
+ local c_red='\[\e[31m\]'
+ local c_green='\[\e[32m\]'
+ local c_lblue='\[\e[1;34m\]'
+ local c_clear='\[\e[0m\]'
+ fi
+ local bad_color=$c_red
+ local ok_color=$c_green
+ local flags_color="$c_lblue"
+
+ local branch_color=""
+ if [ $detached = no ]; then
+ branch_color="$ok_color"
+ else
+ branch_color="$bad_color"
+ fi
+ c="$branch_color$c"
+
+ z="$c_clear$z"
+ if [ "$w" = "*" ]; then
+ w="$bad_color$w"
+ fi
+ if [ -n "$i" ]; then
+ i="$ok_color$i"
+ fi
+ if [ -n "$s" ]; then
+ s="$flags_color$s"
+ fi
+ if [ -n "$u" ]; then
+ u="$bad_color$u"
+ fi
+ r="$c_clear$r"
+}
+
+# Helper function to read the first line of a file into a variable.
+# __git_eread requires 2 arguments, the file path and the name of the
+# variable, in that order.
+__git_eread ()
+{
+ test -r "$1" && IFS=$'\r\n' read "$2" <"$1"
+}
+
+# see if a cherry-pick or revert is in progress, if the user has committed a
+# conflict resolution with 'git commit' in the middle of a sequence of picks or
+# reverts then CHERRY_PICK_HEAD/REVERT_HEAD will not exist so we have to read
+# the todo file.
+__git_sequencer_status ()
+{
+ local todo
+ if test -f "$g/CHERRY_PICK_HEAD"
+ then
+ r="|CHERRY-PICKING"
+ return 0;
+ elif test -f "$g/REVERT_HEAD"
+ then
+ r="|REVERTING"
+ return 0;
+ elif __git_eread "$g/sequencer/todo" todo
+ then
+ case "$todo" in
+ p[\ \ ]|pick[\ \ ]*)
+ r="|CHERRY-PICKING"
+ return 0
+ ;;
+ revert[\ \ ]*)
+ r="|REVERTING"
+ return 0
+ ;;
+ esac
+ fi
+ return 1
+}
# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
# when called from PS1 using command substitution
@@ -233,6 +341,8 @@ __git_ps1_show_upstream ()
# In this mode you can request colored hints using GIT_PS1_SHOWCOLORHINTS=true
__git_ps1 ()
{
+ # preserve exit status
+ local exit=$?
local pcmode=no
local detached=no
local ps1pc_start='\u@\h:\w '
@@ -244,46 +354,133 @@ __git_ps1 ()
ps1pc_start="$1"
ps1pc_end="$2"
printf_format="${3:-$printf_format}"
+ # set PS1 to a plain prompt so that we can
+ # simply return early if the prompt should not
+ # be decorated
+ PS1="$ps1pc_start$ps1pc_end"
;;
0|1) printf_format="${1:-$printf_format}"
;;
- *) return
+ *) return $exit
;;
esac
- local g="$(__gitdir)"
- if [ -z "$g" ]; then
- if [ $pcmode = yes ]; then
- #In PC mode PS1 always needs to be set
- PS1="$ps1pc_start$ps1pc_end"
- fi
+ # ps1_expanded: This variable is set to 'yes' if the shell
+ # subjects the value of PS1 to parameter expansion:
+ #
+ # * bash does unless the promptvars option is disabled
+ # * zsh does not unless the PROMPT_SUBST option is set
+ # * POSIX shells always do
+ #
+ # If the shell would expand the contents of PS1 when drawing
+ # the prompt, a raw ref name must not be included in PS1.
+ # This protects the user from arbitrary code execution via
+ # specially crafted ref names. For example, a ref named
+ # 'refs/heads/$(IFS=_;cmd=sudo_rm_-rf_/;$cmd)' might cause the
+ # shell to execute 'sudo rm -rf /' when the prompt is drawn.
+ #
+ # Instead, the ref name should be placed in a separate global
+ # variable (in the __git_ps1_* namespace to avoid colliding
+ # with the user's environment) and that variable should be
+ # referenced from PS1. For example:
+ #
+ # __git_ps1_foo=$(do_something_to_get_ref_name)
+ # PS1="...stuff...\${__git_ps1_foo}...stuff..."
+ #
+ # If the shell does not expand the contents of PS1, the raw
+ # ref name must be included in PS1.
+ #
+ # The value of this variable is only relevant when in pcmode.
+ #
+ # Assume that the shell follows the POSIX specification and
+ # expands PS1 unless determined otherwise. (This is more
+ # likely to be correct if the user has a non-bash, non-zsh
+ # shell and safer than the alternative if the assumption is
+ # incorrect.)
+ #
+ local ps1_expanded=yes
+ [ -z "${ZSH_VERSION-}" ] || [[ -o PROMPT_SUBST ]] || ps1_expanded=no
+ [ -z "${BASH_VERSION-}" ] || shopt -q promptvars || ps1_expanded=no
+
+ local repo_info rev_parse_exit_code
+ repo_info="$(git rev-parse --git-dir --is-inside-git-dir \
+ --is-bare-repository --is-inside-work-tree \
+ --short HEAD 2>/dev/null)"
+ rev_parse_exit_code="$?"
+
+ if [ -z "$repo_info" ]; then
+ return $exit
+ fi
+
+ local short_sha=""
+ if [ "$rev_parse_exit_code" = "0" ]; then
+ short_sha="${repo_info##*$'\n'}"
+ repo_info="${repo_info%$'\n'*}"
+ fi
+ local inside_worktree="${repo_info##*$'\n'}"
+ repo_info="${repo_info%$'\n'*}"
+ local bare_repo="${repo_info##*$'\n'}"
+ repo_info="${repo_info%$'\n'*}"
+ local inside_gitdir="${repo_info##*$'\n'}"
+ local g="${repo_info%$'\n'*}"
+
+ if [ "true" = "$inside_worktree" ] &&
+ [ -n "${GIT_PS1_HIDE_IF_PWD_IGNORED-}" ] &&
+ [ "$(git config --bool bash.hideIfPwdIgnored)" != "false" ] &&
+ git check-ignore -q .
+ then
+ return $exit
+ fi
+
+ local sparse=""
+ if [ -z "${GIT_PS1_COMPRESSSPARSESTATE-}" ] &&
+ [ -z "${GIT_PS1_OMITSPARSESTATE-}" ] &&
+ [ "$(git config --bool core.sparseCheckout)" = "true" ]; then
+ sparse="|SPARSE"
+ fi
+
+ local r=""
+ local b=""
+ local step=""
+ local total=""
+ if [ -d "$g/rebase-merge" ]; then
+ __git_eread "$g/rebase-merge/head-name" b
+ __git_eread "$g/rebase-merge/msgnum" step
+ __git_eread "$g/rebase-merge/end" total
+ r="|REBASE"
else
- local r=""
- local b=""
- if [ -f "$g/rebase-merge/interactive" ]; then
- r="|REBASE-i"
- b="$(cat "$g/rebase-merge/head-name")"
- elif [ -d "$g/rebase-merge" ]; then
- r="|REBASE-m"
- b="$(cat "$g/rebase-merge/head-name")"
- else
- if [ -d "$g/rebase-apply" ]; then
- if [ -f "$g/rebase-apply/rebasing" ]; then
- r="|REBASE"
- elif [ -f "$g/rebase-apply/applying" ]; then
- r="|AM"
- else
- r="|AM/REBASE"
- fi
- elif [ -f "$g/MERGE_HEAD" ]; then
- r="|MERGING"
- elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
- r="|CHERRY-PICKING"
- elif [ -f "$g/BISECT_LOG" ]; then
- r="|BISECTING"
+ if [ -d "$g/rebase-apply" ]; then
+ __git_eread "$g/rebase-apply/next" step
+ __git_eread "$g/rebase-apply/last" total
+ if [ -f "$g/rebase-apply/rebasing" ]; then
+ __git_eread "$g/rebase-apply/head-name" b
+ r="|REBASE"
+ elif [ -f "$g/rebase-apply/applying" ]; then
+ r="|AM"
+ else
+ r="|AM/REBASE"
fi
+ elif [ -f "$g/MERGE_HEAD" ]; then
+ r="|MERGING"
+ elif __git_sequencer_status; then
+ :
+ elif [ -f "$g/BISECT_LOG" ]; then
+ r="|BISECTING"
+ fi
- b="$(git symbolic-ref HEAD 2>/dev/null)" || {
+ if [ -n "$b" ]; then
+ :
+ elif [ -h "$g/HEAD" ]; then
+ # symlink symbolic ref
+ b="$(git symbolic-ref HEAD 2>/dev/null)"
+ else
+ local head=""
+ if ! __git_eread "$g/HEAD" head; then
+ return $exit
+ fi
+ # is it a symbolic ref?
+ b="${head#ref: }"
+ if [ "$head" = "$b" ]; then
detached=yes
b="$(
case "${GIT_PS1_DESCRIBE_STYLE-}" in
@@ -291,105 +488,99 @@ __git_ps1 ()
git describe --contains HEAD ;;
(branch)
git describe --contains --all HEAD ;;
+ (tag)
+ git describe --tags HEAD ;;
(describe)
git describe HEAD ;;
(* | default)
git describe --tags --exact-match HEAD ;;
esac 2>/dev/null)" ||
- b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
- b="unknown"
+ b="$short_sha..."
b="($b)"
- }
+ fi
fi
+ fi
- local w=""
- local i=""
- local s=""
- local u=""
- local c=""
- local p=""
+ if [ -n "$step" ] && [ -n "$total" ]; then
+ r="$r $step/$total"
+ fi
- if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
- if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
- c="BARE:"
- else
- b="GIT_DIR!"
- fi
- elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
- if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
- if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
- git diff --no-ext-diff --quiet --exit-code || w="*"
- if git rev-parse --quiet --verify HEAD >/dev/null; then
- git diff-index --cached --quiet HEAD -- || i="+"
- else
- i="#"
- fi
- fi
- fi
- if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
- git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
- fi
+ local w=""
+ local i=""
+ local s=""
+ local u=""
+ local h=""
+ local c=""
+ local p=""
- if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
- if [ -n "$(git ls-files --others --exclude-standard)" ]; then
- u="%"
- fi
+ if [ "true" = "$inside_gitdir" ]; then
+ if [ "true" = "$bare_repo" ]; then
+ c="BARE:"
+ else
+ b="GIT_DIR!"
+ fi
+ elif [ "true" = "$inside_worktree" ]; then
+ if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] &&
+ [ "$(git config --bool bash.showDirtyState)" != "false" ]
+ then
+ git diff --no-ext-diff --quiet || w="*"
+ git diff --no-ext-diff --cached --quiet || i="+"
+ if [ -z "$short_sha" ] && [ -z "$i" ]; then
+ i="#"
fi
+ fi
+ if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ] &&
+ git rev-parse --verify --quiet refs/stash >/dev/null
+ then
+ s="$"
+ fi
- if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
- __git_ps1_show_upstream
- fi
+ if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] &&
+ [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] &&
+ git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*' >/dev/null 2>/dev/null
+ then
+ u="%${ZSH_VERSION+%}"
fi
- local f="$w$i$s$u"
- if [ $pcmode = yes ]; then
- local gitstring=
- if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
- local c_red='\e[31m'
- local c_green='\e[32m'
- local c_lblue='\e[1;34m'
- local c_clear='\e[0m'
- local bad_color=$c_red
- local ok_color=$c_green
- local branch_color="$c_clear"
- local flags_color="$c_lblue"
- local branchstring="$c${b##refs/heads/}"
-
- if [ $detached = no ]; then
- branch_color="$ok_color"
- else
- branch_color="$bad_color"
- fi
-
- # Setting gitstring directly with \[ and \] around colors
- # is necessary to prevent wrapping issues!
- gitstring="\[$branch_color\]$branchstring\[$c_clear\]"
-
- if [ -n "$w$i$s$u$r$p" ]; then
- gitstring="$gitstring "
- fi
- if [ "$w" = "*" ]; then
- gitstring="$gitstring\[$bad_color\]$w"
- fi
- if [ -n "$i" ]; then
- gitstring="$gitstring\[$ok_color\]$i"
- fi
- if [ -n "$s" ]; then
- gitstring="$gitstring\[$flags_color\]$s"
- fi
- if [ -n "$u" ]; then
- gitstring="$gitstring\[$bad_color\]$u"
- fi
- gitstring="$gitstring\[$c_clear\]$r$p"
- else
- gitstring="$c${b##refs/heads/}${f:+ $f}$r$p"
- fi
+ if [ -n "${GIT_PS1_COMPRESSSPARSESTATE-}" ] &&
+ [ "$(git config --bool core.sparseCheckout)" = "true" ]; then
+ h="?"
+ fi
+
+ if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+ __git_ps1_show_upstream
+ fi
+ fi
+
+ local z="${GIT_PS1_STATESEPARATOR-" "}"
+
+ # NO color option unless in PROMPT_COMMAND mode or it's Zsh
+ if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
+ if [ $pcmode = yes ] || [ -n "${ZSH_VERSION-}" ]; then
+ __git_ps1_colorize_gitstring
+ fi
+ fi
+
+ b=${b##refs/heads/}
+ if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
+ __git_ps1_branch_name=$b
+ b="\${__git_ps1_branch_name}"
+ fi
+
+ local f="$h$w$i$s$u"
+ local gitstring="$c$b${f:+$z$f}${sparse}$r$p"
+
+ if [ $pcmode = yes ]; then
+ if [ "${__git_printf_supports_v-}" != yes ]; then
gitstring=$(printf -- "$printf_format" "$gitstring")
- PS1="$ps1pc_start$gitstring$ps1pc_end"
else
- # NO color option unless in PROMPT_COMMAND mode
- printf -- "$printf_format" "$c${b##refs/heads/}${f:+ $f}$r$p"
+ printf -v gitstring -- "$printf_format" "$gitstring"
fi
+ PS1="$ps1pc_start$gitstring$ps1pc_end"
+ else
+ printf -- "$printf_format" "$gitstring"
fi
+
+ return $exit
}
diff --git a/contrib/contacts/.gitignore b/contrib/contacts/.gitignore
new file mode 100644
index 0000000000..f385ee643c
--- /dev/null
+++ b/contrib/contacts/.gitignore
@@ -0,0 +1,3 @@
+git-contacts.1
+git-contacts.html
+git-contacts.xml
diff --git a/contrib/contacts/Makefile b/contrib/contacts/Makefile
new file mode 100644
index 0000000000..a2990f0dcb
--- /dev/null
+++ b/contrib/contacts/Makefile
@@ -0,0 +1,71 @@
+# The default target of this Makefile is...
+all::
+
+-include ../../config.mak.autogen
+-include ../../config.mak
+
+prefix ?= /usr/local
+gitexecdir ?= $(prefix)/libexec/git-core
+mandir ?= $(prefix)/share/man
+man1dir ?= $(mandir)/man1
+htmldir ?= $(prefix)/share/doc/git-doc
+
+../../GIT-VERSION-FILE: FORCE
+ $(MAKE) -C ../../ GIT-VERSION-FILE
+
+-include ../../GIT-VERSION-FILE
+
+# this should be set to a 'standard' bsd-type install program
+INSTALL ?= install
+RM ?= rm -f
+
+ASCIIDOC = asciidoc
+XMLTO = xmlto
+
+ifndef SHELL_PATH
+ SHELL_PATH = /bin/sh
+endif
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+
+ASCIIDOC_CONF = ../../Documentation/asciidoc.conf
+MANPAGE_XSL = ../../Documentation/manpage-normal.xsl
+
+GIT_CONTACTS := git-contacts
+
+GIT_CONTACTS_DOC := git-contacts.1
+GIT_CONTACTS_XML := git-contacts.xml
+GIT_CONTACTS_TXT := git-contacts.txt
+GIT_CONTACTS_HTML := git-contacts.html
+
+doc: $(GIT_CONTACTS_DOC) $(GIT_CONTACTS_HTML)
+
+install: $(GIT_CONTACTS)
+ $(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir)
+ $(INSTALL) -m 755 $(GIT_CONTACTS) $(DESTDIR)$(gitexecdir)
+
+install-doc: install-man install-html
+
+install-man: $(GIT_CONTACTS_DOC)
+ $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir)
+ $(INSTALL) -m 644 $^ $(DESTDIR)$(man1dir)
+
+install-html: $(GIT_CONTACTS_HTML)
+ $(INSTALL) -d -m 755 $(DESTDIR)$(htmldir)
+ $(INSTALL) -m 644 $^ $(DESTDIR)$(htmldir)
+
+$(GIT_CONTACTS_DOC): $(GIT_CONTACTS_XML)
+ $(XMLTO) -m $(MANPAGE_XSL) man $^
+
+$(GIT_CONTACTS_XML): $(GIT_CONTACTS_TXT)
+ $(ASCIIDOC) -b docbook -d manpage -f $(ASCIIDOC_CONF) \
+ -agit_version=$(GIT_VERSION) $^
+
+$(GIT_CONTACTS_HTML): $(GIT_CONTACTS_TXT)
+ $(ASCIIDOC) -b xhtml11 -d manpage -f $(ASCIIDOC_CONF) \
+ -agit_version=$(GIT_VERSION) $^
+
+clean:
+ $(RM) $(GIT_CONTACTS)
+ $(RM) *.xml *.html *.1
+
+.PHONY: FORCE
diff --git a/contrib/contacts/git-contacts b/contrib/contacts/git-contacts
new file mode 100755
index 0000000000..85ad732fc0
--- /dev/null
+++ b/contrib/contacts/git-contacts
@@ -0,0 +1,203 @@
+#!/usr/bin/perl
+
+# List people who might be interested in a patch. Useful as the argument to
+# git-send-email --cc-cmd option, and in other situations.
+#
+# Usage: git contacts <file | rev-list option> ...
+
+use strict;
+use warnings;
+use IPC::Open2;
+
+my $since = '5-years-ago';
+my $min_percent = 10;
+my $labels_rx = qr/Signed-off-by|Reviewed-by|Acked-by|Cc|Reported-by/i;
+my %seen;
+
+sub format_contact {
+ my ($name, $email) = @_;
+ return "$name <$email>";
+}
+
+sub parse_commit {
+ my ($commit, $data) = @_;
+ my $contacts = $commit->{contacts};
+ my $inbody = 0;
+ for (split(/^/m, $data)) {
+ if (not $inbody) {
+ if (/^author ([^<>]+) <(\S+)> .+$/) {
+ $contacts->{format_contact($1, $2)} = 1;
+ } elsif (/^$/) {
+ $inbody = 1;
+ }
+ } elsif (/^$labels_rx:\s+([^<>]+)\s+<(\S+?)>$/o) {
+ $contacts->{format_contact($1, $2)} = 1;
+ }
+ }
+}
+
+sub import_commits {
+ my ($commits) = @_;
+ return unless %$commits;
+ my $pid = open2 my $reader, my $writer, qw(git cat-file --batch);
+ for my $id (keys(%$commits)) {
+ print $writer "$id\n";
+ my $line = <$reader>;
+ if ($line =~ /^([0-9a-f]{40}) commit (\d+)/) {
+ my ($cid, $len) = ($1, $2);
+ die "expected $id but got $cid\n" unless $id eq $cid;
+ my $data;
+ # cat-file emits newline after data, so read len+1
+ read $reader, $data, $len + 1;
+ parse_commit($commits->{$id}, $data);
+ }
+ }
+ close $reader;
+ close $writer;
+ waitpid($pid, 0);
+ die "git-cat-file error: $?\n" if $?;
+}
+
+sub get_blame {
+ my ($commits, $source, $from, $ranges) = @_;
+ return unless @$ranges;
+ open my $f, '-|',
+ qw(git blame --porcelain -C),
+ map({"-L$_->[0],+$_->[1]"} @$ranges),
+ '--since', $since, "$from^", '--', $source or die;
+ while (<$f>) {
+ if (/^([0-9a-f]{40}) \d+ \d+ \d+$/) {
+ my $id = $1;
+ $commits->{$id} = { id => $id, contacts => {} }
+ unless $seen{$id};
+ $seen{$id} = 1;
+ }
+ }
+ close $f;
+}
+
+sub blame_sources {
+ my ($sources, $commits) = @_;
+ for my $s (keys %$sources) {
+ for my $id (keys %{$sources->{$s}}) {
+ get_blame($commits, $s, $id, $sources->{$s}{$id});
+ }
+ }
+}
+
+sub scan_patches {
+ my ($sources, $id, $f) = @_;
+ my $source;
+ while (<$f>) {
+ if (/^From ([0-9a-f]{40}) Mon Sep 17 00:00:00 2001$/) {
+ $id = $1;
+ $seen{$id} = 1;
+ }
+ next unless $id;
+ if (m{^--- (?:a/(.+)|/dev/null)$}) {
+ $source = $1;
+ } elsif (/^@@ -(\d+)(?:,(\d+))?/ && $source) {
+ my $len = defined($2) ? $2 : 1;
+ push @{$sources->{$source}{$id}}, [$1, $len] if $len;
+ }
+ }
+}
+
+sub scan_patch_file {
+ my ($commits, $file) = @_;
+ open my $f, '<', $file or die "read failure: $file: $!\n";
+ scan_patches($commits, undef, $f);
+ close $f;
+}
+
+sub parse_rev_args {
+ my @args = @_;
+ open my $f, '-|',
+ qw(git rev-parse --revs-only --default HEAD --symbolic), @args
+ or die;
+ my @revs;
+ while (<$f>) {
+ chomp;
+ push @revs, $_;
+ }
+ close $f;
+ return @revs if scalar(@revs) != 1;
+ return "^$revs[0]", 'HEAD' unless $revs[0] =~ /^-/;
+ return $revs[0], 'HEAD';
+}
+
+sub scan_rev_args {
+ my ($commits, $args) = @_;
+ my @revs = parse_rev_args(@$args);
+ open my $f, '-|', qw(git rev-list --reverse), @revs or die;
+ while (<$f>) {
+ chomp;
+ my $id = $_;
+ $seen{$id} = 1;
+ open my $g, '-|', qw(git show -C --oneline), $id or die;
+ scan_patches($commits, $id, $g);
+ close $g;
+ }
+ close $f;
+}
+
+sub mailmap_contacts {
+ my ($contacts) = @_;
+ my %mapped;
+ my $pid = open2 my $reader, my $writer, qw(git check-mailmap --stdin);
+ for my $contact (keys(%$contacts)) {
+ print $writer "$contact\n";
+ my $canonical = <$reader>;
+ chomp $canonical;
+ $mapped{$canonical} += $contacts->{$contact};
+ }
+ close $reader;
+ close $writer;
+ waitpid($pid, 0);
+ die "git-check-mailmap error: $?\n" if $?;
+ return \%mapped;
+}
+
+if (!@ARGV) {
+ die "No input revisions or patch files\n";
+}
+
+my (@files, @rev_args);
+for (@ARGV) {
+ if (-e) {
+ push @files, $_;
+ } else {
+ push @rev_args, $_;
+ }
+}
+
+my %sources;
+for (@files) {
+ scan_patch_file(\%sources, $_);
+}
+if (@rev_args) {
+ scan_rev_args(\%sources, \@rev_args)
+}
+
+my $toplevel = `git rev-parse --show-toplevel`;
+chomp $toplevel;
+chdir($toplevel) or die "chdir failure: $toplevel: $!\n";
+
+my %commits;
+blame_sources(\%sources, \%commits);
+import_commits(\%commits);
+
+my $contacts = {};
+for my $commit (values %commits) {
+ for my $contact (keys %{$commit->{contacts}}) {
+ $contacts->{$contact}++;
+ }
+}
+$contacts = mailmap_contacts($contacts);
+
+my $ncommits = scalar(keys %commits);
+for my $contact (keys %$contacts) {
+ my $percent = $contacts->{$contact} * 100 / $ncommits;
+ next if $percent < $min_percent;
+ print "$contact\n";
+}
diff --git a/contrib/contacts/git-contacts.txt b/contrib/contacts/git-contacts.txt
new file mode 100644
index 0000000000..dd914d1261
--- /dev/null
+++ b/contrib/contacts/git-contacts.txt
@@ -0,0 +1,94 @@
+git-contacts(1)
+===============
+
+NAME
+----
+git-contacts - List people who might be interested in a set of changes
+
+
+SYNOPSIS
+--------
+[verse]
+'git contacts' (<patch>|<range>|<rev>)...
+
+
+DESCRIPTION
+-----------
+
+Given a set of changes, specified as patch files or revisions, determine people
+who might be interested in those changes. This is done by consulting the
+history of each patch or revision hunk to find people mentioned by commits
+which touched the lines of files under consideration.
+
+Input consists of one or more patch files or revision arguments. A revision
+argument can be a range or a single `<rev>` which is interpreted as
+`<rev>..HEAD`, thus the same revision arguments are accepted as for
+linkgit:git-format-patch[1]. Patch files and revision arguments can be combined
+in the same invocation.
+
+This command can be useful for determining the list of people with whom to
+discuss proposed changes, or for finding the list of recipients to Cc: when
+submitting a patch series via `git send-email`. For the latter case, `git
+contacts` can be used as the argument to `git send-email`'s `--cc-cmd` option.
+
+
+DISCUSSION
+----------
+
+`git blame` is invoked for each hunk in a patch file or revision. For each
+commit mentioned by `git blame`, the commit message is consulted for people who
+authored, reviewed, signed, acknowledged, or were Cc:'d. Once the list of
+participants is known, each person's relevance is computed by considering how
+many commits mentioned that person compared with the total number of commits
+under consideration. The final output consists only of participants who exceed
+a minimum threshold of participation.
+
+
+OUTPUT
+------
+
+For each person of interest, a single line is output, terminated by a newline.
+If the person's name is known, ``Name $$<user@host>$$'' is printed; otherwise
+only ``$$<user@host>$$'' is printed.
+
+
+EXAMPLES
+--------
+
+* Consult patch files:
++
+------------
+$ git contacts feature/*.patch
+------------
+
+* Revision range:
++
+------------
+$ git contacts R1..R2
+------------
+
+* From a single revision to `HEAD`:
++
+------------
+$ git contacts origin
+------------
+
+* Helper for `git send-email`:
++
+------------
+$ git send-email --cc-cmd='git contacts' feature/*.patch
+------------
+
+
+LIMITATIONS
+-----------
+
+Several conditions controlling a person's significance are currently
+hard-coded, such as minimum participation level (10%), blame date-limiting (5
+years), and `-C` level for detecting moved and copied lines (a single `-C`). In
+the future, these conditions may become configurable.
+
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/contrib/continuous/cidaemon b/contrib/continuous/cidaemon
deleted file mode 100644
index 4009a151de..0000000000
--- a/contrib/continuous/cidaemon
+++ /dev/null
@@ -1,503 +0,0 @@
-#!/usr/bin/perl
-#
-# A daemon that waits for update events sent by its companion
-# post-receive-cinotify hook, checks out a new copy of source,
-# compiles it, and emails the guilty parties if the compile
-# (and optionally test suite) fails.
-#
-# To use this daemon, configure it and run it. It will disconnect
-# from your terminal and fork into the background. The daemon must
-# have local filesystem access to the source repositories, as it
-# uses objects/info/alternates to avoid copying objects.
-#
-# Add its companion post-receive-cinotify hook as the post-receive
-# hook to each repository that the daemon should monitor. Yes, a
-# single daemon can monitor more than one repository.
-#
-# To use multiple daemons on the same system, give them each a
-# unique queue file and tmpdir.
-#
-# Global Config
-# -------------
-# Reads from a Git style configuration file. This will be
-# ~/.gitconfig by default but can be overridden by setting
-# the GIT_CONFIG_FILE environment variable before starting.
-#
-# cidaemon.smtpHost
-# Hostname of the SMTP server the daemon will send email
-# through. Defaults to 'localhost'.
-#
-# cidaemon.smtpUser
-# Username to authenticate to the SMTP server as. This
-# variable is optional; if it is not supplied then no
-# authentication will be performed.
-#
-# cidaemon.smtpPassword
-# Password to authenticate to the SMTP server as. This
-# variable is optional. If not supplied but smtpUser was,
-# the daemon prompts for the password before forking into
-# the background.
-#
-# cidaemon.smtpAuth
-# Type of authentication to perform with the SMTP server.
-# If set to 'login' and smtpUser was defined, this will
-# use the AUTH LOGIN command, which is suitable for use
-# with at least one version of Microsoft Exchange Server.
-# If not set the daemon will use whatever auth methods
-# are supported by your version of Net::SMTP.
-#
-# cidaemon.email
-# Email address that daemon generated emails will be sent
-# from. This should be a useful email address within your
-# organization. Required.
-#
-# cidaemon.name
-# Human friendly name that the daemon will send emails as.
-# Defaults to 'cidaemon'.
-#
-# cidaemon.scanDelay
-# Number of seconds to sleep between polls of the queue file.
-# Defaults to 60.
-#
-# cidaemon.recentCache
-# Number of recent commit SHA-1s per repository to cache and
-# skip building if they appear again. This is useful to avoid
-# rebuilding the same commit multiple times just because it was
-# pushed into more than one branch. Defaults to 100.
-#
-# cidaemon.tmpdir
-# Scratch directory to create the builds within. The daemon
-# makes a new subdirectory for each build, then deletes it when
-# the build has finished. The pid file is also placed here.
-# Defaults to '/tmp'.
-#
-# cidaemon.queue
-# Path to the queue file that the post-receive-cinotify hook
-# appends events to. This file is polled by the daemon. It
-# must not be on an NFS mount (uses flock). Required.
-#
-# cidaemon.nocc
-# Perl regex patterns to match against author and committer
-# lines. If a pattern matches, that author or committer will
-# not be notified of a build failure.
-#
-# Per Repository Config
-# ----------------------
-# Read from the source repository's config file.
-#
-# builder.command
-# Shell command to execute the build. This command must
-# return 0 on "success" and non-zero on failure. If you
-# also want to run a test suite, make sure your command
-# does that too. Required.
-#
-# builder.queue
-# Queue file to notify the cidaemon through. Should match
-# cidaemon.queue. If not set the hook will not notify the
-# cidaemon.
-#
-# builder.skip
-# Perl regex patterns of refs that should not be sent to
-# cidaemon. Updates of these refs will be ignored.
-#
-# builder.newBranchBase
-# Glob patterns of refs that should be used to form the
-# 'old' revions of a newly created ref. This should set
-# to be globs that match your 'mainline' branches. This
-# way a build failure of a brand new topic branch does not
-# attempt to email everyone since the beginning of time;
-# instead it only emails those authors of commits not in
-# these 'mainline' branches.
-
-local $ENV{PATH} = join ':', qw(
- /opt/git/bin
- /usr/bin
- /bin
- );
-
-use strict;
-use warnings;
-use FindBin qw($RealBin);
-use File::Spec;
-use lib File::Spec->catfile($RealBin, '..', 'perl5');
-use Storable qw(retrieve nstore);
-use Fcntl ':flock';
-use POSIX qw(strftime);
-use Getopt::Long qw(:config no_auto_abbrev auto_help);
-
-sub git_config ($;$)
-{
- my $var = shift;
- my $required = shift || 0;
- local *GIT;
- open GIT, '-|','git','config','--get',$var;
- my $r = <GIT>;
- chop $r if $r;
- close GIT;
- die "error: $var not set.\n" if ($required && !$r);
- return $r;
-}
-
-package EXCHANGE_NET_SMTP;
-
-# Microsoft Exchange Server requires an 'AUTH LOGIN'
-# style of authentication. This is different from
-# the default supported by Net::SMTP so we subclass
-# and override the auth method to support that.
-
-use Net::SMTP;
-use Net::Cmd;
-use MIME::Base64 qw(encode_base64);
-our @ISA = qw(Net::SMTP);
-our $auth_type = ::git_config 'cidaemon.smtpAuth';
-
-sub new
-{
- my $self = shift;
- my $type = ref($self) || $self;
- $type->SUPER::new(@_);
-}
-
-sub auth
-{
- my $self = shift;
- return $self->SUPER::auth(@_) unless $auth_type eq 'login';
-
- my $user = encode_base64 shift, '';
- my $pass = encode_base64 shift, '';
- return 0 unless CMD_MORE == $self->command("AUTH LOGIN")->response;
- return 0 unless CMD_MORE == $self->command($user)->response;
- CMD_OK == $self->command($pass)->response;
-}
-
-package main;
-
-my ($debug_flag, %recent);
-
-my $ex_host = git_config('cidaemon.smtpHost') || 'localhost';
-my $ex_user = git_config('cidaemon.smtpUser');
-my $ex_pass = git_config('cidaemon.smtpPassword');
-
-my $ex_from_addr = git_config('cidaemon.email', 1);
-my $ex_from_name = git_config('cidaemon.name') || 'cidaemon';
-
-my $scan_delay = git_config('cidaemon.scanDelay') || 60;
-my $recent_size = git_config('cidaemon.recentCache') || 100;
-my $tmpdir = git_config('cidaemon.tmpdir') || '/tmp';
-my $queue_name = git_config('cidaemon.queue', 1);
-my $queue_lock = "$queue_name.lock";
-
-my @nocc_list;
-open GIT,'git config --get-all cidaemon.nocc|';
-while (<GIT>) {
- chop;
- push @nocc_list, $_;
-}
-close GIT;
-
-sub nocc_author ($)
-{
- local $_ = shift;
- foreach my $pat (@nocc_list) {
- return 1 if /$pat/;
- }
- 0;
-}
-
-sub input_echo ($)
-{
- my $prompt = shift;
-
- local $| = 1;
- print $prompt;
- my $input = <STDIN>;
- chop $input;
- return $input;
-}
-
-sub input_noecho ($)
-{
- my $prompt = shift;
-
- my $end = sub {system('stty','echo');print "\n";exit};
- local $SIG{TERM} = $end;
- local $SIG{INT} = $end;
- system('stty','-echo');
-
- local $| = 1;
- print $prompt;
- my $input = <STDIN>;
- system('stty','echo');
- print "\n";
- chop $input;
- return $input;
-}
-
-sub rfc2822_date ()
-{
- strftime("%a, %d %b %Y %H:%M:%S %Z", localtime);
-}
-
-sub send_email ($$$)
-{
- my ($subj, $body, $to) = @_;
- my $now = rfc2822_date;
- my $to_str = '';
- my @rcpt_to;
- foreach (@$to) {
- my $s = $_;
- $s =~ s/^/"/;
- $s =~ s/(\s+<)/"$1/;
- $to_str .= ', ' if $to_str;
- $to_str .= $s;
- push @rcpt_to, $1 if $s =~ /<(.*)>/;
- }
- die "Nobody to send to.\n" unless @rcpt_to;
- my $msg = <<EOF;
-From: "$ex_from_name" <$ex_from_addr>
-To: $to_str
-Date: $now
-Subject: $subj
-
-$body
-EOF
-
- my $smtp = EXCHANGE_NET_SMTP->new(Host => $ex_host)
- or die "Cannot connect to $ex_host: $!\n";
- if ($ex_user && $ex_pass) {
- $smtp->auth($ex_user,$ex_pass)
- or die "$ex_host rejected $ex_user\n";
- }
- $smtp->mail($ex_from_addr)
- or die "$ex_host rejected $ex_from_addr\n";
- scalar($smtp->recipient(@rcpt_to, { SkipBad => 1 }))
- or die "$ex_host did not accept any addresses.\n";
- $smtp->data($msg)
- or die "$ex_host rejected message data\n";
- $smtp->quit;
-}
-
-sub pop_queue ()
-{
- open LOCK, ">$queue_lock" or die "Can't open $queue_lock: $!";
- flock LOCK, LOCK_EX;
-
- my $queue = -f $queue_name ? retrieve $queue_name : [];
- my $ent = shift @$queue;
- nstore $queue, $queue_name;
-
- flock LOCK, LOCK_UN;
- close LOCK;
- $ent;
-}
-
-sub git_exec (@)
-{
- system('git',@_) == 0 or die "Cannot git " . join(' ', @_) . "\n";
-}
-
-sub git_val (@)
-{
- open(C, '-|','git',@_);
- my $r = <C>;
- chop $r if $r;
- close C;
- $r;
-}
-
-sub do_build ($$)
-{
- my ($git_dir, $new) = @_;
-
- my $tmp = File::Spec->catfile($tmpdir, "builder$$");
- system('rm','-rf',$tmp) == 0 or die "Cannot clear $tmp\n";
- die "Cannot clear $tmp.\n" if -e $tmp;
-
- my $result = 1;
- eval {
- my $command;
- {
- local $ENV{GIT_DIR} = $git_dir;
- $command = git_val 'config','builder.command';
- }
- die "No builder.command for $git_dir.\n" unless $command;
-
- git_exec 'clone','-n','-l','-s',$git_dir,$tmp;
- chmod 0700, $tmp or die "Cannot lock $tmp\n";
- chdir $tmp or die "Cannot enter $tmp\n";
-
- git_exec 'update-ref','HEAD',$new;
- git_exec 'read-tree','-m','-u','HEAD','HEAD';
- system $command;
- if ($? == -1) {
- print STDERR "failed to execute '$command': $!\n";
- $result = 1;
- } elsif ($? & 127) {
- my $sig = $? & 127;
- print STDERR "'$command' died from signal $sig\n";
- $result = 1;
- } else {
- my $r = $? >> 8;
- print STDERR "'$command' exited with $r\n" if $r;
- $result = $r;
- }
- };
- if ($@) {
- $result = 2;
- print STDERR "$@\n";
- }
-
- chdir '/';
- system('rm','-rf',$tmp);
- rmdir $tmp;
- $result;
-}
-
-sub build_failed ($$$$$)
-{
- my ($git_dir, $ref, $old, $new, $msg) = @_;
-
- $git_dir =~ m,/([^/]+)$,;
- my $repo_name = $1;
- $ref =~ s,^refs/(heads|tags)/,,;
-
- my %authors;
- my $shortlog;
- my $revstr;
- {
- local $ENV{GIT_DIR} = $git_dir;
- my @revs = ($new);
- push @revs, '--not', @$old if @$old;
- open LOG,'-|','git','rev-list','--pretty=raw',@revs;
- while (<LOG>) {
- if (s/^(author|committer) //) {
- chomp;
- s/>.*$/>/;
- $authors{$_} = 1 unless nocc_author $_;
- }
- }
- close LOG;
- open LOG,'-|','git','shortlog',@revs;
- $shortlog .= $_ while <LOG>;
- close LOG;
- $revstr = join(' ', @revs);
- }
-
- my @to = sort keys %authors;
- unless (@to) {
- print STDERR "error: No authors in $revstr\n";
- return;
- }
-
- my $subject = "[$repo_name] $ref : Build Failed";
- my $body = <<EOF;
-Project: $git_dir
-Branch: $ref
-Commits: $revstr
-
-$shortlog
-Build Output:
---------------------------------------------------------------
-$msg
-EOF
- send_email($subject, $body, \@to);
-}
-
-sub run_build ($$$$)
-{
- my ($git_dir, $ref, $old, $new) = @_;
-
- if ($debug_flag) {
- my @revs = ($new);
- push @revs, '--not', @$old if @$old;
- print "BUILDING $git_dir\n";
- print " BRANCH: $ref\n";
- print " COMMITS: ", join(' ', @revs), "\n";
- }
-
- local(*R, *W);
- pipe R, W or die "cannot pipe builder: $!";
-
- my $builder = fork();
- if (!defined $builder) {
- die "cannot fork builder: $!";
- } elsif (0 == $builder) {
- close R;
- close STDIN;open(STDIN, '/dev/null');
- open(STDOUT, '>&W');
- open(STDERR, '>&W');
- exit do_build $git_dir, $new;
- } else {
- close W;
- my $out = '';
- $out .= $_ while <R>;
- close R;
- waitpid $builder, 0;
- build_failed $git_dir, $ref, $old, $new, $out if $?;
- }
-
- print "DONE\n\n" if $debug_flag;
-}
-
-sub daemon_loop ()
-{
- my $run = 1;
- my $stop_sub = sub {$run = 0};
- $SIG{HUP} = $stop_sub;
- $SIG{INT} = $stop_sub;
- $SIG{TERM} = $stop_sub;
-
- mkdir $tmpdir, 0755;
- my $pidfile = File::Spec->catfile($tmpdir, "cidaemon.pid");
- open(O, ">$pidfile"); print O "$$\n"; close O;
-
- while ($run) {
- my $ent = pop_queue;
- if ($ent) {
- my ($git_dir, $ref, $old, $new) = @$ent;
-
- $ent = $recent{$git_dir};
- $recent{$git_dir} = $ent = [[], {}] unless $ent;
- my ($rec_arr, $rec_hash) = @$ent;
- next if $rec_hash->{$new}++;
- while (@$rec_arr >= $recent_size) {
- my $to_kill = shift @$rec_arr;
- delete $rec_hash->{$to_kill};
- }
- push @$rec_arr, $new;
-
- run_build $git_dir, $ref, $old, $new;
- } else {
- sleep $scan_delay;
- }
- }
-
- unlink $pidfile;
-}
-
-$debug_flag = 0;
-GetOptions(
- 'debug|d' => \$debug_flag,
- 'smtp-user=s' => \$ex_user,
-) or die "usage: $0 [--debug] [--smtp-user=user]\n";
-
-$ex_pass = input_noecho("$ex_user SMTP password: ")
- if ($ex_user && !$ex_pass);
-
-if ($debug_flag) {
- daemon_loop;
- exit 0;
-}
-
-my $daemon = fork();
-if (!defined $daemon) {
- die "cannot fork daemon: $!";
-} elsif (0 == $daemon) {
- close STDIN;open(STDIN, '/dev/null');
- close STDOUT;open(STDOUT, '>/dev/null');
- close STDERR;open(STDERR, '>/dev/null');
- daemon_loop;
- exit 0;
-} else {
- print "Daemon $daemon running in the background.\n";
-}
diff --git a/contrib/continuous/post-receive-cinotify b/contrib/continuous/post-receive-cinotify
deleted file mode 100644
index b8f5a609af..0000000000
--- a/contrib/continuous/post-receive-cinotify
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/perl
-#
-# A hook that notifies its companion cidaemon through a simple
-# queue file that a ref has been updated via a push (actually
-# by a receive-pack running on the server).
-#
-# See cidaemon for per-repository configuration details.
-#
-# To use this hook, add it as the post-receive hook, make it
-# executable, and set its configuration options.
-#
-
-local $ENV{PATH} = '/opt/git/bin';
-
-use strict;
-use warnings;
-use File::Spec;
-use Storable qw(retrieve nstore);
-use Fcntl ':flock';
-
-my $git_dir = File::Spec->rel2abs($ENV{GIT_DIR});
-my $queue_name = `git config --get builder.queue`;chop $queue_name;
-$queue_name =~ m,^([^\s]+)$,; $queue_name = $1; # untaint
-unless ($queue_name) {
- 1 while <STDIN>;
- print STDERR "\nerror: builder.queue not set. Not enqueing.\n\n";
- exit;
-}
-my $queue_lock = "$queue_name.lock";
-
-my @skip;
-open S, "git config --get-all builder.skip|";
-while (<S>) {
- chop;
- push @skip, $_;
-}
-close S;
-
-my @new_branch_base;
-open S, "git config --get-all builder.newBranchBase|";
-while (<S>) {
- chop;
- push @new_branch_base, $_;
-}
-close S;
-
-sub skip ($)
-{
- local $_ = shift;
- foreach my $p (@skip) {
- return 1 if /^$p/;
- }
- 0;
-}
-
-open LOCK, ">$queue_lock" or die "Can't open $queue_lock: $!";
-flock LOCK, LOCK_EX;
-
-my $queue = -f $queue_name ? retrieve $queue_name : [];
-my %existing;
-foreach my $r (@$queue) {
- my ($gd, $ref) = @$r;
- $existing{$gd}{$ref} = $r;
-}
-
-my @new_branch_commits;
-my $loaded_new_branch_commits = 0;
-
-while (<STDIN>) {
- chop;
- my ($old, $new, $ref) = split / /, $_, 3;
-
- next if $old eq $new;
- next if $new =~ /^0{40}$/;
- next if skip $ref;
-
- my $r = $existing{$git_dir}{$ref};
- if ($r) {
- $r->[3] = $new;
- } else {
- if ($old =~ /^0{40}$/) {
- if (!$loaded_new_branch_commits && @new_branch_base) {
- open M,'-|','git','show-ref',@new_branch_base;
- while (<M>) {
- ($_) = split / /, $_;
- push @new_branch_commits, $_;
- }
- close M;
- $loaded_new_branch_commits = 1;
- }
- $old = [@new_branch_commits];
- } else {
- $old = [$old];
- }
-
- $r = [$git_dir, $ref, $old, $new];
- $existing{$git_dir}{$ref} = $r;
- push @$queue, $r;
- }
-}
-nstore $queue, $queue_name;
-
-flock LOCK, LOCK_UN;
-close LOCK;
diff --git a/contrib/convert-objects/convert-objects.c b/contrib/convert-objects/convert-objects.c
deleted file mode 100644
index f3b57bf1d2..0000000000
--- a/contrib/convert-objects/convert-objects.c
+++ /dev/null
@@ -1,329 +0,0 @@
-#include "cache.h"
-#include "blob.h"
-#include "commit.h"
-#include "tree.h"
-
-struct entry {
- unsigned char old_sha1[20];
- unsigned char new_sha1[20];
- int converted;
-};
-
-#define MAXOBJECTS (1000000)
-
-static struct entry *convert[MAXOBJECTS];
-static int nr_convert;
-
-static struct entry * convert_entry(unsigned char *sha1);
-
-static struct entry *insert_new(unsigned char *sha1, int pos)
-{
- struct entry *new = xcalloc(1, sizeof(struct entry));
- hashcpy(new->old_sha1, sha1);
- memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *));
- convert[pos] = new;
- nr_convert++;
- if (nr_convert == MAXOBJECTS)
- die("you're kidding me - hit maximum object limit");
- return new;
-}
-
-static struct entry *lookup_entry(unsigned char *sha1)
-{
- int low = 0, high = nr_convert;
-
- while (low < high) {
- int next = (low + high) / 2;
- struct entry *n = convert[next];
- int cmp = hashcmp(sha1, n->old_sha1);
- if (!cmp)
- return n;
- if (cmp < 0) {
- high = next;
- continue;
- }
- low = next+1;
- }
- return insert_new(sha1, low);
-}
-
-static void convert_binary_sha1(void *buffer)
-{
- struct entry *entry = convert_entry(buffer);
- hashcpy(buffer, entry->new_sha1);
-}
-
-static void convert_ascii_sha1(void *buffer)
-{
- unsigned char sha1[20];
- struct entry *entry;
-
- if (get_sha1_hex(buffer, sha1))
- die("expected sha1, got '%s'", (char *) buffer);
- entry = convert_entry(sha1);
- memcpy(buffer, sha1_to_hex(entry->new_sha1), 40);
-}
-
-static unsigned int convert_mode(unsigned int mode)
-{
- unsigned int newmode;
-
- newmode = mode & S_IFMT;
- if (S_ISREG(mode))
- newmode |= (mode & 0100) ? 0755 : 0644;
- return newmode;
-}
-
-static int write_subdirectory(void *buffer, unsigned long size, const char *base, int baselen, unsigned char *result_sha1)
-{
- char *new = xmalloc(size);
- unsigned long newlen = 0;
- unsigned long used;
-
- used = 0;
- while (size) {
- int len = 21 + strlen(buffer);
- char *path = strchr(buffer, ' ');
- unsigned char *sha1;
- unsigned int mode;
- char *slash, *origpath;
-
- if (!path || strtoul_ui(buffer, 8, &mode))
- die("bad tree conversion");
- mode = convert_mode(mode);
- path++;
- if (memcmp(path, base, baselen))
- break;
- origpath = path;
- path += baselen;
- slash = strchr(path, '/');
- if (!slash) {
- newlen += sprintf(new + newlen, "%o %s", mode, path);
- new[newlen++] = '\0';
- hashcpy((unsigned char *)new + newlen, (unsigned char *) buffer + len - 20);
- newlen += 20;
-
- used += len;
- size -= len;
- buffer = (char *) buffer + len;
- continue;
- }
-
- newlen += sprintf(new + newlen, "%o %.*s", S_IFDIR, (int)(slash - path), path);
- new[newlen++] = 0;
- sha1 = (unsigned char *)(new + newlen);
- newlen += 20;
-
- len = write_subdirectory(buffer, size, origpath, slash-origpath+1, sha1);
-
- used += len;
- size -= len;
- buffer = (char *) buffer + len;
- }
-
- write_sha1_file(new, newlen, tree_type, result_sha1);
- free(new);
- return used;
-}
-
-static void convert_tree(void *buffer, unsigned long size, unsigned char *result_sha1)
-{
- void *orig_buffer = buffer;
- unsigned long orig_size = size;
-
- while (size) {
- size_t len = 1+strlen(buffer);
-
- convert_binary_sha1((char *) buffer + len);
-
- len += 20;
- if (len > size)
- die("corrupt tree object");
- size -= len;
- buffer = (char *) buffer + len;
- }
-
- write_subdirectory(orig_buffer, orig_size, "", 0, result_sha1);
-}
-
-static unsigned long parse_oldstyle_date(const char *buf)
-{
- char c, *p;
- char buffer[100];
- struct tm tm;
- const char *formats[] = {
- "%c",
- "%a %b %d %T",
- "%Z",
- "%Y",
- " %Y",
- NULL
- };
- /* We only ever did two timezones in the bad old format .. */
- const char *timezones[] = {
- "PDT", "PST", "CEST", NULL
- };
- const char **fmt = formats;
-
- p = buffer;
- while (isspace(c = *buf))
- buf++;
- while ((c = *buf++) != '\n')
- *p++ = c;
- *p++ = 0;
- buf = buffer;
- memset(&tm, 0, sizeof(tm));
- do {
- const char *next = strptime(buf, *fmt, &tm);
- if (next) {
- if (!*next)
- return mktime(&tm);
- buf = next;
- } else {
- const char **p = timezones;
- while (isspace(*buf))
- buf++;
- while (*p) {
- if (!memcmp(buf, *p, strlen(*p))) {
- buf += strlen(*p);
- break;
- }
- p++;
- }
- }
- fmt++;
- } while (*buf && *fmt);
- printf("left: %s\n", buf);
- return mktime(&tm);
-}
-
-static int convert_date_line(char *dst, void **buf, unsigned long *sp)
-{
- unsigned long size = *sp;
- char *line = *buf;
- char *next = strchr(line, '\n');
- char *date = strchr(line, '>');
- int len;
-
- if (!next || !date)
- die("missing or bad author/committer line %s", line);
- next++; date += 2;
-
- *buf = next;
- *sp = size - (next - line);
-
- len = date - line;
- memcpy(dst, line, len);
- dst += len;
-
- /* Is it already in new format? */
- if (isdigit(*date)) {
- int datelen = next - date;
- memcpy(dst, date, datelen);
- return len + datelen;
- }
-
- /*
- * Hacky hacky: one of the sparse old-style commits does not have
- * any date at all, but we can fake it by using the committer date.
- */
- if (*date == '\n' && strchr(next, '>'))
- date = strchr(next, '>')+2;
-
- return len + sprintf(dst, "%lu -0700\n", parse_oldstyle_date(date));
-}
-
-static void convert_date(void *buffer, unsigned long size, unsigned char *result_sha1)
-{
- char *new = xmalloc(size + 100);
- unsigned long newlen = 0;
-
- /* "tree <sha1>\n" */
- memcpy(new + newlen, buffer, 46);
- newlen += 46;
- buffer = (char *) buffer + 46;
- size -= 46;
-
- /* "parent <sha1>\n" */
- while (!memcmp(buffer, "parent ", 7)) {
- memcpy(new + newlen, buffer, 48);
- newlen += 48;
- buffer = (char *) buffer + 48;
- size -= 48;
- }
-
- /* "author xyz <xyz> date" */
- newlen += convert_date_line(new + newlen, &buffer, &size);
- /* "committer xyz <xyz> date" */
- newlen += convert_date_line(new + newlen, &buffer, &size);
-
- /* Rest */
- memcpy(new + newlen, buffer, size);
- newlen += size;
-
- write_sha1_file(new, newlen, commit_type, result_sha1);
- free(new);
-}
-
-static void convert_commit(void *buffer, unsigned long size, unsigned char *result_sha1)
-{
- void *orig_buffer = buffer;
- unsigned long orig_size = size;
-
- if (memcmp(buffer, "tree ", 5))
- die("Bad commit '%s'", (char *) buffer);
- convert_ascii_sha1((char *) buffer + 5);
- buffer = (char *) buffer + 46; /* "tree " + "hex sha1" + "\n" */
- while (!memcmp(buffer, "parent ", 7)) {
- convert_ascii_sha1((char *) buffer + 7);
- buffer = (char *) buffer + 48;
- }
- convert_date(orig_buffer, orig_size, result_sha1);
-}
-
-static struct entry * convert_entry(unsigned char *sha1)
-{
- struct entry *entry = lookup_entry(sha1);
- enum object_type type;
- void *buffer, *data;
- unsigned long size;
-
- if (entry->converted)
- return entry;
- data = read_sha1_file(sha1, &type, &size);
- if (!data)
- die("unable to read object %s", sha1_to_hex(sha1));
-
- buffer = xmalloc(size);
- memcpy(buffer, data, size);
-
- if (type == OBJ_BLOB) {
- write_sha1_file(buffer, size, blob_type, entry->new_sha1);
- } else if (type == OBJ_TREE)
- convert_tree(buffer, size, entry->new_sha1);
- else if (type == OBJ_COMMIT)
- convert_commit(buffer, size, entry->new_sha1);
- else
- die("unknown object type %d in %s", type, sha1_to_hex(sha1));
- entry->converted = 1;
- free(buffer);
- free(data);
- return entry;
-}
-
-int main(int argc, char **argv)
-{
- unsigned char sha1[20];
- struct entry *entry;
-
- setup_git_directory();
-
- if (argc != 2)
- usage("git-convert-objects <sha1>");
- if (get_sha1(argv[1], sha1))
- die("Not a valid object name %s", argv[1]);
-
- entry = convert_entry(sha1);
- printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1));
- return 0;
-}
diff --git a/contrib/convert-objects/git-convert-objects.txt b/contrib/convert-objects/git-convert-objects.txt
deleted file mode 100644
index 0565d83fc4..0000000000
--- a/contrib/convert-objects/git-convert-objects.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-git-convert-objects(1)
-======================
-
-NAME
-----
-git-convert-objects - Converts old-style git repository
-
-
-SYNOPSIS
---------
-[verse]
-'git-convert-objects'
-
-DESCRIPTION
------------
-Converts old-style git repository to the latest format
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
diff --git a/contrib/coverage-diff.sh b/contrib/coverage-diff.sh
new file mode 100755
index 0000000000..4ec419f900
--- /dev/null
+++ b/contrib/coverage-diff.sh
@@ -0,0 +1,108 @@
+#!/bin/sh
+
+# Usage: Run 'contrib/coverage-diff.sh <version1> <version2>' from source-root
+# after running
+#
+# make coverage-test
+# make coverage-report
+#
+# while checked out at <version2>. This script combines the *.gcov files
+# generated by the 'make' commands above with 'git diff <version1> <version2>'
+# to report new lines that are not covered by the test suite.
+
+V1=$1
+V2=$2
+
+diff_lines () {
+ perl -e '
+ my $line_num;
+ while (<>) {
+ # Hunk header? Grab the beginning in postimage.
+ if (/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/) {
+ $line_num = $1;
+ next;
+ }
+
+ # Have we seen a hunk? Ignore "diff --git" etc.
+ next unless defined $line_num;
+
+ # Deleted line? Ignore.
+ if (/^-/) {
+ next;
+ }
+
+ # Show only the line number of added lines.
+ if (/^\+/) {
+ print "$line_num\n";
+ }
+ # Either common context or added line appear in
+ # the postimage. Count it.
+ $line_num++;
+ }
+ '
+}
+
+files=$(git diff --name-only "$V1" "$V2" -- \*.c)
+
+# create empty file
+>coverage-data.txt
+
+for file in $files
+do
+ git diff "$V1" "$V2" -- "$file" |
+ diff_lines |
+ sort >new_lines.txt
+
+ if ! test -s new_lines.txt
+ then
+ continue
+ fi
+
+ hash_file=$(echo $file | sed "s/\//\#/")
+
+ if ! test -s "$hash_file.gcov"
+ then
+ continue
+ fi
+
+ sed -ne '/#####:/{
+ s/ #####://
+ s/:.*//
+ s/ //g
+ p
+ }' "$hash_file.gcov" |
+ sort >uncovered_lines.txt
+
+ comm -12 uncovered_lines.txt new_lines.txt |
+ sed -e 's/$/\)/' |
+ sed -e 's/^/ /' >uncovered_new_lines.txt
+
+ grep -q '[^[:space:]]' <uncovered_new_lines.txt &&
+ echo $file >>coverage-data.txt &&
+ git blame -s "$V2" -- "$file" |
+ sed 's/\t//g' |
+ grep -f uncovered_new_lines.txt >>coverage-data.txt &&
+ echo >>coverage-data.txt
+
+ rm -f new_lines.txt uncovered_lines.txt uncovered_new_lines.txt
+done
+
+cat coverage-data.txt
+
+echo "Commits introducing uncovered code:"
+
+commit_list=$(cat coverage-data.txt |
+ grep -E '^[0-9a-f]{7,} ' |
+ awk '{print $1;}' |
+ sort |
+ uniq)
+
+(
+ for commit in $commit_list
+ do
+ git log --no-decorate --pretty=format:'%an %h: %s' -1 $commit
+ echo
+ done
+) | sort
+
+rm coverage-data.txt
diff --git a/contrib/credential/gnome-keyring/Makefile b/contrib/credential/gnome-keyring/Makefile
index e6561d8db6..22c19df94b 100644
--- a/contrib/credential/gnome-keyring/Makefile
+++ b/contrib/credential/gnome-keyring/Makefile
@@ -4,12 +4,13 @@ all:: $(MAIN)
CC = gcc
RM = rm -f
CFLAGS = -g -O2 -Wall
+PKG_CONFIG = pkg-config
-include ../../../config.mak.autogen
-include ../../../config.mak
-INCS:=$(shell pkg-config --cflags gnome-keyring-1)
-LIBS:=$(shell pkg-config --libs gnome-keyring-1)
+INCS:=$(shell $(PKG_CONFIG) --cflags gnome-keyring-1 glib-2.0)
+LIBS:=$(shell $(PKG_CONFIG) --libs gnome-keyring-1 glib-2.0)
SRCS:=$(MAIN).c
OBJS:=$(SRCS:.c=.o)
diff --git a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
index 41f61c5db3..5927e27ae6 100644
--- a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
+++ b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
@@ -13,8 +13,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/*
@@ -25,135 +24,148 @@
#include <stdio.h>
#include <string.h>
-#include <stdarg.h>
#include <stdlib.h>
-#include <errno.h>
+#include <glib.h>
#include <gnome-keyring.h>
-/*
- * This credential struct and API is simplified from git's credential.{h,c}
- */
-struct credential
-{
- char *protocol;
- char *host;
- unsigned short port;
- char *path;
- char *username;
- char *password;
-};
+#ifdef GNOME_KEYRING_DEFAULT
-#define CREDENTIAL_INIT \
- { NULL,NULL,0,NULL,NULL,NULL }
+ /* Modern gnome-keyring */
-void credential_init(struct credential *c);
-void credential_clear(struct credential *c);
-int credential_read(struct credential *c);
-void credential_write(const struct credential *c);
+#include <gnome-keyring-memory.h>
-typedef int (*credential_op_cb)(struct credential*);
+#else
-struct credential_operation
-{
- char *name;
- credential_op_cb op;
-};
+ /*
+ * Support ancient gnome-keyring, circ. RHEL 5.X.
+ * GNOME_KEYRING_DEFAULT seems to have been introduced with Gnome 2.22,
+ * and the other features roughly around Gnome 2.20, 6 months before.
+ * Ubuntu 8.04 used Gnome 2.22 (I think). Not sure any distro used 2.20.
+ * So the existence/non-existence of GNOME_KEYRING_DEFAULT seems like
+ * a decent thing to use as an indicator.
+ */
-#define CREDENTIAL_OP_END \
- { NULL,NULL }
+#define GNOME_KEYRING_DEFAULT NULL
/*
- * Table with operation callbacks is defined in concrete
- * credential helper implementation and contains entries
- * like { "get", function_to_get_credential } terminated
- * by CREDENTIAL_OP_END.
+ * ancient gnome-keyring returns DENIED when an entry is not found.
+ * Setting NO_MATCH to DENIED will prevent us from reporting DENIED
+ * errors during get and erase operations, but we will still report
+ * DENIED errors during a store.
*/
-struct credential_operation const credential_helper_ops[];
+#define GNOME_KEYRING_RESULT_NO_MATCH GNOME_KEYRING_RESULT_DENIED
-/* ---------------- common helper functions ----------------- */
+#define gnome_keyring_memory_alloc g_malloc
+#define gnome_keyring_memory_free gnome_keyring_free_password
+#define gnome_keyring_memory_strdup g_strdup
-static inline void free_password(char *password)
+static const char *gnome_keyring_result_to_message(GnomeKeyringResult result)
{
- char *c = password;
- if (!password)
- return;
-
- while (*c) *c++ = '\0';
- free(password);
+ switch (result) {
+ case GNOME_KEYRING_RESULT_OK:
+ return "OK";
+ case GNOME_KEYRING_RESULT_DENIED:
+ return "Denied";
+ case GNOME_KEYRING_RESULT_NO_KEYRING_DAEMON:
+ return "No Keyring Daemon";
+ case GNOME_KEYRING_RESULT_ALREADY_UNLOCKED:
+ return "Already UnLocked";
+ case GNOME_KEYRING_RESULT_NO_SUCH_KEYRING:
+ return "No Such Keyring";
+ case GNOME_KEYRING_RESULT_BAD_ARGUMENTS:
+ return "Bad Arguments";
+ case GNOME_KEYRING_RESULT_IO_ERROR:
+ return "IO Error";
+ case GNOME_KEYRING_RESULT_CANCELLED:
+ return "Cancelled";
+ case GNOME_KEYRING_RESULT_ALREADY_EXISTS:
+ return "Already Exists";
+ default:
+ return "Unknown Error";
+ }
}
-static inline void warning(const char *fmt, ...)
+/*
+ * Support really ancient gnome-keyring, circ. RHEL 4.X.
+ * Just a guess for the Glib version. Glib 2.8 was roughly Gnome 2.12 ?
+ * Which was released with gnome-keyring 0.4.3 ??
+ */
+#if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 8
+
+static void gnome_keyring_done_cb(GnomeKeyringResult result, gpointer user_data)
{
- va_list ap;
+ gpointer *data = (gpointer *)user_data;
+ int *done = (int *)data[0];
+ GnomeKeyringResult *r = (GnomeKeyringResult *)data[1];
- va_start(ap, fmt);
- fprintf(stderr, "warning: ");
- vfprintf(stderr, fmt, ap);
- fprintf(stderr, "\n" );
- va_end(ap);
+ *r = result;
+ *done = 1;
}
-static inline void error(const char *fmt, ...)
+static void wait_for_request_completion(int *done)
{
- va_list ap;
-
- va_start(ap, fmt);
- fprintf(stderr, "error: ");
- vfprintf(stderr, fmt, ap);
- fprintf(stderr, "\n" );
- va_end(ap);
+ GMainContext *mc = g_main_context_default();
+ while (!*done)
+ g_main_context_iteration(mc, TRUE);
}
-static inline void die(const char *fmt, ...)
+static GnomeKeyringResult gnome_keyring_item_delete_sync(const char *keyring, guint32 id)
{
- va_list ap;
+ int done = 0;
+ GnomeKeyringResult result;
+ gpointer data[] = { &done, &result };
- va_start(ap,fmt);
- error(fmt, ap);
- va_end(ap);
- exit(EXIT_FAILURE);
-}
+ gnome_keyring_item_delete(keyring, id, gnome_keyring_done_cb, data,
+ NULL);
-static inline void die_errno(int err)
-{
- error("%s", strerror(err));
- exit(EXIT_FAILURE);
+ wait_for_request_completion(&done);
+
+ return result;
}
-static inline char *xstrdup(const char *str)
-{
- char *ret = strdup(str);
- if (!ret)
- die_errno(errno);
+#endif
+#endif
- return ret;
-}
+/*
+ * This credential struct and API is simplified from git's credential.{h,c}
+ */
+struct credential {
+ char *protocol;
+ char *host;
+ unsigned short port;
+ char *path;
+ char *username;
+ char *password;
+};
+
+#define CREDENTIAL_INIT { 0 }
+
+typedef int (*credential_op_cb)(struct credential *);
+
+struct credential_operation {
+ char *name;
+ credential_op_cb op;
+};
+
+#define CREDENTIAL_OP_END { NULL, NULL }
/* ----------------- GNOME Keyring functions ----------------- */
/* create a special keyring option string, if path is given */
-static char* keyring_object(struct credential *c)
+static char *keyring_object(struct credential *c)
{
- char* object = NULL;
-
if (!c->path)
- return object;
+ return NULL;
- object = (char*) malloc(strlen(c->host)+strlen(c->path)+8);
- if(!object)
- die_errno(errno);
+ if (c->port)
+ return g_strdup_printf("%s:%hd/%s", c->host, c->port, c->path);
- if(c->port)
- sprintf(object,"%s:%hd/%s",c->host,c->port,c->path);
- else
- sprintf(object,"%s/%s",c->host,c->path);
-
- return object;
+ return g_strdup_printf("%s/%s", c->host, c->path);
}
-int keyring_get(struct credential *c)
+static int keyring_get(struct credential *c)
{
- char* object = NULL;
+ char *object = NULL;
GList *entries;
GnomeKeyringNetworkPasswordData *password_data;
GnomeKeyringResult result;
@@ -173,7 +185,7 @@ int keyring_get(struct credential *c)
c->port,
&entries);
- free(object);
+ g_free(object);
if (result == GNOME_KEYRING_RESULT_NO_MATCH)
return EXIT_SUCCESS;
@@ -182,18 +194,18 @@ int keyring_get(struct credential *c)
return EXIT_SUCCESS;
if (result != GNOME_KEYRING_RESULT_OK) {
- error("%s",gnome_keyring_result_to_message(result));
+ g_critical("%s", gnome_keyring_result_to_message(result));
return EXIT_FAILURE;
}
/* pick the first one from the list */
- password_data = (GnomeKeyringNetworkPasswordData *) entries->data;
+ password_data = (GnomeKeyringNetworkPasswordData *)entries->data;
- free_password(c->password);
- c->password = xstrdup(password_data->password);
+ gnome_keyring_memory_free(c->password);
+ c->password = gnome_keyring_memory_strdup(password_data->password);
if (!c->username)
- c->username = xstrdup(password_data->user);
+ c->username = g_strdup(password_data->user);
gnome_keyring_network_password_list_free(entries);
@@ -201,10 +213,11 @@ int keyring_get(struct credential *c)
}
-int keyring_store(struct credential *c)
+static int keyring_store(struct credential *c)
{
guint32 item_id;
- char *object = NULL;
+ char *object = NULL;
+ GnomeKeyringResult result;
/*
* Sanity check that what we are storing is actually sensible.
@@ -219,7 +232,7 @@ int keyring_store(struct credential *c)
object = keyring_object(c);
- gnome_keyring_set_network_password_sync(
+ result = gnome_keyring_set_network_password_sync(
GNOME_KEYRING_DEFAULT,
c->username,
NULL /* domain */,
@@ -231,13 +244,20 @@ int keyring_store(struct credential *c)
c->password,
&item_id);
- free(object);
+ g_free(object);
+
+ if (result != GNOME_KEYRING_RESULT_OK &&
+ result != GNOME_KEYRING_RESULT_CANCELLED) {
+ g_critical("%s", gnome_keyring_result_to_message(result));
+ return EXIT_FAILURE;
+ }
+
return EXIT_SUCCESS;
}
-int keyring_erase(struct credential *c)
+static int keyring_erase(struct credential *c)
{
- char *object = NULL;
+ char *object = NULL;
GList *entries;
GnomeKeyringNetworkPasswordData *password_data;
GnomeKeyringResult result;
@@ -265,7 +285,7 @@ int keyring_erase(struct credential *c)
c->port,
&entries);
- free(object);
+ g_free(object);
if (result == GNOME_KEYRING_RESULT_NO_MATCH)
return EXIT_SUCCESS;
@@ -273,23 +293,21 @@ int keyring_erase(struct credential *c)
if (result == GNOME_KEYRING_RESULT_CANCELLED)
return EXIT_SUCCESS;
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error("%s",gnome_keyring_result_to_message(result));
+ if (result != GNOME_KEYRING_RESULT_OK) {
+ g_critical("%s", gnome_keyring_result_to_message(result));
return EXIT_FAILURE;
}
/* pick the first one from the list (delete all matches?) */
- password_data = (GnomeKeyringNetworkPasswordData *) entries->data;
+ password_data = (GnomeKeyringNetworkPasswordData *)entries->data;
result = gnome_keyring_item_delete_sync(
password_data->keyring, password_data->item_id);
gnome_keyring_network_password_list_free(entries);
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error("%s",gnome_keyring_result_to_message(result));
+ if (result != GNOME_KEYRING_RESULT_OK) {
+ g_critical("%s", gnome_keyring_result_to_message(result));
return EXIT_FAILURE;
}
@@ -300,9 +318,8 @@ int keyring_erase(struct credential *c)
* Table with helper operation callbacks, used by generic
* credential helper main function.
*/
-struct credential_operation const credential_helper_ops[] =
-{
- { "get", keyring_get },
+static struct credential_operation const credential_helper_ops[] = {
+ { "get", keyring_get },
{ "store", keyring_store },
{ "erase", keyring_erase },
CREDENTIAL_OP_END
@@ -310,67 +327,70 @@ struct credential_operation const credential_helper_ops[] =
/* ------------------ credential functions ------------------ */
-void credential_init(struct credential *c)
+static void credential_init(struct credential *c)
{
memset(c, 0, sizeof(*c));
}
-void credential_clear(struct credential *c)
+static void credential_clear(struct credential *c)
{
- free(c->protocol);
- free(c->host);
- free(c->path);
- free(c->username);
- free_password(c->password);
+ g_free(c->protocol);
+ g_free(c->host);
+ g_free(c->path);
+ g_free(c->username);
+ gnome_keyring_memory_free(c->password);
credential_init(c);
}
-int credential_read(struct credential *c)
+static int credential_read(struct credential *c)
{
- char buf[1024];
- ssize_t line_len = 0;
- char *key = buf;
- char *value;
+ char *buf;
+ size_t line_len;
+ char *key;
+ char *value;
- while (fgets(buf, sizeof(buf), stdin))
- {
+ key = buf = gnome_keyring_memory_alloc(1024);
+
+ while (fgets(buf, 1024, stdin)) {
line_len = strlen(buf);
- if(buf[line_len-1]=='\n')
- buf[--line_len]='\0';
+ if (line_len && buf[line_len-1] == '\n')
+ buf[--line_len] = '\0';
- if(!line_len)
+ if (!line_len)
break;
- value = strchr(buf,'=');
- if(!value) {
- warning("invalid credential line: %s", key);
+ value = strchr(buf, '=');
+ if (!value) {
+ g_warning("invalid credential line: %s", key);
+ gnome_keyring_memory_free(buf);
return -1;
}
*value++ = '\0';
if (!strcmp(key, "protocol")) {
- free(c->protocol);
- c->protocol = xstrdup(value);
+ g_free(c->protocol);
+ c->protocol = g_strdup(value);
} else if (!strcmp(key, "host")) {
- free(c->host);
- c->host = xstrdup(value);
- value = strrchr(c->host,':');
+ g_free(c->host);
+ c->host = g_strdup(value);
+ value = strrchr(c->host, ':');
if (value) {
*value++ = '\0';
c->port = atoi(value);
}
} else if (!strcmp(key, "path")) {
- free(c->path);
- c->path = xstrdup(value);
+ g_free(c->path);
+ c->path = g_strdup(value);
} else if (!strcmp(key, "username")) {
- free(c->username);
- c->username = xstrdup(value);
+ g_free(c->username);
+ c->username = g_strdup(value);
} else if (!strcmp(key, "password")) {
- free_password(c->password);
- c->password = xstrdup(value);
- while (*value) *value++ = '\0';
+ gnome_keyring_memory_free(c->password);
+ c->password = gnome_keyring_memory_strdup(value);
+ while (*value)
+ *value++ = '\0';
}
/*
* Ignore other lines; we don't know what they mean, but
@@ -378,17 +398,20 @@ int credential_read(struct credential *c)
* learn new lines, and the helpers are updated to match.
*/
}
+
+ gnome_keyring_memory_free(buf);
+
return 0;
}
-void credential_write_item(FILE *fp, const char *key, const char *value)
+static void credential_write_item(FILE *fp, const char *key, const char *value)
{
if (!value)
return;
fprintf(fp, "%s=%s\n", key, value);
}
-void credential_write(const struct credential *c)
+static void credential_write(const struct credential *c)
{
/* only write username/password, if set */
credential_write_item(stdout, "username", c->username);
@@ -398,16 +421,16 @@ void credential_write(const struct credential *c)
static void usage(const char *name)
{
struct credential_operation const *try_op = credential_helper_ops;
- const char *basename = strrchr(name,'/');
+ const char *basename = strrchr(name, '/');
basename = (basename) ? basename + 1 : name;
- fprintf(stderr, "Usage: %s <", basename);
- while(try_op->name) {
- fprintf(stderr,"%s",(try_op++)->name);
- if(try_op->name)
- fprintf(stderr,"%s","|");
+ fprintf(stderr, "usage: %s <", basename);
+ while (try_op->name) {
+ fprintf(stderr, "%s", (try_op++)->name);
+ if (try_op->name)
+ fprintf(stderr, "%s", "|");
}
- fprintf(stderr,"%s",">\n");
+ fprintf(stderr, "%s", ">\n");
}
int main(int argc, char *argv[])
@@ -415,23 +438,25 @@ int main(int argc, char *argv[])
int ret = EXIT_SUCCESS;
struct credential_operation const *try_op = credential_helper_ops;
- struct credential cred = CREDENTIAL_INIT;
+ struct credential cred = CREDENTIAL_INIT;
if (!argv[1]) {
usage(argv[0]);
- goto out;
+ exit(EXIT_FAILURE);
}
+ g_set_application_name("Git Credential Helper");
+
/* lookup operation callback */
- while(try_op->name && strcmp(argv[1], try_op->name))
+ while (try_op->name && strcmp(argv[1], try_op->name))
try_op++;
/* unsupported operation given -- ignore silently */
- if(!try_op->name || !try_op->op)
+ if (!try_op->name || !try_op->op)
goto out;
ret = credential_read(&cred);
- if(ret)
+ if (ret)
goto out;
/* perform credential operation */
diff --git a/contrib/credential/libsecret/Makefile b/contrib/credential/libsecret/Makefile
new file mode 100644
index 0000000000..3e67552cc5
--- /dev/null
+++ b/contrib/credential/libsecret/Makefile
@@ -0,0 +1,25 @@
+MAIN:=git-credential-libsecret
+all:: $(MAIN)
+
+CC = gcc
+RM = rm -f
+CFLAGS = -g -O2 -Wall
+PKG_CONFIG = pkg-config
+
+-include ../../../config.mak.autogen
+-include ../../../config.mak
+
+INCS:=$(shell $(PKG_CONFIG) --cflags libsecret-1 glib-2.0)
+LIBS:=$(shell $(PKG_CONFIG) --libs libsecret-1 glib-2.0)
+
+SRCS:=$(MAIN).c
+OBJS:=$(SRCS:.c=.o)
+
+%.o: %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(INCS) -o $@ -c $<
+
+$(MAIN): $(OBJS)
+ $(CC) -o $@ $(LDFLAGS) $^ $(LIBS)
+
+clean:
+ @$(RM) $(MAIN) $(OBJS)
diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c
new file mode 100644
index 0000000000..2c5d76d789
--- /dev/null
+++ b/contrib/credential/libsecret/git-credential-libsecret.c
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2011 John Szakmeister <john@szakmeister.net>
+ * 2012 Philipp A. Hartmann <pah@qo.cx>
+ * 2016 Mantas MikulÄ—nas <grawity@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Credits:
+ * - GNOME Keyring API handling originally written by John Szakmeister
+ * - ported to credential helper API by Philipp A. Hartmann
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <libsecret/secret.h>
+
+/*
+ * This credential struct and API is simplified from git's credential.{h,c}
+ */
+struct credential {
+ char *protocol;
+ char *host;
+ unsigned short port;
+ char *path;
+ char *username;
+ char *password;
+};
+
+#define CREDENTIAL_INIT { 0 }
+
+typedef int (*credential_op_cb)(struct credential *);
+
+struct credential_operation {
+ char *name;
+ credential_op_cb op;
+};
+
+#define CREDENTIAL_OP_END { NULL, NULL }
+
+/* ----------------- Secret Service functions ----------------- */
+
+static char *make_label(struct credential *c)
+{
+ if (c->port)
+ return g_strdup_printf("Git: %s://%s:%hu/%s",
+ c->protocol, c->host, c->port, c->path ? c->path : "");
+ else
+ return g_strdup_printf("Git: %s://%s/%s",
+ c->protocol, c->host, c->path ? c->path : "");
+}
+
+static GHashTable *make_attr_list(struct credential *c)
+{
+ GHashTable *al = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+
+ if (c->username)
+ g_hash_table_insert(al, "user", g_strdup(c->username));
+ if (c->protocol)
+ g_hash_table_insert(al, "protocol", g_strdup(c->protocol));
+ if (c->host)
+ g_hash_table_insert(al, "server", g_strdup(c->host));
+ if (c->port)
+ g_hash_table_insert(al, "port", g_strdup_printf("%hu", c->port));
+ if (c->path)
+ g_hash_table_insert(al, "object", g_strdup(c->path));
+
+ return al;
+}
+
+static int keyring_get(struct credential *c)
+{
+ SecretService *service = NULL;
+ GHashTable *attributes = NULL;
+ GError *error = NULL;
+ GList *items = NULL;
+
+ if (!c->protocol || !(c->host || c->path))
+ return EXIT_FAILURE;
+
+ service = secret_service_get_sync(0, NULL, &error);
+ if (error != NULL) {
+ g_critical("could not connect to Secret Service: %s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ attributes = make_attr_list(c);
+ items = secret_service_search_sync(service,
+ SECRET_SCHEMA_COMPAT_NETWORK,
+ attributes,
+ SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK,
+ NULL,
+ &error);
+ g_hash_table_unref(attributes);
+ if (error != NULL) {
+ g_critical("lookup failed: %s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ if (items != NULL) {
+ SecretItem *item;
+ SecretValue *secret;
+ const char *s;
+
+ item = items->data;
+ secret = secret_item_get_secret(item);
+ attributes = secret_item_get_attributes(item);
+
+ s = g_hash_table_lookup(attributes, "user");
+ if (s) {
+ g_free(c->username);
+ c->username = g_strdup(s);
+ }
+
+ s = secret_value_get_text(secret);
+ if (s) {
+ g_free(c->password);
+ c->password = g_strdup(s);
+ }
+
+ g_hash_table_unref(attributes);
+ secret_value_unref(secret);
+ g_list_free_full(items, g_object_unref);
+ }
+
+ return EXIT_SUCCESS;
+}
+
+
+static int keyring_store(struct credential *c)
+{
+ char *label = NULL;
+ GHashTable *attributes = NULL;
+ GError *error = NULL;
+
+ /*
+ * Sanity check that what we are storing is actually sensible.
+ * In particular, we can't make a URL without a protocol field.
+ * Without either a host or pathname (depending on the scheme),
+ * we have no primary key. And without a username and password,
+ * we are not actually storing a credential.
+ */
+ if (!c->protocol || !(c->host || c->path) ||
+ !c->username || !c->password)
+ return EXIT_FAILURE;
+
+ label = make_label(c);
+ attributes = make_attr_list(c);
+ secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK,
+ attributes,
+ NULL,
+ label,
+ c->password,
+ NULL,
+ &error);
+ g_free(label);
+ g_hash_table_unref(attributes);
+
+ if (error != NULL) {
+ g_critical("store failed: %s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+static int keyring_erase(struct credential *c)
+{
+ GHashTable *attributes = NULL;
+ GError *error = NULL;
+
+ /*
+ * Sanity check that we actually have something to match
+ * against. The input we get is a restrictive pattern,
+ * so technically a blank credential means "erase everything".
+ * But it is too easy to accidentally send this, since it is equivalent
+ * to empty input. So explicitly disallow it, and require that the
+ * pattern have some actual content to match.
+ */
+ if (!c->protocol && !c->host && !c->path && !c->username)
+ return EXIT_FAILURE;
+
+ attributes = make_attr_list(c);
+ secret_password_clearv_sync(SECRET_SCHEMA_COMPAT_NETWORK,
+ attributes,
+ NULL,
+ &error);
+ g_hash_table_unref(attributes);
+
+ if (error != NULL) {
+ g_critical("erase failed: %s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+/*
+ * Table with helper operation callbacks, used by generic
+ * credential helper main function.
+ */
+static struct credential_operation const credential_helper_ops[] = {
+ { "get", keyring_get },
+ { "store", keyring_store },
+ { "erase", keyring_erase },
+ CREDENTIAL_OP_END
+};
+
+/* ------------------ credential functions ------------------ */
+
+static void credential_init(struct credential *c)
+{
+ memset(c, 0, sizeof(*c));
+}
+
+static void credential_clear(struct credential *c)
+{
+ g_free(c->protocol);
+ g_free(c->host);
+ g_free(c->path);
+ g_free(c->username);
+ g_free(c->password);
+
+ credential_init(c);
+}
+
+static int credential_read(struct credential *c)
+{
+ char *buf;
+ size_t line_len;
+ char *key;
+ char *value;
+
+ key = buf = g_malloc(1024);
+
+ while (fgets(buf, 1024, stdin)) {
+ line_len = strlen(buf);
+
+ if (line_len && buf[line_len-1] == '\n')
+ buf[--line_len] = '\0';
+
+ if (!line_len)
+ break;
+
+ value = strchr(buf, '=');
+ if (!value) {
+ g_warning("invalid credential line: %s", key);
+ g_free(buf);
+ return -1;
+ }
+ *value++ = '\0';
+
+ if (!strcmp(key, "protocol")) {
+ g_free(c->protocol);
+ c->protocol = g_strdup(value);
+ } else if (!strcmp(key, "host")) {
+ g_free(c->host);
+ c->host = g_strdup(value);
+ value = strrchr(c->host, ':');
+ if (value) {
+ *value++ = '\0';
+ c->port = atoi(value);
+ }
+ } else if (!strcmp(key, "path")) {
+ g_free(c->path);
+ c->path = g_strdup(value);
+ } else if (!strcmp(key, "username")) {
+ g_free(c->username);
+ c->username = g_strdup(value);
+ } else if (!strcmp(key, "password")) {
+ g_free(c->password);
+ c->password = g_strdup(value);
+ while (*value)
+ *value++ = '\0';
+ }
+ /*
+ * Ignore other lines; we don't know what they mean, but
+ * this future-proofs us when later versions of git do
+ * learn new lines, and the helpers are updated to match.
+ */
+ }
+
+ g_free(buf);
+
+ return 0;
+}
+
+static void credential_write_item(FILE *fp, const char *key, const char *value)
+{
+ if (!value)
+ return;
+ fprintf(fp, "%s=%s\n", key, value);
+}
+
+static void credential_write(const struct credential *c)
+{
+ /* only write username/password, if set */
+ credential_write_item(stdout, "username", c->username);
+ credential_write_item(stdout, "password", c->password);
+}
+
+static void usage(const char *name)
+{
+ struct credential_operation const *try_op = credential_helper_ops;
+ const char *basename = strrchr(name, '/');
+
+ basename = (basename) ? basename + 1 : name;
+ fprintf(stderr, "usage: %s <", basename);
+ while (try_op->name) {
+ fprintf(stderr, "%s", (try_op++)->name);
+ if (try_op->name)
+ fprintf(stderr, "%s", "|");
+ }
+ fprintf(stderr, "%s", ">\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = EXIT_SUCCESS;
+
+ struct credential_operation const *try_op = credential_helper_ops;
+ struct credential cred = CREDENTIAL_INIT;
+
+ if (!argv[1]) {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ g_set_application_name("Git Credential Helper");
+
+ /* lookup operation callback */
+ while (try_op->name && strcmp(argv[1], try_op->name))
+ try_op++;
+
+ /* unsupported operation given -- ignore silently */
+ if (!try_op->name || !try_op->op)
+ goto out;
+
+ ret = credential_read(&cred);
+ if (ret)
+ goto out;
+
+ /* perform credential operation */
+ ret = (*try_op->op)(&cred);
+
+ credential_write(&cred);
+
+out:
+ credential_clear(&cred);
+ return ret;
+}
diff --git a/contrib/credential/netrc/.gitignore b/contrib/credential/netrc/.gitignore
new file mode 100644
index 0000000000..d41cdde84b
--- /dev/null
+++ b/contrib/credential/netrc/.gitignore
@@ -0,0 +1 @@
+git-credential-netrc
diff --git a/contrib/credential/netrc/Makefile b/contrib/credential/netrc/Makefile
new file mode 100644
index 0000000000..c284fb8ac4
--- /dev/null
+++ b/contrib/credential/netrc/Makefile
@@ -0,0 +1,30 @@
+# The default target of this Makefile is...
+all::
+
+SCRIPT_PERL = git-credential-netrc.perl
+GIT_ROOT_DIR = ../../..
+HERE = contrib/credential/netrc
+
+SCRIPT_PERL_FULL = $(patsubst %,$(HERE)/%,$(SCRIPT_PERL))
+
+all:: build
+
+build:
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ build-perl-script
+
+install: build
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ install-perl-script
+
+clean:
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ clean-perl-script
+
+test: build
+ ./t-git-credential-netrc.sh
+
+testverbose: build
+ ./t-git-credential-netrc.sh -d -v
+
+.PHONY: all build install clean test testverbose
diff --git a/contrib/credential/netrc/git-credential-netrc.perl b/contrib/credential/netrc/git-credential-netrc.perl
new file mode 100755
index 0000000000..bc57cc6588
--- /dev/null
+++ b/contrib/credential/netrc/git-credential-netrc.perl
@@ -0,0 +1,440 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+use File::Basename;
+use Git;
+
+my $VERSION = "0.2";
+
+my %options = (
+ help => 0,
+ debug => 0,
+ verbose => 0,
+ insecure => 0,
+ file => [],
+
+ # identical token maps, e.g. host -> host, will be inserted later
+ tmap => {
+ port => 'protocol',
+ machine => 'host',
+ path => 'path',
+ login => 'username',
+ user => 'username',
+ password => 'password',
+ }
+ );
+
+# Map each credential protocol token to itself on the netrc side.
+foreach (values %{$options{tmap}}) {
+ $options{tmap}->{$_} = $_;
+}
+
+# Now, $options{tmap} has a mapping from the netrc format to the Git credential
+# helper protocol.
+
+# Next, we build the reverse token map.
+
+# When $rmap{foo} contains 'bar', that means that what the Git credential helper
+# protocol calls 'bar' is found as 'foo' in the netrc/authinfo file. Keys in
+# %rmap are what we expect to read from the netrc/authinfo file.
+
+my %rmap;
+foreach my $k (keys %{$options{tmap}}) {
+ push @{$rmap{$options{tmap}->{$k}}}, $k;
+}
+
+Getopt::Long::Configure("bundling");
+
+# TODO: maybe allow the token map $options{tmap} to be configurable.
+GetOptions(\%options,
+ "help|h",
+ "debug|d",
+ "insecure|k",
+ "verbose|v",
+ "file|f=s@",
+ 'gpg|g:s',
+ );
+
+if ($options{help}) {
+ my $shortname = basename($0);
+ $shortname =~ s/git-credential-//;
+
+ print <<EOHIPPUS;
+
+$0 [(-f <authfile>)...] [-g <program>] [-d] [-v] [-k] get
+
+Version $VERSION by tzz\@lifelogs.com. License: BSD.
+
+Options:
+
+ -f|--file <authfile>: specify netrc-style files. Files with the .gpg
+ extension will be decrypted by GPG before parsing.
+ Multiple -f arguments are OK. They are processed in
+ order, and the first matching entry found is returned
+ via the credential helper protocol (see below).
+
+ When no -f option is given, .authinfo.gpg, .netrc.gpg,
+ .authinfo, and .netrc files in your home directory are
+ used in this order.
+
+ -g|--gpg <program> : specify the program for GPG. By default, this is the
+ value of gpg.program in the git repository or global
+ option or gpg.
+
+ -k|--insecure : ignore bad file ownership or permissions
+
+ -d|--debug : turn on debugging (developer info)
+
+ -v|--verbose : be more verbose (show files and information found)
+
+To enable this credential helper:
+
+ git config credential.helper '$shortname -f AUTHFILE1 -f AUTHFILE2'
+
+(Note that Git will prepend "git-credential-" to the helper name and look for it
+in the path.)
+
+...and if you want lots of debugging info:
+
+ git config credential.helper '$shortname -f AUTHFILE -d'
+
+...or to see the files opened and data found:
+
+ git config credential.helper '$shortname -f AUTHFILE -v'
+
+Only "get" mode is supported by this credential helper. It opens every
+<authfile> and looks for the first entry that matches the requested search
+criteria:
+
+ 'port|protocol':
+ The protocol that will be used (e.g., https). (protocol=X)
+
+ 'machine|host':
+ The remote hostname for a network credential. (host=X)
+
+ 'path':
+ The path with which the credential will be used. (path=X)
+
+ 'login|user|username':
+ The credential’s username, if we already have one. (username=X)
+
+Thus, when we get this query on STDIN:
+
+host=github.com
+protocol=https
+username=tzz
+
+this credential helper will look for the first entry in every <authfile> that
+matches
+
+machine github.com port https login tzz
+
+OR
+
+machine github.com protocol https login tzz
+
+OR... etc. acceptable tokens as listed above. Any unknown tokens are
+simply ignored.
+
+Then, the helper will print out whatever tokens it got from the entry, including
+"password" tokens, mapping back to Git's helper protocol; e.g. "port" is mapped
+back to "protocol". Any redundant entry tokens (part of the original query) are
+skipped.
+
+Again, note that only the first matching entry from all the <authfile>s,
+processed in the sequence given on the command line, is used.
+
+Netrc/authinfo tokens can be quoted as 'STRING' or "STRING".
+
+No caching is performed by this credential helper.
+
+EOHIPPUS
+
+ exit 0;
+}
+
+my $mode = shift @ARGV;
+
+# Credentials must get a parameter, so die if it's missing.
+die "Syntax: $0 [(-f <authfile>)...] [-d] get" unless defined $mode;
+
+# Only support 'get' mode; with any other unsupported ones we just exit.
+exit 0 unless $mode eq 'get';
+
+my $files = $options{file};
+
+# if no files were given, use a predefined list.
+# note that .gpg files come first
+unless (scalar @$files) {
+ my @candidates = qw[
+ ~/.authinfo.gpg
+ ~/.netrc.gpg
+ ~/.authinfo
+ ~/.netrc
+ ];
+
+ $files = $options{file} = [ map { glob $_ } @candidates ];
+}
+
+load_config(\%options);
+
+my $query = read_credential_data_from_stdin();
+
+FILE:
+foreach my $file (@$files) {
+ my $gpgmode = $file =~ m/\.gpg$/;
+ unless (-r $file) {
+ log_verbose("Unable to read $file; skipping it");
+ next FILE;
+ }
+
+ # the following check is copied from Net::Netrc, for non-GPG files
+ # OS/2 and Win32 do not handle stat in a way compatible with this check :-(
+ unless ($gpgmode || $options{insecure} ||
+ $^O eq 'os2'
+ || $^O eq 'MSWin32'
+ || $^O eq 'MacOS'
+ || $^O =~ /^cygwin/) {
+ my @stat = stat($file);
+
+ if (@stat) {
+ if ($stat[2] & 077) {
+ log_verbose("Insecure $file (mode=%04o); skipping it",
+ $stat[2] & 07777);
+ next FILE;
+ }
+
+ if ($stat[4] != $<) {
+ log_verbose("Not owner of $file; skipping it");
+ next FILE;
+ }
+ }
+ }
+
+ my @entries = load_netrc($file, $gpgmode);
+
+ unless (scalar @entries) {
+ if ($!) {
+ log_verbose("Unable to open $file: $!");
+ } else {
+ log_verbose("No netrc entries found in $file");
+ }
+
+ next FILE;
+ }
+
+ my $entry = find_netrc_entry($query, @entries);
+ if ($entry) {
+ print_credential_data($entry, $query);
+ # we're done!
+ last FILE;
+ }
+}
+
+exit 0;
+
+sub load_netrc {
+ my $file = shift @_;
+ my $gpgmode = shift @_;
+
+ my $io;
+ if ($gpgmode) {
+ my @cmd = ($options{'gpg'}, qw(--decrypt), $file);
+ log_verbose("Using GPG to open $file: [@cmd]");
+ open $io, "-|", @cmd;
+ } else {
+ log_verbose("Opening $file...");
+ open $io, '<', $file;
+ }
+
+ # nothing to do if the open failed (we log the error later)
+ return unless $io;
+
+ # Net::Netrc does this, but the functionality is merged with the file
+ # detection logic, so we have to extract just the part we need
+ my @netrc_entries = net_netrc_loader($io);
+
+ # these entries will use the credential helper protocol token names
+ my @entries;
+
+ foreach my $nentry (@netrc_entries) {
+ my %entry;
+ my $num_port;
+
+ if (!defined $nentry->{machine}) {
+ next;
+ }
+ if (defined $nentry->{port} && $nentry->{port} =~ m/^\d+$/) {
+ $num_port = $nentry->{port};
+ delete $nentry->{port};
+ }
+
+ # create the new entry for the credential helper protocol
+ $entry{$options{tmap}->{$_}} = $nentry->{$_} foreach keys %$nentry;
+
+ # for "host X port Y" where Y is an integer (captured by
+ # $num_port above), set the host to "X:Y"
+ if (defined $entry{host} && defined $num_port) {
+ $entry{host} = join(':', $entry{host}, $num_port);
+ }
+
+ push @entries, \%entry;
+ }
+
+ return @entries;
+}
+
+sub net_netrc_loader {
+ my $fh = shift @_;
+ my @entries;
+ my ($mach, $macdef, $tok, @tok);
+
+ LINE:
+ while (<$fh>) {
+ undef $macdef if /\A\n\Z/;
+
+ if ($macdef) {
+ next LINE;
+ }
+
+ s/^\s*//;
+ chomp;
+
+ while (length && s/^("((?:[^"]+|\\.)*)"|((?:[^\\\s]+|\\.)*))\s*//) {
+ (my $tok = $+) =~ s/\\(.)/$1/g;
+ push(@tok, $tok);
+ }
+
+ TOKEN:
+ while (@tok) {
+ if ($tok[0] eq "default") {
+ shift(@tok);
+ $mach = { machine => undef };
+ next TOKEN;
+ }
+
+ $tok = shift(@tok);
+
+ if ($tok eq "machine") {
+ my $host = shift @tok;
+ $mach = { machine => $host };
+ push @entries, $mach;
+ } elsif (exists $options{tmap}->{$tok}) {
+ unless ($mach) {
+ log_debug("Skipping token $tok because no machine was given");
+ next TOKEN;
+ }
+
+ my $value = shift @tok;
+ unless (defined $value) {
+ log_debug("Token $tok had no value, skipping it.");
+ next TOKEN;
+ }
+
+ # Following line added by rmerrell to remove '/' escape char in .netrc
+ $value =~ s/\/\\/\\/g;
+ $mach->{$tok} = $value;
+ } elsif ($tok eq "macdef") { # we ignore macros
+ next TOKEN unless $mach;
+ my $value = shift @tok;
+ $macdef = 1;
+ }
+ }
+ }
+
+ return @entries;
+}
+
+sub read_credential_data_from_stdin {
+ # the query: start with every token with no value
+ my %q = map { $_ => undef } values(%{$options{tmap}});
+
+ while (<STDIN>) {
+ next unless m/^([^=]+)=(.+)/;
+
+ my ($token, $value) = ($1, $2);
+ die "Unknown search token $token" unless exists $q{$token};
+ $q{$token} = $value;
+ log_debug("We were given search token $token and value $value");
+ }
+
+ foreach (sort keys %q) {
+ log_debug("Searching for %s = %s", $_, $q{$_} || '(any value)');
+ }
+
+ return \%q;
+}
+
+# takes the search tokens and then a list of entries
+# each entry is a hash reference
+sub find_netrc_entry {
+ my $query = shift @_;
+
+ ENTRY:
+ foreach my $entry (@_)
+ {
+ my $entry_text = join ', ', map { "$_=$entry->{$_}" } keys %$entry;
+ foreach my $check (sort keys %$query) {
+ if (!defined $entry->{$check}) {
+ log_debug("OK: entry has no $check token, so any value satisfies check $check");
+ } elsif (defined $query->{$check}) {
+ log_debug("compare %s [%s] to [%s] (entry: %s)",
+ $check,
+ $entry->{$check},
+ $query->{$check},
+ $entry_text);
+ unless ($query->{$check} eq $entry->{$check}) {
+ next ENTRY;
+ }
+ } else {
+ log_debug("OK: any value satisfies check $check");
+ }
+ }
+
+ return $entry;
+ }
+
+ # nothing was found
+ return;
+}
+
+sub print_credential_data {
+ my $entry = shift @_;
+ my $query = shift @_;
+
+ log_debug("entry has passed all the search checks");
+ TOKEN:
+ foreach my $git_token (sort keys %$entry) {
+ log_debug("looking for useful token $git_token");
+ # don't print unknown (to the credential helper protocol) tokens
+ next TOKEN unless exists $query->{$git_token};
+
+ # don't print things asked in the query (the entry matches them)
+ next TOKEN if defined $query->{$git_token};
+
+ log_debug("FOUND: $git_token=$entry->{$git_token}");
+ printf "%s=%s\n", $git_token, $entry->{$git_token};
+ }
+}
+sub load_config {
+ # load settings from git config
+ my $options = shift;
+ # set from command argument, gpg.program option, or default to gpg
+ $options->{'gpg'} //= Git::config('gpg.program')
+ // 'gpg';
+ log_verbose("using $options{'gpg'} for GPG operations");
+}
+sub log_verbose {
+ return unless $options{verbose};
+ printf STDERR @_;
+ printf STDERR "\n";
+}
+
+sub log_debug {
+ return unless $options{debug};
+ printf STDERR @_;
+ printf STDERR "\n";
+}
diff --git a/contrib/credential/netrc/t-git-credential-netrc.sh b/contrib/credential/netrc/t-git-credential-netrc.sh
new file mode 100755
index 0000000000..07227d0228
--- /dev/null
+++ b/contrib/credential/netrc/t-git-credential-netrc.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+(
+ cd ../../../t
+ test_description='git-credential-netrc'
+ . ./test-lib.sh
+
+ if ! test_have_prereq PERL; then
+ skip_all='skipping perl interface tests, perl not available'
+ test_done
+ fi
+
+ perl -MTest::More -e 0 2>/dev/null || {
+ skip_all="Perl Test::More unavailable, skipping test"
+ test_done
+ }
+
+ # set up test repository
+
+ test_expect_success \
+ 'set up test repository' \
+ 'git config --add gpg.program test.git-config-gpg'
+
+ # The external test will outputs its own plan
+ test_external_has_tap=1
+
+ export PERL5LIB="$GITPERLLIB"
+ test_external \
+ 'git-credential-netrc' \
+ perl "$GIT_BUILD_DIR"/contrib/credential/netrc/test.pl
+
+ test_done
+)
diff --git a/contrib/credential/netrc/test.command-option-gpg b/contrib/credential/netrc/test.command-option-gpg
new file mode 100755
index 0000000000..d8f1285d41
--- /dev/null
+++ b/contrib/credential/netrc/test.command-option-gpg
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo machine command-option-gpg login username password password
diff --git a/contrib/credential/netrc/test.git-config-gpg b/contrib/credential/netrc/test.git-config-gpg
new file mode 100755
index 0000000000..65cf594c20
--- /dev/null
+++ b/contrib/credential/netrc/test.git-config-gpg
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo machine git-config-gpg login username password password
diff --git a/contrib/credential/netrc/test.netrc b/contrib/credential/netrc/test.netrc
new file mode 100644
index 0000000000..ba119a937f
--- /dev/null
+++ b/contrib/credential/netrc/test.netrc
@@ -0,0 +1,13 @@
+machine imap login tzz@lifelogs.com port imaps password letmeknow
+machine imap login bob port imaps password bobwillknow
+
+# comment test
+
+machine imap2 login tzz port 1099 password tzzknow
+machine imap2 login bob password bobwillknow
+
+# another command
+
+machine github.com
+ multilinetoken anothervalue
+ login carol password carolknows
diff --git a/contrib/credential/netrc/test.netrc.gpg b/contrib/credential/netrc/test.netrc.gpg
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/credential/netrc/test.netrc.gpg
diff --git a/contrib/credential/netrc/test.pl b/contrib/credential/netrc/test.pl
new file mode 100755
index 0000000000..c0fb3718b2
--- /dev/null
+++ b/contrib/credential/netrc/test.pl
@@ -0,0 +1,139 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+use Test::More qw(no_plan);
+use File::Basename;
+use File::Spec::Functions qw(:DEFAULT rel2abs);
+use IPC::Open2;
+
+BEGIN {
+ # t-git-credential-netrc.sh kicks off our testing, so we have to go
+ # from there.
+ Test::More->builder->current_test(1);
+}
+
+my @global_credential_args = @ARGV;
+my $scriptDir = dirname rel2abs $0;
+my ($netrc, $netrcGpg, $gcNetrc) = map { catfile $scriptDir, $_; }
+ qw(test.netrc
+ test.netrc.gpg
+ git-credential-netrc);
+local $ENV{PATH} = join ':'
+ , $scriptDir
+ , $ENV{PATH}
+ ? $ENV{PATH}
+ : ();
+
+diag "Testing insecure file, nothing should be found\n";
+chmod 0644, $netrc;
+my $cred = run_credential(['-f', $netrc, 'get'],
+ { host => 'github.com' });
+
+ok(scalar keys %$cred == 0, "Got 0 keys from insecure file");
+
+diag "Testing missing file, nothing should be found\n";
+chmod 0644, $netrc;
+$cred = run_credential(['-f', '///nosuchfile///', 'get'],
+ { host => 'github.com' });
+
+ok(scalar keys %$cred == 0, "Got 0 keys from missing file");
+
+chmod 0600, $netrc;
+
+diag "Testing with invalid data\n";
+$cred = run_credential(['-f', $netrc, 'get'],
+ "bad data");
+ok(scalar keys %$cred == 4, "Got first found keys with bad data");
+
+diag "Testing netrc file for a missing corovamilkbar entry\n";
+$cred = run_credential(['-f', $netrc, 'get'],
+ { host => 'corovamilkbar' });
+
+ok(scalar keys %$cred == 0, "Got no corovamilkbar keys");
+
+diag "Testing netrc file for a github.com entry\n";
+$cred = run_credential(['-f', $netrc, 'get'],
+ { host => 'github.com' });
+
+ok(scalar keys %$cred == 2, "Got 2 Github keys");
+
+is($cred->{password}, 'carolknows', "Got correct Github password");
+is($cred->{username}, 'carol', "Got correct Github username");
+
+diag "Testing netrc file for a username-specific entry\n";
+$cred = run_credential(['-f', $netrc, 'get'],
+ { host => 'imap', username => 'bob' });
+
+ok(scalar keys %$cred == 2, "Got 2 username-specific keys");
+
+is($cred->{password}, 'bobwillknow', "Got correct user-specific password");
+is($cred->{protocol}, 'imaps', "Got correct user-specific protocol");
+
+diag "Testing netrc file for a host:port-specific entry\n";
+$cred = run_credential(['-f', $netrc, 'get'],
+ { host => 'imap2:1099' });
+
+ok(scalar keys %$cred == 2, "Got 2 host:port-specific keys");
+
+is($cred->{password}, 'tzzknow', "Got correct host:port-specific password");
+is($cred->{username}, 'tzz', "Got correct host:port-specific username");
+
+diag "Testing netrc file that 'host:port kills host' entry\n";
+$cred = run_credential(['-f', $netrc, 'get'],
+ { host => 'imap2' });
+
+ok(scalar keys %$cred == 2, "Got 2 'host:port kills host' keys");
+
+is($cred->{password}, 'bobwillknow', "Got correct 'host:port kills host' password");
+is($cred->{username}, 'bob', "Got correct 'host:port kills host' username");
+
+diag 'Testing netrc file decryption by git config gpg.program setting\n';
+$cred = run_credential( ['-f', $netrcGpg, 'get']
+ , { host => 'git-config-gpg' }
+ );
+
+ok(scalar keys %$cred == 2, 'Got keys decrypted by git config option');
+
+diag 'Testing netrc file decryption by gpg option\n';
+$cred = run_credential( ['-f', $netrcGpg, '-g', 'test.command-option-gpg', 'get']
+ , { host => 'command-option-gpg' }
+ );
+
+ok(scalar keys %$cred == 2, 'Got keys decrypted by command option');
+
+my $is_passing = eval { Test::More->is_passing };
+exit($is_passing ? 0 : 1) unless $@ =~ /Can't locate object method/;
+
+sub run_credential
+{
+ my $args = shift @_;
+ my $data = shift @_;
+ my $pid = open2(my $chld_out, my $chld_in,
+ $gcNetrc, @global_credential_args,
+ @$args);
+
+ die "Couldn't open pipe to netrc credential helper: $!" unless $pid;
+
+ if (ref $data eq 'HASH')
+ {
+ print $chld_in "$_=$data->{$_}\n" foreach sort keys %$data;
+ }
+ else
+ {
+ print $chld_in "$data\n";
+ }
+
+ close $chld_in;
+ my %ret;
+
+ while (<$chld_out>)
+ {
+ chomp;
+ next unless m/^([^=]+)=(.+)/;
+
+ $ret{$1} = $2;
+ }
+
+ return \%ret;
+}
diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
index 6beed123ab..0b44a9b7cc 100644
--- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c
+++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
@@ -10,6 +10,7 @@ static char *username;
static char *password;
static UInt16 port;
+__attribute__((format (printf, 1, 2)))
static void die(const char *err, ...)
{
char msg[4096];
@@ -127,10 +128,20 @@ static void read_credential(void)
*v++ = '\0';
if (!strcmp(buf, "protocol")) {
- if (!strcmp(v, "https"))
+ if (!strcmp(v, "imap"))
+ protocol = kSecProtocolTypeIMAP;
+ else if (!strcmp(v, "imaps"))
+ protocol = kSecProtocolTypeIMAPS;
+ else if (!strcmp(v, "ftp"))
+ protocol = kSecProtocolTypeFTP;
+ else if (!strcmp(v, "ftps"))
+ protocol = kSecProtocolTypeFTPS;
+ else if (!strcmp(v, "https"))
protocol = kSecProtocolTypeHTTPS;
else if (!strcmp(v, "http"))
protocol = kSecProtocolTypeHTTP;
+ else if (!strcmp(v, "smtp"))
+ protocol = kSecProtocolTypeSMTP;
else /* we don't yet handle other protocols */
exit(0);
}
@@ -154,7 +165,7 @@ static void read_credential(void)
int main(int argc, const char **argv)
{
const char *usage =
- "Usage: git credential-osxkeychain <get|store|erase>";
+ "usage: git credential-osxkeychain <get|store|erase>";
if (!argv[1])
die(usage);
diff --git a/contrib/credential/wincred/Makefile b/contrib/credential/wincred/Makefile
index bad45ca47a..6e992c0866 100644
--- a/contrib/credential/wincred/Makefile
+++ b/contrib/credential/wincred/Makefile
@@ -1,14 +1,22 @@
all: git-credential-wincred.exe
-CC = gcc
-RM = rm -f
-CFLAGS = -O2 -Wall
-
-include ../../../config.mak.autogen
-include ../../../config.mak
+CC ?= gcc
+RM ?= rm -f
+CFLAGS ?= -O2 -Wall
+
+prefix ?= /usr/local
+libexecdir ?= $(prefix)/libexec/git-core
+
+INSTALL ?= install
+
git-credential-wincred.exe : git-credential-wincred.c
$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
+install: git-credential-wincred.exe
+ $(INSTALL) -m 755 $^ $(libexecdir)
+
clean:
$(RM) git-credential-wincred.exe
diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c
index cbaec5f24b..5091048f9c 100644
--- a/contrib/credential/wincred/git-credential-wincred.c
+++ b/contrib/credential/wincred/git-credential-wincred.c
@@ -9,6 +9,9 @@
/* common helpers */
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+__attribute__((format (printf, 1, 2)))
static void die(const char *err, ...)
{
char msg[4096];
@@ -30,14 +33,6 @@ static void *xmalloc(size_t size)
return ret;
}
-static char *xstrdup(const char *str)
-{
- char *ret = strdup(str);
- if (!ret)
- die("Out of memory");
- return ret;
-}
-
/* MinGW doesn't have wincred.h, so we need to define stuff */
typedef struct _CREDENTIAL_ATTRIBUTEW {
@@ -67,95 +62,125 @@ typedef struct _CREDENTIALW {
#define CRED_MAX_ATTRIBUTES 64
typedef BOOL (WINAPI *CredWriteWT)(PCREDENTIALW, DWORD);
-typedef BOOL (WINAPI *CredUnPackAuthenticationBufferWT)(DWORD, PVOID, DWORD,
- LPWSTR, DWORD *, LPWSTR, DWORD *, LPWSTR, DWORD *);
typedef BOOL (WINAPI *CredEnumerateWT)(LPCWSTR, DWORD, DWORD *,
PCREDENTIALW **);
-typedef BOOL (WINAPI *CredPackAuthenticationBufferWT)(DWORD, LPWSTR, LPWSTR,
- PBYTE, DWORD *);
typedef VOID (WINAPI *CredFreeT)(PVOID);
typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD);
-static HMODULE advapi, credui;
+static HMODULE advapi;
static CredWriteWT CredWriteW;
-static CredUnPackAuthenticationBufferWT CredUnPackAuthenticationBufferW;
static CredEnumerateWT CredEnumerateW;
-static CredPackAuthenticationBufferWT CredPackAuthenticationBufferW;
static CredFreeT CredFree;
static CredDeleteWT CredDeleteW;
static void load_cred_funcs(void)
{
/* load DLLs */
- advapi = LoadLibrary("advapi32.dll");
- credui = LoadLibrary("credui.dll");
- if (!advapi || !credui)
- die("failed to load DLLs");
+ advapi = LoadLibraryExA("advapi32.dll", NULL,
+ LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (!advapi)
+ die("failed to load advapi32.dll");
/* get function pointers */
CredWriteW = (CredWriteWT)GetProcAddress(advapi, "CredWriteW");
- CredUnPackAuthenticationBufferW = (CredUnPackAuthenticationBufferWT)
- GetProcAddress(credui, "CredUnPackAuthenticationBufferW");
CredEnumerateW = (CredEnumerateWT)GetProcAddress(advapi,
"CredEnumerateW");
- CredPackAuthenticationBufferW = (CredPackAuthenticationBufferWT)
- GetProcAddress(credui, "CredPackAuthenticationBufferW");
CredFree = (CredFreeT)GetProcAddress(advapi, "CredFree");
CredDeleteW = (CredDeleteWT)GetProcAddress(advapi, "CredDeleteW");
- if (!CredWriteW || !CredUnPackAuthenticationBufferW ||
- !CredEnumerateW || !CredPackAuthenticationBufferW || !CredFree ||
- !CredDeleteW)
+ if (!CredWriteW || !CredEnumerateW || !CredFree || !CredDeleteW)
die("failed to load functions");
}
-static char target_buf[1024];
-static char *protocol, *host, *path, *username;
-static WCHAR *wusername, *password, *target;
+static WCHAR *wusername, *password, *protocol, *host, *path, target[1024];
-static void write_item(const char *what, WCHAR *wbuf)
+static void write_item(const char *what, LPCWSTR wbuf, int wlen)
{
char *buf;
- int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, NULL, 0, NULL,
+
+ if (!wbuf || !wlen) {
+ printf("%s=\n", what);
+ return;
+ }
+
+ int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, NULL, 0, NULL,
FALSE);
buf = xmalloc(len);
- if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, NULL, FALSE))
+ if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, buf, len, NULL, FALSE))
die("WideCharToMultiByte failed!");
printf("%s=", what);
- fwrite(buf, 1, len - 1, stdout);
+ fwrite(buf, 1, len, stdout);
putchar('\n');
free(buf);
}
-static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,
- const char *want)
+/*
+ * Match an (optional) expected string and a delimiter in the target string,
+ * consuming the matched text by updating the target pointer.
+ */
+
+static LPCWSTR wcsstr_last(LPCWSTR str, LPCWSTR find)
+{
+ LPCWSTR res = NULL, pos;
+ for (pos = wcsstr(str, find); pos; pos = wcsstr(pos + 1, find))
+ res = pos;
+ return res;
+}
+
+static int match_part_with_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim, int last)
{
- int i;
- if (!want)
- return 1;
+ LPCWSTR delim_pos, start = *ptarget;
+ int len;
+
+ /* find start of delimiter (or end-of-string if delim is empty) */
+ if (*delim)
+ delim_pos = last ? wcsstr_last(start, delim) : wcsstr(start, delim);
+ else
+ delim_pos = start + wcslen(start);
+
+ /*
+ * match text up to delimiter, or end of string (e.g. the '/' after
+ * host is optional if not followed by a path)
+ */
+ if (delim_pos)
+ len = delim_pos - start;
+ else
+ len = wcslen(start);
+
+ /* update ptarget if we either found a delimiter or need a match */
+ if (delim_pos || want)
+ *ptarget = delim_pos ? delim_pos + wcslen(delim) : start + len;
+
+ return !want || (!wcsncmp(want, start, len) && !want[len]);
+}
- for (i = 0; i < cred->AttributeCount; ++i)
- if (!wcscmp(cred->Attributes[i].Keyword, keyword))
- return !strcmp((const char *)cred->Attributes[i].Value,
- want);
+static int match_part(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
+{
+ return match_part_with_last(ptarget, want, delim, 0);
+}
- return 0; /* not found */
+static int match_part_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
+{
+ return match_part_with_last(ptarget, want, delim, 1);
}
static int match_cred(const CREDENTIALW *cred)
{
- return (!wusername || !wcscmp(wusername, cred->UserName)) &&
- match_attr(cred, L"git_protocol", protocol) &&
- match_attr(cred, L"git_host", host) &&
- match_attr(cred, L"git_path", path);
+ LPCWSTR target = cred->TargetName;
+ if (wusername && wcscmp(wusername, cred->UserName ? cred->UserName : L""))
+ return 0;
+
+ return match_part(&target, L"git", L":") &&
+ match_part(&target, protocol, L"://") &&
+ match_part_last(&target, wusername, L"@") &&
+ match_part(&target, host, L"/") &&
+ match_part(&target, path, L"");
}
static void get_credential(void)
{
- WCHAR *user_buf, *pass_buf;
- DWORD user_buf_size = 0, pass_buf_size = 0;
- CREDENTIALW **creds, *cred = NULL;
+ CREDENTIALW **creds;
DWORD num_creds;
int i;
@@ -165,90 +190,36 @@ static void get_credential(void)
/* search for the first credential that matches username */
for (i = 0; i < num_creds; ++i)
if (match_cred(creds[i])) {
- cred = creds[i];
+ write_item("username", creds[i]->UserName,
+ creds[i]->UserName ? wcslen(creds[i]->UserName) : 0);
+ write_item("password",
+ (LPCWSTR)creds[i]->CredentialBlob,
+ creds[i]->CredentialBlobSize / sizeof(WCHAR));
break;
}
- if (!cred)
- return;
-
- CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
- cred->CredentialBlobSize, NULL, &user_buf_size, NULL, NULL,
- NULL, &pass_buf_size);
-
- user_buf = xmalloc(user_buf_size * sizeof(WCHAR));
- pass_buf = xmalloc(pass_buf_size * sizeof(WCHAR));
-
- if (!CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
- cred->CredentialBlobSize, user_buf, &user_buf_size, NULL, NULL,
- pass_buf, &pass_buf_size))
- die("CredUnPackAuthenticationBuffer failed");
CredFree(creds);
-
- /* zero-terminate (sizes include zero-termination) */
- user_buf[user_buf_size - 1] = L'\0';
- pass_buf[pass_buf_size - 1] = L'\0';
-
- write_item("username", user_buf);
- write_item("password", pass_buf);
-
- free(user_buf);
- free(pass_buf);
-}
-
-static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,
- const char *value)
-{
- attr->Keyword = (LPWSTR)keyword;
- attr->Flags = 0;
- attr->ValueSize = strlen(value) + 1; /* store zero-termination */
- attr->Value = (LPBYTE)value;
}
static void store_credential(void)
{
CREDENTIALW cred;
- BYTE *auth_buf;
- DWORD auth_buf_size = 0;
- CREDENTIAL_ATTRIBUTEW attrs[CRED_MAX_ATTRIBUTES];
if (!wusername || !password)
return;
- /* query buffer size */
- CredPackAuthenticationBufferW(0, wusername, password,
- NULL, &auth_buf_size);
-
- auth_buf = xmalloc(auth_buf_size);
-
- if (!CredPackAuthenticationBufferW(0, wusername, password,
- auth_buf, &auth_buf_size))
- die("CredPackAuthenticationBuffer failed");
-
cred.Flags = 0;
cred.Type = CRED_TYPE_GENERIC;
cred.TargetName = target;
cred.Comment = L"saved by git-credential-wincred";
- cred.CredentialBlobSize = auth_buf_size;
- cred.CredentialBlob = auth_buf;
+ cred.CredentialBlobSize = (wcslen(password)) * sizeof(WCHAR);
+ cred.CredentialBlob = (LPVOID)password;
cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
- cred.AttributeCount = 1;
- cred.Attributes = attrs;
+ cred.AttributeCount = 0;
+ cred.Attributes = NULL;
cred.TargetAlias = NULL;
cred.UserName = wusername;
- write_attr(attrs, L"git_protocol", protocol);
-
- if (host) {
- write_attr(attrs + cred.AttributeCount, L"git_host", host);
- cred.AttributeCount++;
- }
-
- if (path) {
- write_attr(attrs + cred.AttributeCount, L"git_path", path);
- cred.AttributeCount++;
- }
-
if (!CredWriteW(&cred, 0))
die("CredWrite failed");
}
@@ -284,10 +255,13 @@ static void read_credential(void)
while (fgets(buf, sizeof(buf), stdin)) {
char *v;
+ int len = strlen(buf);
+ /* strip trailing CR / LF */
+ while (len && strchr("\r\n", buf[len - 1]))
+ buf[--len] = 0;
- if (!strcmp(buf, "\n"))
+ if (!*buf)
break;
- buf[strlen(buf)-1] = '\0';
v = strchr(buf, '=');
if (!v)
@@ -295,13 +269,12 @@ static void read_credential(void)
*v++ = '\0';
if (!strcmp(buf, "protocol"))
- protocol = xstrdup(v);
+ protocol = utf8_to_utf16_dup(v);
else if (!strcmp(buf, "host"))
- host = xstrdup(v);
+ host = utf8_to_utf16_dup(v);
else if (!strcmp(buf, "path"))
- path = xstrdup(v);
+ path = utf8_to_utf16_dup(v);
else if (!strcmp(buf, "username")) {
- username = xstrdup(v);
wusername = utf8_to_utf16_dup(v);
} else if (!strcmp(buf, "password"))
password = utf8_to_utf16_dup(v);
@@ -313,7 +286,7 @@ static void read_credential(void)
int main(int argc, char *argv[])
{
const char *usage =
- "Usage: git credential-wincred <get|store|erase>\n";
+ "usage: git credential-wincred <get|store|erase>\n";
if (!argv[1])
die(usage);
@@ -330,22 +303,20 @@ int main(int argc, char *argv[])
return 0;
/* prepare 'target', the unique key for the credential */
- strncat(target_buf, "git:", sizeof(target_buf));
- strncat(target_buf, protocol, sizeof(target_buf));
- strncat(target_buf, "://", sizeof(target_buf));
- if (username) {
- strncat(target_buf, username, sizeof(target_buf));
- strncat(target_buf, "@", sizeof(target_buf));
+ wcscpy(target, L"git:");
+ wcsncat(target, protocol, ARRAY_SIZE(target));
+ wcsncat(target, L"://", ARRAY_SIZE(target));
+ if (wusername) {
+ wcsncat(target, wusername, ARRAY_SIZE(target));
+ wcsncat(target, L"@", ARRAY_SIZE(target));
}
if (host)
- strncat(target_buf, host, sizeof(target_buf));
+ wcsncat(target, host, ARRAY_SIZE(target));
if (path) {
- strncat(target_buf, "/", sizeof(target_buf));
- strncat(target_buf, path, sizeof(target_buf));
+ wcsncat(target, L"/", ARRAY_SIZE(target));
+ wcsncat(target, path, ARRAY_SIZE(target));
}
- target = utf8_to_utf16_dup(target_buf);
-
if (!strcmp(argv[1], "get"))
get_credential();
else if (!strcmp(argv[1], "store"))
diff --git a/contrib/diff-highlight/.gitignore b/contrib/diff-highlight/.gitignore
new file mode 100644
index 0000000000..c07454824e
--- /dev/null
+++ b/contrib/diff-highlight/.gitignore
@@ -0,0 +1,2 @@
+shebang.perl
+diff-highlight
diff --git a/contrib/diff-highlight/DiffHighlight.pm b/contrib/diff-highlight/DiffHighlight.pm
new file mode 100644
index 0000000000..376f577737
--- /dev/null
+++ b/contrib/diff-highlight/DiffHighlight.pm
@@ -0,0 +1,285 @@
+package DiffHighlight;
+
+use 5.008;
+use warnings FATAL => 'all';
+use strict;
+
+# Use the correct value for both UNIX and Windows (/dev/null vs nul)
+use File::Spec;
+
+my $NULL = File::Spec->devnull();
+
+# Highlight by reversing foreground and background. You could do
+# other things like bold or underline if you prefer.
+my @OLD_HIGHLIGHT = (
+ color_config('color.diff-highlight.oldnormal'),
+ color_config('color.diff-highlight.oldhighlight', "\x1b[7m"),
+ color_config('color.diff-highlight.oldreset', "\x1b[27m")
+);
+my @NEW_HIGHLIGHT = (
+ color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]),
+ color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]),
+ color_config('color.diff-highlight.newreset', $OLD_HIGHLIGHT[2])
+);
+
+my $RESET = "\x1b[m";
+my $COLOR = qr/\x1b\[[0-9;]*m/;
+my $BORING = qr/$COLOR|\s/;
+
+my @removed;
+my @added;
+my $in_hunk;
+my $graph_indent = 0;
+
+our $line_cb = sub { print @_ };
+our $flush_cb = sub { local $| = 1 };
+
+# Count the visible width of a string, excluding any terminal color sequences.
+sub visible_width {
+ local $_ = shift;
+ my $ret = 0;
+ while (length) {
+ if (s/^$COLOR//) {
+ # skip colors
+ } elsif (s/^.//) {
+ $ret++;
+ }
+ }
+ return $ret;
+}
+
+# Return a substring of $str, omitting $len visible characters from the
+# beginning, where terminal color sequences do not count as visible.
+sub visible_substr {
+ my ($str, $len) = @_;
+ while ($len > 0) {
+ if ($str =~ s/^$COLOR//) {
+ next
+ }
+ $str =~ s/^.//;
+ $len--;
+ }
+ return $str;
+}
+
+sub handle_line {
+ my $orig = shift;
+ local $_ = $orig;
+
+ # match a graph line that begins a commit
+ if (/^(?:$COLOR?\|$COLOR?[ ])* # zero or more leading "|" with space
+ $COLOR?\*$COLOR?[ ] # a "*" with its trailing space
+ (?:$COLOR?\|$COLOR?[ ])* # zero or more trailing "|"
+ [ ]* # trailing whitespace for merges
+ /x) {
+ my $graph_prefix = $&;
+
+ # We must flush before setting graph indent, since the
+ # new commit may be indented differently from what we
+ # queued.
+ flush();
+ $graph_indent = visible_width($graph_prefix);
+
+ } elsif ($graph_indent) {
+ if (length($_) < $graph_indent) {
+ $graph_indent = 0;
+ } else {
+ $_ = visible_substr($_, $graph_indent);
+ }
+ }
+
+ if (!$in_hunk) {
+ $line_cb->($orig);
+ $in_hunk = /^$COLOR*\@\@ /;
+ }
+ elsif (/^$COLOR*-/) {
+ push @removed, $orig;
+ }
+ elsif (/^$COLOR*\+/) {
+ push @added, $orig;
+ }
+ else {
+ flush();
+ $line_cb->($orig);
+ $in_hunk = /^$COLOR*[\@ ]/;
+ }
+
+ # Most of the time there is enough output to keep things streaming,
+ # but for something like "git log -Sfoo", you can get one early
+ # commit and then many seconds of nothing. We want to show
+ # that one commit as soon as possible.
+ #
+ # Since we can receive arbitrary input, there's no optimal
+ # place to flush. Flushing on a blank line is a heuristic that
+ # happens to match git-log output.
+ if (/^$/) {
+ $flush_cb->();
+ }
+}
+
+sub flush {
+ # Flush any queued hunk (this can happen when there is no trailing
+ # context in the final diff of the input).
+ show_hunk(\@removed, \@added);
+ @removed = ();
+ @added = ();
+}
+
+sub highlight_stdin {
+ while (<STDIN>) {
+ handle_line($_);
+ }
+ flush();
+}
+
+# Ideally we would feed the default as a human-readable color to
+# git-config as the fallback value. But diff-highlight does
+# not otherwise depend on git at all, and there are reports
+# of it being used in other settings. Let's handle our own
+# fallback, which means we will work even if git can't be run.
+sub color_config {
+ my ($key, $default) = @_;
+ my $s = `git config --get-color $key 2>$NULL`;
+ return length($s) ? $s : $default;
+}
+
+sub show_hunk {
+ my ($a, $b) = @_;
+
+ # If one side is empty, then there is nothing to compare or highlight.
+ if (!@$a || !@$b) {
+ $line_cb->(@$a, @$b);
+ return;
+ }
+
+ # If we have mismatched numbers of lines on each side, we could try to
+ # be clever and match up similar lines. But for now we are simple and
+ # stupid, and only handle multi-line hunks that remove and add the same
+ # number of lines.
+ if (@$a != @$b) {
+ $line_cb->(@$a, @$b);
+ return;
+ }
+
+ my @queue;
+ for (my $i = 0; $i < @$a; $i++) {
+ my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]);
+ $line_cb->($rm);
+ push @queue, $add;
+ }
+ $line_cb->(@queue);
+}
+
+sub highlight_pair {
+ my @a = split_line(shift);
+ my @b = split_line(shift);
+
+ # Find common prefix, taking care to skip any ansi
+ # color codes.
+ my $seen_plusminus;
+ my ($pa, $pb) = (0, 0);
+ while ($pa < @a && $pb < @b) {
+ if ($a[$pa] =~ /$COLOR/) {
+ $pa++;
+ }
+ elsif ($b[$pb] =~ /$COLOR/) {
+ $pb++;
+ }
+ elsif ($a[$pa] eq $b[$pb]) {
+ $pa++;
+ $pb++;
+ }
+ elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') {
+ $seen_plusminus = 1;
+ $pa++;
+ $pb++;
+ }
+ else {
+ last;
+ }
+ }
+
+ # Find common suffix, ignoring colors.
+ my ($sa, $sb) = ($#a, $#b);
+ while ($sa >= $pa && $sb >= $pb) {
+ if ($a[$sa] =~ /$COLOR/) {
+ $sa--;
+ }
+ elsif ($b[$sb] =~ /$COLOR/) {
+ $sb--;
+ }
+ elsif ($a[$sa] eq $b[$sb]) {
+ $sa--;
+ $sb--;
+ }
+ else {
+ last;
+ }
+ }
+
+ if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) {
+ return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT),
+ highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT);
+ }
+ else {
+ return join('', @a),
+ join('', @b);
+ }
+}
+
+# we split either by $COLOR or by character. This has the side effect of
+# leaving in graph cruft. It works because the graph cruft does not contain "-"
+# or "+"
+sub split_line {
+ local $_ = shift;
+ return utf8::decode($_) ?
+ map { utf8::encode($_); $_ }
+ map { /$COLOR/ ? $_ : (split //) }
+ split /($COLOR+)/ :
+ map { /$COLOR/ ? $_ : (split //) }
+ split /($COLOR+)/;
+}
+
+sub highlight_line {
+ my ($line, $prefix, $suffix, $theme) = @_;
+
+ my $start = join('', @{$line}[0..($prefix-1)]);
+ my $mid = join('', @{$line}[$prefix..$suffix]);
+ my $end = join('', @{$line}[($suffix+1)..$#$line]);
+
+ # If we have a "normal" color specified, then take over the whole line.
+ # Otherwise, we try to just manipulate the highlighted bits.
+ if (defined $theme->[0]) {
+ s/$COLOR//g for ($start, $mid, $end);
+ chomp $end;
+ return join('',
+ $theme->[0], $start, $RESET,
+ $theme->[1], $mid, $RESET,
+ $theme->[0], $end, $RESET,
+ "\n"
+ );
+ } else {
+ return join('',
+ $start,
+ $theme->[1], $mid, $theme->[2],
+ $end
+ );
+ }
+}
+
+# Pairs are interesting to highlight only if we are going to end up
+# highlighting a subset (i.e., not the whole line). Otherwise, the highlighting
+# is just useless noise. We can detect this by finding either a matching prefix
+# or suffix (disregarding boring bits like whitespace and colorization).
+sub is_pair_interesting {
+ my ($a, $pa, $sa, $b, $pb, $sb) = @_;
+ my $prefix_a = join('', @$a[0..($pa-1)]);
+ my $prefix_b = join('', @$b[0..($pb-1)]);
+ my $suffix_a = join('', @$a[($sa+1)..$#$a]);
+ my $suffix_b = join('', @$b[($sb+1)..$#$b]);
+
+ return visible_substr($prefix_a, $graph_indent) !~ /^$COLOR*-$BORING*$/ ||
+ visible_substr($prefix_b, $graph_indent) !~ /^$COLOR*\+$BORING*$/ ||
+ $suffix_a !~ /^$BORING*$/ ||
+ $suffix_b !~ /^$BORING*$/;
+}
diff --git a/contrib/diff-highlight/Makefile b/contrib/diff-highlight/Makefile
new file mode 100644
index 0000000000..f2be7cc924
--- /dev/null
+++ b/contrib/diff-highlight/Makefile
@@ -0,0 +1,23 @@
+all: diff-highlight
+
+PERL_PATH = /usr/bin/perl
+-include ../../config.mak
+
+PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+
+diff-highlight: shebang.perl DiffHighlight.pm diff-highlight.perl
+ cat $^ >$@+
+ chmod +x $@+
+ mv $@+ $@
+
+shebang.perl: FORCE
+ @echo '#!$(PERL_PATH_SQ)' >$@+
+ @cmp $@+ $@ >/dev/null 2>/dev/null || mv $@+ $@
+
+test: all
+ $(MAKE) -C t
+
+clean:
+ $(RM) diff-highlight
+
+.PHONY: FORCE
diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README
index 502e03b305..d4c2343175 100644
--- a/contrib/diff-highlight/README
+++ b/contrib/diff-highlight/README
@@ -58,6 +58,77 @@ following in your git configuration:
diff = diff-highlight | less
---------------------------------------------
+
+Color Config
+------------
+
+You can configure the highlight colors and attributes using git's
+config. The colors for "old" and "new" lines can be specified
+independently. There are two "modes" of configuration:
+
+ 1. You can specify a "highlight" color and a matching "reset" color.
+ This will retain any existing colors in the diff, and apply the
+ "highlight" and "reset" colors before and after the highlighted
+ portion.
+
+ 2. You can specify a "normal" color and a "highlight" color. In this
+ case, existing colors are dropped from that line. The non-highlighted
+ bits of the line get the "normal" color, and the highlights get the
+ "highlight" color.
+
+If no "new" colors are specified, they default to the "old" colors. If
+no "old" colors are specified, the default is to reverse the foreground
+and background for highlighted portions.
+
+Examples:
+
+---------------------------------------------
+# Underline highlighted portions
+[color "diff-highlight"]
+oldHighlight = ul
+oldReset = noul
+---------------------------------------------
+
+---------------------------------------------
+# Varying background intensities
+[color "diff-highlight"]
+oldNormal = "black #f8cbcb"
+oldHighlight = "black #ffaaaa"
+newNormal = "black #cbeecb"
+newHighlight = "black #aaffaa"
+---------------------------------------------
+
+
+Using diff-highlight as a module
+--------------------------------
+
+If you want to pre- or post- process the highlighted lines as part of
+another perl script, you can use the DiffHighlight module. You can
+either "require" it or just cat the module together with your script (to
+avoid run-time dependencies).
+
+Your script may set up one or more of the following variables:
+
+ - $DiffHighlight::line_cb - this should point to a function which is
+ called whenever DiffHighlight has lines (which may contain
+ highlights) to output. The default function prints each line to
+ stdout. Note that the function may be called with multiple lines.
+
+ - $DiffHighlight::flush_cb - this should point to a function which
+ flushes the output (because DiffHighlight believes it has completed
+ processing a logical chunk of input). The default function flushes
+ stdout.
+
+The script may then feed lines, one at a time, to DiffHighlight::handle_line().
+When lines are done processing, they will be fed to $line_cb. Note that
+DiffHighlight may queue up many input lines (to analyze a whole hunk)
+before calling $line_cb. After providing all lines, call
+DiffHighlight::flush() to flush any unprocessed lines.
+
+If you just want to process stdin, DiffHighlight::highlight_stdin()
+is a convenience helper which will loop and flush for you.
+
+
Bugs
----
diff --git a/contrib/diff-highlight/diff-highlight b/contrib/diff-highlight/diff-highlight
deleted file mode 100755
index c4404d49c9..0000000000
--- a/contrib/diff-highlight/diff-highlight
+++ /dev/null
@@ -1,173 +0,0 @@
-#!/usr/bin/perl
-
-use warnings FATAL => 'all';
-use strict;
-
-# Highlight by reversing foreground and background. You could do
-# other things like bold or underline if you prefer.
-my $HIGHLIGHT = "\x1b[7m";
-my $UNHIGHLIGHT = "\x1b[27m";
-my $COLOR = qr/\x1b\[[0-9;]*m/;
-my $BORING = qr/$COLOR|\s/;
-
-my @removed;
-my @added;
-my $in_hunk;
-
-while (<>) {
- if (!$in_hunk) {
- print;
- $in_hunk = /^$COLOR*\@/;
- }
- elsif (/^$COLOR*-/) {
- push @removed, $_;
- }
- elsif (/^$COLOR*\+/) {
- push @added, $_;
- }
- else {
- show_hunk(\@removed, \@added);
- @removed = ();
- @added = ();
-
- print;
- $in_hunk = /^$COLOR*[\@ ]/;
- }
-
- # Most of the time there is enough output to keep things streaming,
- # but for something like "git log -Sfoo", you can get one early
- # commit and then many seconds of nothing. We want to show
- # that one commit as soon as possible.
- #
- # Since we can receive arbitrary input, there's no optimal
- # place to flush. Flushing on a blank line is a heuristic that
- # happens to match git-log output.
- if (!length) {
- local $| = 1;
- }
-}
-
-# Flush any queued hunk (this can happen when there is no trailing context in
-# the final diff of the input).
-show_hunk(\@removed, \@added);
-
-exit 0;
-
-sub show_hunk {
- my ($a, $b) = @_;
-
- # If one side is empty, then there is nothing to compare or highlight.
- if (!@$a || !@$b) {
- print @$a, @$b;
- return;
- }
-
- # If we have mismatched numbers of lines on each side, we could try to
- # be clever and match up similar lines. But for now we are simple and
- # stupid, and only handle multi-line hunks that remove and add the same
- # number of lines.
- if (@$a != @$b) {
- print @$a, @$b;
- return;
- }
-
- my @queue;
- for (my $i = 0; $i < @$a; $i++) {
- my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]);
- print $rm;
- push @queue, $add;
- }
- print @queue;
-}
-
-sub highlight_pair {
- my @a = split_line(shift);
- my @b = split_line(shift);
-
- # Find common prefix, taking care to skip any ansi
- # color codes.
- my $seen_plusminus;
- my ($pa, $pb) = (0, 0);
- while ($pa < @a && $pb < @b) {
- if ($a[$pa] =~ /$COLOR/) {
- $pa++;
- }
- elsif ($b[$pb] =~ /$COLOR/) {
- $pb++;
- }
- elsif ($a[$pa] eq $b[$pb]) {
- $pa++;
- $pb++;
- }
- elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') {
- $seen_plusminus = 1;
- $pa++;
- $pb++;
- }
- else {
- last;
- }
- }
-
- # Find common suffix, ignoring colors.
- my ($sa, $sb) = ($#a, $#b);
- while ($sa >= $pa && $sb >= $pb) {
- if ($a[$sa] =~ /$COLOR/) {
- $sa--;
- }
- elsif ($b[$sb] =~ /$COLOR/) {
- $sb--;
- }
- elsif ($a[$sa] eq $b[$sb]) {
- $sa--;
- $sb--;
- }
- else {
- last;
- }
- }
-
- if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) {
- return highlight_line(\@a, $pa, $sa),
- highlight_line(\@b, $pb, $sb);
- }
- else {
- return join('', @a),
- join('', @b);
- }
-}
-
-sub split_line {
- local $_ = shift;
- return map { /$COLOR/ ? $_ : (split //) }
- split /($COLOR*)/;
-}
-
-sub highlight_line {
- my ($line, $prefix, $suffix) = @_;
-
- return join('',
- @{$line}[0..($prefix-1)],
- $HIGHLIGHT,
- @{$line}[$prefix..$suffix],
- $UNHIGHLIGHT,
- @{$line}[($suffix+1)..$#$line]
- );
-}
-
-# Pairs are interesting to highlight only if we are going to end up
-# highlighting a subset (i.e., not the whole line). Otherwise, the highlighting
-# is just useless noise. We can detect this by finding either a matching prefix
-# or suffix (disregarding boring bits like whitespace and colorization).
-sub is_pair_interesting {
- my ($a, $pa, $sa, $b, $pb, $sb) = @_;
- my $prefix_a = join('', @$a[0..($pa-1)]);
- my $prefix_b = join('', @$b[0..($pb-1)]);
- my $suffix_a = join('', @$a[($sa+1)..$#$a]);
- my $suffix_b = join('', @$b[($sb+1)..$#$b]);
-
- return $prefix_a !~ /^$COLOR*-$BORING*$/ ||
- $prefix_b !~ /^$COLOR*\+$BORING*$/ ||
- $suffix_a !~ /^$BORING*$/ ||
- $suffix_b !~ /^$BORING*$/;
-}
diff --git a/contrib/diff-highlight/diff-highlight.perl b/contrib/diff-highlight/diff-highlight.perl
new file mode 100644
index 0000000000..9b3e9c1f4d
--- /dev/null
+++ b/contrib/diff-highlight/diff-highlight.perl
@@ -0,0 +1,8 @@
+package main;
+
+# Some scripts may not realize that SIGPIPE is being ignored when launching the
+# pager--for instance scripts written in Python.
+$SIG{PIPE} = 'DEFAULT';
+
+DiffHighlight::highlight_stdin();
+exit 0;
diff --git a/contrib/diff-highlight/t/.gitignore b/contrib/diff-highlight/t/.gitignore
new file mode 100644
index 0000000000..7dcbb232cd
--- /dev/null
+++ b/contrib/diff-highlight/t/.gitignore
@@ -0,0 +1,2 @@
+/trash directory*
+/test-results
diff --git a/contrib/diff-highlight/t/Makefile b/contrib/diff-highlight/t/Makefile
new file mode 100644
index 0000000000..5ff5275496
--- /dev/null
+++ b/contrib/diff-highlight/t/Makefile
@@ -0,0 +1,22 @@
+-include ../../../config.mak.autogen
+-include ../../../config.mak
+
+# copied from ../../t/Makefile
+SHELL_PATH ?= $(SHELL)
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
+
+all: test
+test: $(T)
+
+.PHONY: help clean all test $(T)
+
+help:
+ @echo 'Run "$(MAKE) test" to launch test scripts'
+ @echo 'Run "$(MAKE) clean" to remove trash folders'
+
+$(T):
+ @echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
+
+clean:
+ $(RM) -r 'trash directory'.*
diff --git a/contrib/diff-highlight/t/t9400-diff-highlight.sh b/contrib/diff-highlight/t/t9400-diff-highlight.sh
new file mode 100755
index 0000000000..f6f5195d00
--- /dev/null
+++ b/contrib/diff-highlight/t/t9400-diff-highlight.sh
@@ -0,0 +1,341 @@
+#!/bin/sh
+
+test_description='Test diff-highlight'
+
+CURR_DIR=$(pwd)
+TEST_OUTPUT_DIRECTORY=$(pwd)
+TEST_DIRECTORY="$CURR_DIR"/../../../t
+DIFF_HIGHLIGHT="$CURR_DIR"/../diff-highlight
+
+CW="$(printf "\033[7m")" # white
+CR="$(printf "\033[27m")" # reset
+
+. "$TEST_DIRECTORY"/test-lib.sh
+
+if ! test_have_prereq PERL
+then
+ skip_all='skipping diff-highlight tests; perl not available'
+ test_done
+fi
+
+# dh_test is a test helper function which takes 3 file names as parameters. The
+# first 2 files are used to generate diff and commit output, which is then
+# piped through diff-highlight. The 3rd file should contain the expected output
+# of diff-highlight (minus the diff/commit header, ie. everything after and
+# including the first @@ line).
+dh_test () {
+ a="$1" b="$2" &&
+
+ cat >patch.exp &&
+
+ {
+ cat "$a" >file &&
+ git add file &&
+ git commit -m "Add a file" &&
+
+ cat "$b" >file &&
+ git diff file >diff.raw &&
+ git commit -a -m "Update a file" &&
+ git show >commit.raw
+ } >/dev/null &&
+
+ "$DIFF_HIGHLIGHT" <diff.raw | test_strip_patch_header >diff.act &&
+ "$DIFF_HIGHLIGHT" <commit.raw | test_strip_patch_header >commit.act &&
+ test_cmp patch.exp diff.act &&
+ test_cmp patch.exp commit.act
+}
+
+test_strip_patch_header () {
+ sed -n '/^@@/,$p' $*
+}
+
+# dh_test_setup_history generates a contrived graph such that we have at least
+# 1 nesting (E) and 2 nestings (F).
+#
+# A---B master
+# /
+# D---E---F branch
+#
+# git log --all --graph
+# * commit
+# | B
+# | * commit
+# | | F
+# * | commit
+# | | A
+# | * commit
+# |/
+# | E
+# * commit
+# D
+#
+dh_test_setup_history () {
+ echo file1 >file &&
+ git add file &&
+ test_tick &&
+ git commit -m "D" &&
+
+ git checkout -b branch &&
+ echo file2 >file &&
+ test_tick &&
+ git commit -a -m "E" &&
+
+ git checkout master &&
+ echo file2 >file &&
+ test_tick &&
+ git commit -a -m "A" &&
+
+ git checkout branch &&
+ echo file3 >file &&
+ test_tick &&
+ git commit -a -m "F" &&
+
+ git checkout master &&
+ echo file3 >file &&
+ test_tick &&
+ git commit -a -m "B"
+}
+
+left_trim () {
+ "$PERL_PATH" -pe 's/^\s+//'
+}
+
+trim_graph () {
+ # graphs start with * or |
+ # followed by a space or / or \
+ "$PERL_PATH" -pe 's@^((\*|\|)( |/|\\))+@@'
+}
+
+test_expect_success 'diff-highlight highlights the beginning of a line' '
+ cat >a <<-\EOF &&
+ aaa
+ bbb
+ ccc
+ EOF
+
+ cat >b <<-\EOF &&
+ aaa
+ 0bb
+ ccc
+ EOF
+
+ dh_test a b <<-EOF
+ @@ -1,3 +1,3 @@
+ aaa
+ -${CW}b${CR}bb
+ +${CW}0${CR}bb
+ ccc
+ EOF
+'
+
+test_expect_success 'diff-highlight highlights the end of a line' '
+ cat >a <<-\EOF &&
+ aaa
+ bbb
+ ccc
+ EOF
+
+ cat >b <<-\EOF &&
+ aaa
+ bb0
+ ccc
+ EOF
+
+ dh_test a b <<-EOF
+ @@ -1,3 +1,3 @@
+ aaa
+ -bb${CW}b${CR}
+ +bb${CW}0${CR}
+ ccc
+ EOF
+'
+
+test_expect_success 'diff-highlight highlights the middle of a line' '
+ cat >a <<-\EOF &&
+ aaa
+ bbb
+ ccc
+ EOF
+
+ cat >b <<-\EOF &&
+ aaa
+ b0b
+ ccc
+ EOF
+
+ dh_test a b <<-EOF
+ @@ -1,3 +1,3 @@
+ aaa
+ -b${CW}b${CR}b
+ +b${CW}0${CR}b
+ ccc
+ EOF
+'
+
+test_expect_success 'diff-highlight does not highlight whole line' '
+ cat >a <<-\EOF &&
+ aaa
+ bbb
+ ccc
+ EOF
+
+ cat >b <<-\EOF &&
+ aaa
+ 000
+ ccc
+ EOF
+
+ dh_test a b <<-EOF
+ @@ -1,3 +1,3 @@
+ aaa
+ -bbb
+ +000
+ ccc
+ EOF
+'
+
+test_expect_failure 'diff-highlight highlights mismatched hunk size' '
+ cat >a <<-\EOF &&
+ aaa
+ bbb
+ EOF
+
+ cat >b <<-\EOF &&
+ aaa
+ b0b
+ ccc
+ EOF
+
+ dh_test a b <<-EOF
+ @@ -1,3 +1,3 @@
+ aaa
+ -b${CW}b${CR}b
+ +b${CW}0${CR}b
+ +ccc
+ EOF
+'
+
+# These two code points share the same leading byte in UTF-8 representation;
+# a naive byte-wise diff would highlight only the second byte.
+#
+# - U+00f3 ("o" with acute)
+o_accent=$(printf '\303\263')
+# - U+00f8 ("o" with stroke)
+o_stroke=$(printf '\303\270')
+
+test_expect_success 'diff-highlight treats multibyte utf-8 as a unit' '
+ echo "unic${o_accent}de" >a &&
+ echo "unic${o_stroke}de" >b &&
+ dh_test a b <<-EOF
+ @@ -1 +1 @@
+ -unic${CW}${o_accent}${CR}de
+ +unic${CW}${o_stroke}${CR}de
+ EOF
+'
+
+# Unlike the UTF-8 above, these are combining code points which are meant
+# to modify the character preceding them:
+#
+# - U+0301 (combining acute accent)
+combine_accent=$(printf '\314\201')
+# - U+0302 (combining circumflex)
+combine_circum=$(printf '\314\202')
+
+test_expect_failure 'diff-highlight treats combining code points as a unit' '
+ echo "unico${combine_accent}de" >a &&
+ echo "unico${combine_circum}de" >b &&
+ dh_test a b <<-EOF
+ @@ -1 +1 @@
+ -unic${CW}o${combine_accent}${CR}de
+ +unic${CW}o${combine_circum}${CR}de
+ EOF
+'
+
+test_expect_success 'diff-highlight works with the --graph option' '
+ dh_test_setup_history &&
+
+ # date-order so that the commits are interleaved for both
+ # trim graph elements so we can do a diff
+ # trim leading space because our trim_graph is not perfect
+ git log --branches -p --date-order |
+ "$DIFF_HIGHLIGHT" | left_trim >graph.exp &&
+ git log --branches -p --date-order --graph |
+ "$DIFF_HIGHLIGHT" | trim_graph | left_trim >graph.act &&
+ test_cmp graph.exp graph.act
+'
+
+# Just reuse the previous graph test, but with --color. Our trimming
+# doesn't know about color, so just sanity check that something got
+# highlighted.
+test_expect_success 'diff-highlight works with color graph' '
+ git log --branches -p --date-order --graph --color |
+ "$DIFF_HIGHLIGHT" | trim_graph | left_trim >graph &&
+ grep "\[7m" graph
+'
+
+# Most combined diffs won't meet diff-highlight's line-number filter. So we
+# create one here where one side drops a line and the other modifies it. That
+# should result in a diff like:
+#
+# - modified content
+# ++resolved content
+#
+# which naively looks like one side added "+resolved".
+test_expect_success 'diff-highlight ignores combined diffs' '
+ echo "content" >file &&
+ git add file &&
+ git commit -m base &&
+
+ >file &&
+ git commit -am master &&
+
+ git checkout -b other HEAD^ &&
+ echo "modified content" >file &&
+ git commit -am other &&
+
+ test_must_fail git merge master &&
+ echo "resolved content" >file &&
+ git commit -am resolved &&
+
+ cat >expect <<-\EOF &&
+ --- a/file
+ +++ b/file
+ @@@ -1,1 -1,0 +1,1 @@@
+ - modified content
+ ++resolved content
+ EOF
+
+ git show -c | "$DIFF_HIGHLIGHT" >actual.raw &&
+ sed -n "/^---/,\$p" <actual.raw >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'diff-highlight handles --graph with leading dash' '
+ cat >file <<-\EOF &&
+ before
+ the old line
+ -leading dash
+ EOF
+ git add file &&
+ git commit -m before &&
+
+ sed s/old/new/ <file >file.tmp &&
+ mv file.tmp file &&
+ git add file &&
+ git commit -m after &&
+
+ cat >expect <<-EOF &&
+ --- a/file
+ +++ b/file
+ @@ -1,3 +1,3 @@
+ before
+ -the ${CW}old${CR} line
+ +the ${CW}new${CR} line
+ -leading dash
+ EOF
+ git log --graph -p -1 | "$DIFF_HIGHLIGHT" >actual.raw &&
+ trim_graph <actual.raw | sed -n "/^---/,\$p" >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/contrib/diffall/README b/contrib/diffall/README
deleted file mode 100644
index 507f17dcd6..0000000000
--- a/contrib/diffall/README
+++ /dev/null
@@ -1,31 +0,0 @@
-The git-diffall script provides a directory based diff mechanism
-for git.
-
-To determine what diff viewer is used, the script requires either
-the 'diff.tool' or 'merge.tool' configuration option to be set.
-
-This script is compatible with most common forms used to specify a
-range of revisions to diff:
-
- 1. git diffall: shows diff between working tree and staged changes
- 2. git diffall --cached [<commit>]: shows diff between staged
- changes and HEAD (or other named commit)
- 3. git diffall <commit>: shows diff between working tree and named
- commit
- 4. git diffall <commit> <commit>: show diff between two named commits
- 5. git diffall <commit>..<commit>: same as above
- 6. git diffall <commit>...<commit>: show the changes on the branch
- containing and up to the second, starting at a common ancestor
- of both <commit>
-
-Note: all forms take an optional path limiter [-- <path>*]
-
-The '--extcmd=<command>' option allows the user to specify a custom
-command for viewing diffs. When given, configured defaults are
-ignored and the script runs $command $LOCAL $REMOTE. Additionally,
-$BASE is set in the environment.
-
-This script is based on an example provided by Thomas Rast on the
-Git list [1]:
-
-[1] http://thread.gmane.org/gmane.comp.version-control.git/124807
diff --git a/contrib/diffall/git-diffall b/contrib/diffall/git-diffall
deleted file mode 100755
index 84f2b654d7..0000000000
--- a/contrib/diffall/git-diffall
+++ /dev/null
@@ -1,257 +0,0 @@
-#!/bin/sh
-# Copyright 2010 - 2012, Tim Henigan <tim.henigan@gmail.com>
-#
-# Perform a directory diff between commits in the repository using
-# the external diff or merge tool specified in the user's config.
-
-USAGE='[--cached] [--copy-back] [-x|--extcmd=<command>] <commit>{0,2} [-- <path>*]
-
- --cached Compare to the index rather than the working tree.
-
- --copy-back Copy files back to the working tree when the diff
- tool exits (in case they were modified by the
- user). This option is only valid if the diff
- compared with the working tree.
-
- -x=<command>
- --extcmd=<command> Specify a custom command for viewing diffs.
- git-diffall ignores the configured defaults and
- runs $command $LOCAL $REMOTE when this option is
- specified. Additionally, $BASE is set in the
- environment.
-'
-
-SUBDIRECTORY_OK=1
-. "$(git --exec-path)/git-sh-setup"
-
-TOOL_MODE=diff
-. "$(git --exec-path)/git-mergetool--lib"
-
-merge_tool="$(get_merge_tool)"
-if test -z "$merge_tool"
-then
- echo "Error: Either the 'diff.tool' or 'merge.tool' option must be set."
- usage
-fi
-
-start_dir=$(pwd)
-
-# All the file paths returned by the diff command are relative to the root
-# of the working copy. So if the script is called from a subdirectory, it
-# must switch to the root of working copy before trying to use those paths.
-cdup=$(git rev-parse --show-cdup) &&
-cd "$cdup" || {
- echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
- exit 1
-}
-
-# set up temp dir
-tmp=$(perl -e 'use File::Temp qw(tempdir);
- $t=tempdir("/tmp/git-diffall.XXXXX") or exit(1);
- print $t') || exit 1
-trap 'rm -rf "$tmp"' EXIT
-
-left=
-right=
-paths=
-dashdash_seen=
-compare_staged=
-merge_base=
-left_dir=
-right_dir=
-diff_tool=
-copy_back=
-
-while test $# != 0
-do
- case "$1" in
- -h|--h|--he|--hel|--help)
- usage
- ;;
- --cached)
- compare_staged=1
- ;;
- --copy-back)
- copy_back=1
- ;;
- -x|--e|--ex|--ext|--extc|--extcm|--extcmd)
- if test $# = 1
- then
- echo You must specify the tool for use with --extcmd
- usage
- else
- diff_tool=$2
- shift
- fi
- ;;
- --)
- dashdash_seen=1
- ;;
- -*)
- echo Invalid option: "$1"
- usage
- ;;
- *)
- # could be commit, commit range or path limiter
- case "$1" in
- *...*)
- left=${1%...*}
- right=${1#*...}
- merge_base=1
- ;;
- *..*)
- left=${1%..*}
- right=${1#*..}
- ;;
- *)
- if test -n "$dashdash_seen"
- then
- paths="$paths$1 "
- elif test -z "$left"
- then
- left=$1
- elif test -z "$right"
- then
- right=$1
- else
- paths="$paths$1 "
- fi
- ;;
- esac
- ;;
- esac
- shift
-done
-
-# Determine the set of files which changed
-if test -n "$left" && test -n "$right"
-then
- left_dir="cmt-$(git rev-parse --short $left)"
- right_dir="cmt-$(git rev-parse --short $right)"
-
- if test -n "$compare_staged"
- then
- usage
- elif test -n "$merge_base"
- then
- git diff --name-only "$left"..."$right" -- $paths >"$tmp/filelist"
- else
- git diff --name-only "$left" "$right" -- $paths >"$tmp/filelist"
- fi
-elif test -n "$left"
-then
- left_dir="cmt-$(git rev-parse --short $left)"
-
- if test -n "$compare_staged"
- then
- right_dir="staged"
- git diff --name-only --cached "$left" -- $paths >"$tmp/filelist"
- else
- right_dir="working_tree"
- git diff --name-only "$left" -- $paths >"$tmp/filelist"
- fi
-else
- left_dir="HEAD"
-
- if test -n "$compare_staged"
- then
- right_dir="staged"
- git diff --name-only --cached -- $paths >"$tmp/filelist"
- else
- right_dir="working_tree"
- git diff --name-only -- $paths >"$tmp/filelist"
- fi
-fi
-
-# Exit immediately if there are no diffs
-if test ! -s "$tmp/filelist"
-then
- exit 0
-fi
-
-if test -n "$copy_back" && test "$right_dir" != "working_tree"
-then
- echo "--copy-back is only valid when diff includes the working tree."
- exit 1
-fi
-
-# Create the named tmp directories that will hold the files to be compared
-mkdir -p "$tmp/$left_dir" "$tmp/$right_dir"
-
-# Populate the tmp/right_dir directory with the files to be compared
-while read name
-do
- if test -n "$right"
- then
- ls_list=$(git ls-tree $right "$name")
- if test -n "$ls_list"
- then
- mkdir -p "$tmp/$right_dir/$(dirname "$name")"
- git show "$right":"$name" >"$tmp/$right_dir/$name" || true
- fi
- elif test -n "$compare_staged"
- then
- ls_list=$(git ls-files -- "$name")
- if test -n "$ls_list"
- then
- mkdir -p "$tmp/$right_dir/$(dirname "$name")"
- git show :"$name" >"$tmp/$right_dir/$name"
- fi
- else
- if test -e "$name"
- then
- mkdir -p "$tmp/$right_dir/$(dirname "$name")"
- cp "$name" "$tmp/$right_dir/$name"
- fi
- fi
-done < "$tmp/filelist"
-
-# Populate the tmp/left_dir directory with the files to be compared
-while read name
-do
- if test -n "$left"
- then
- ls_list=$(git ls-tree $left "$name")
- if test -n "$ls_list"
- then
- mkdir -p "$tmp/$left_dir/$(dirname "$name")"
- git show "$left":"$name" >"$tmp/$left_dir/$name" || true
- fi
- else
- if test -n "$compare_staged"
- then
- ls_list=$(git ls-tree HEAD "$name")
- if test -n "$ls_list"
- then
- mkdir -p "$tmp/$left_dir/$(dirname "$name")"
- git show HEAD:"$name" >"$tmp/$left_dir/$name"
- fi
- else
- mkdir -p "$tmp/$left_dir/$(dirname "$name")"
- git show :"$name" >"$tmp/$left_dir/$name"
- fi
- fi
-done < "$tmp/filelist"
-
-LOCAL="$tmp/$left_dir"
-REMOTE="$tmp/$right_dir"
-
-if test -n "$diff_tool"
-then
- export BASE
- eval $diff_tool '"$LOCAL"' '"$REMOTE"'
-else
- run_merge_tool "$merge_tool" false
-fi
-
-# Copy files back to the working dir, if requested
-if test -n "$copy_back" && test "$right_dir" = "working_tree"
-then
- cd "$start_dir"
- git_top_dir=$(git rev-parse --show-toplevel)
- find "$tmp/$right_dir" -type f |
- while read file
- do
- cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}"
- done
-fi
diff --git a/contrib/emacs/.gitignore b/contrib/emacs/.gitignore
deleted file mode 100644
index c531d9867f..0000000000
--- a/contrib/emacs/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.elc
diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile
deleted file mode 100644
index 24d9312941..0000000000
--- a/contrib/emacs/Makefile
+++ /dev/null
@@ -1,21 +0,0 @@
-## Build and install stuff
-
-EMACS = emacs
-
-ELC = git.elc git-blame.elc
-INSTALL ?= install
-INSTALL_ELC = $(INSTALL) -m 644
-prefix ?= $(HOME)
-emacsdir = $(prefix)/share/emacs/site-lisp
-RM ?= rm -f
-
-all: $(ELC)
-
-install: all
- $(INSTALL) -d $(DESTDIR)$(emacsdir)
- $(INSTALL_ELC) $(ELC:.elc=.el) $(ELC) $(DESTDIR)$(emacsdir)
-
-%.elc: %.el
- $(EMACS) -batch -f batch-byte-compile $<
-
-clean:; $(RM) $(ELC)
diff --git a/contrib/emacs/README b/contrib/emacs/README
index 82368bdbff..977a16f1e3 100644
--- a/contrib/emacs/README
+++ b/contrib/emacs/README
@@ -1,30 +1,24 @@
-This directory contains various modules for Emacs support.
+This directory used to contain various modules for Emacs support.
-To make the modules available to Emacs, you should add this directory
-to your load-path, and then require the modules you want. This can be
-done by adding to your .emacs something like this:
+These were added shortly after Git was first released. Since then
+Emacs's own support for Git got better than what was offered by these
+modes. There are also popular 3rd-party Git modes such as Magit which
+offer replacements for these.
- (add-to-list 'load-path ".../git/contrib/emacs")
- (require 'git)
- (require 'git-blame)
-
-
-The following modules are available:
+The following modules were available, and can be dug up from the Git
+history:
* git.el:
- Status manager that displays the state of all the files of the
- project, and provides easy access to the most frequently used git
- commands. The user interface is as far as possible compatible with
- the pcl-cvs mode. It can be started with `M-x git-status'.
+ Wrapper for "git status" that provided access to other git commands.
+
+ Modern alternatives to this include Magit, and VC mode that ships
+ with Emacs.
* git-blame.el:
- Emacs implementation of incremental git-blame. When you turn it on
- while viewing a file, the editor buffer will be updated by setting
- the background of individual lines to a color that reflects which
- commit it comes from. And when you move around the buffer, a
- one-line summary will be shown in the echo area.
+ A wrapper for "git blame" written before Emacs's own vc-annotate
+ mode learned to invoke git-blame, which can be done via C-x v g.
* vc-git.el:
diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el
index e671f6c1c6..6a8a2b8ff1 100644
--- a/contrib/emacs/git-blame.el
+++ b/contrib/emacs/git-blame.el
@@ -1,484 +1,6 @@
-;;; git-blame.el --- Minor mode for incremental blame for Git -*- coding: utf-8 -*-
-;;
-;; Copyright (C) 2007 David KÃ¥gedal
-;;
-;; Authors: David KÃ¥gedal <davidk@lysator.liu.se>
-;; Created: 31 Jan 2007
-;; Message-ID: <87iren2vqx.fsf@morpheus.local>
-;; License: GPL
-;; Keywords: git, version control, release management
-;;
-;; Compatibility: Emacs21, Emacs22 and EmacsCVS
-;; Git 1.5 and up
-
-;; This file is *NOT* part of GNU Emacs.
-;; This file is distributed under the same terms as GNU Emacs.
-
-;; This program is free software; you can redistribute it and/or
-;; modify it under the terms of the GNU General Public License as
-;; published by the Free Software Foundation; either version 2 of
-;; the License, or (at your option) any later version.
-
-;; This program is distributed in the hope that it will be
-;; useful, but WITHOUT ANY WARRANTY; without even the implied
-;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-;; PURPOSE. See the GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public
-;; License along with this program; if not, write to the Free
-;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
-;; MA 02111-1307 USA
-
-;; http://www.fsf.org/copyleft/gpl.html
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;;
-;;; Commentary:
-;;
-;; Here is an Emacs implementation of incremental git-blame. When you
-;; turn it on while viewing a file, the editor buffer will be updated by
-;; setting the background of individual lines to a color that reflects
-;; which commit it comes from. And when you move around the buffer, a
-;; one-line summary will be shown in the echo area.
-
-;;; Installation:
-;;
-;; To use this package, put it somewhere in `load-path' (or add
-;; directory with git-blame.el to `load-path'), and add the following
-;; line to your .emacs:
-;;
-;; (require 'git-blame)
-;;
-;; If you do not want to load this package before it is necessary, you
-;; can make use of the `autoload' feature, e.g. by adding to your .emacs
-;; the following lines
-;;
-;; (autoload 'git-blame-mode "git-blame"
-;; "Minor mode for incremental blame for Git." t)
-;;
-;; Then first use of `M-x git-blame-mode' would load the package.
-
-;;; Compatibility:
-;;
-;; It requires GNU Emacs 21 or later and Git 1.5.0 and up
-;;
-;; If you'are using Emacs 20, try changing this:
-;;
-;; (overlay-put ovl 'face (list :background
-;; (cdr (assq 'color (cddddr info)))))
-;;
-;; to
-;;
-;; (overlay-put ovl 'face (cons 'background-color
-;; (cdr (assq 'color (cddddr info)))))
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;;
-;;; Code:
-
-(eval-when-compile (require 'cl)) ; to use `push', `pop'
-(require 'format-spec)
-
-(defface git-blame-prefix-face
- '((((background dark)) (:foreground "gray"
- :background "black"))
- (((background light)) (:foreground "gray"
- :background "white"))
- (t (:weight bold)))
- "The face used for the hash prefix."
- :group 'git-blame)
-
-(defgroup git-blame nil
- "A minor mode showing Git blame information."
- :group 'git
- :link '(function-link git-blame-mode))
-
-
-(defcustom git-blame-use-colors t
- "Use colors to indicate commits in `git-blame-mode'."
- :type 'boolean
- :group 'git-blame)
-
-(defcustom git-blame-prefix-format
- "%h %20A:"
- "The format of the prefix added to each line in `git-blame'
-mode. The format is passed to `format-spec' with the following format keys:
-
- %h - the abbreviated hash
- %H - the full hash
- %a - the author name
- %A - the author email
- %c - the committer name
- %C - the committer email
- %s - the commit summary
-"
- :group 'git-blame)
-
-(defcustom git-blame-mouseover-format
- "%h %a %A: %s"
- "The format of the description shown when pointing at a line in
-`git-blame' mode. The format string is passed to `format-spec'
-with the following format keys:
-
- %h - the abbreviated hash
- %H - the full hash
- %a - the author name
- %A - the author email
- %c - the committer name
- %C - the committer email
- %s - the commit summary
-"
- :group 'git-blame)
-
-
-(defun git-blame-color-scale (&rest elements)
- "Given a list, returns a list of triples formed with each
-elements of the list.
-
-a b => bbb bba bab baa abb aba aaa aab"
- (let (result)
- (dolist (a elements)
- (dolist (b elements)
- (dolist (c elements)
- (setq result (cons (format "#%s%s%s" a b c) result)))))
- result))
-
-;; (git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c") =>
-;; ("#3c3c3c" "#3c3c14" "#3c3c34" "#3c3c2c" "#3c3c1c" "#3c3c24"
-;; "#3c3c04" "#3c3c0c" "#3c143c" "#3c1414" "#3c1434" "#3c142c" ...)
-
-(defmacro git-blame-random-pop (l)
- "Select a random element from L and returns it. Also remove
-selected element from l."
- ;; only works on lists with unique elements
- `(let ((e (elt ,l (random (length ,l)))))
- (setq ,l (remove e ,l))
- e))
-
-(defvar git-blame-log-oneline-format
- "format:[%cr] %cn: %s"
- "*Formatting option used for describing current line in the minibuffer.
-
-This option is used to pass to git log --pretty= command-line option,
-and describe which commit the current line was made.")
-
-(defvar git-blame-dark-colors
- (git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c")
- "*List of colors (format #RGB) to use in a dark environment.
-
-To check out the list, evaluate (list-colors-display git-blame-dark-colors).")
-
-(defvar git-blame-light-colors
- (git-blame-color-scale "c4" "d4" "cc" "dc" "f4" "e4" "fc" "ec")
- "*List of colors (format #RGB) to use in a light environment.
-
-To check out the list, evaluate (list-colors-display git-blame-light-colors).")
-
-(defvar git-blame-colors '()
- "Colors used by git-blame. The list is built once when activating git-blame
-minor mode.")
-
-(defvar git-blame-ancient-color "dark green"
- "*Color to be used for ancient commit.")
-
-(defvar git-blame-autoupdate t
- "*Automatically update the blame display while editing")
-
-(defvar git-blame-proc nil
- "The running git-blame process")
-(make-variable-buffer-local 'git-blame-proc)
-
-(defvar git-blame-overlays nil
- "The git-blame overlays used in the current buffer.")
-(make-variable-buffer-local 'git-blame-overlays)
-
-(defvar git-blame-cache nil
- "A cache of git-blame information for the current buffer")
-(make-variable-buffer-local 'git-blame-cache)
-
-(defvar git-blame-idle-timer nil
- "An idle timer that updates the blame")
-(make-variable-buffer-local 'git-blame-cache)
-
-(defvar git-blame-update-queue nil
- "A queue of update requests")
-(make-variable-buffer-local 'git-blame-update-queue)
-
-;; FIXME: docstrings
-(defvar git-blame-file nil)
-(defvar git-blame-current nil)
-
-(defvar git-blame-mode nil)
-(make-variable-buffer-local 'git-blame-mode)
-
-(defvar git-blame-mode-line-string " blame"
- "String to display on the mode line when git-blame is active.")
-
-(or (assq 'git-blame-mode minor-mode-alist)
- (setq minor-mode-alist
- (cons '(git-blame-mode git-blame-mode-line-string) minor-mode-alist)))
-
-;;;###autoload
-(defun git-blame-mode (&optional arg)
- "Toggle minor mode for displaying Git blame
-
-With prefix ARG, turn the mode on if ARG is positive."
- (interactive "P")
- (cond
- ((null arg)
- (if git-blame-mode (git-blame-mode-off) (git-blame-mode-on)))
- ((> (prefix-numeric-value arg) 0) (git-blame-mode-on))
- (t (git-blame-mode-off))))
-
-(defun git-blame-mode-on ()
- "Turn on git-blame mode.
-
-See also function `git-blame-mode'."
- (make-local-variable 'git-blame-colors)
- (if git-blame-autoupdate
- (add-hook 'after-change-functions 'git-blame-after-change nil t)
- (remove-hook 'after-change-functions 'git-blame-after-change t))
- (git-blame-cleanup)
- (let ((bgmode (cdr (assoc 'background-mode (frame-parameters)))))
- (if (eq bgmode 'dark)
- (setq git-blame-colors git-blame-dark-colors)
- (setq git-blame-colors git-blame-light-colors)))
- (setq git-blame-cache (make-hash-table :test 'equal))
- (setq git-blame-mode t)
- (git-blame-run))
-
-(defun git-blame-mode-off ()
- "Turn off git-blame mode.
-
-See also function `git-blame-mode'."
- (git-blame-cleanup)
- (if git-blame-idle-timer (cancel-timer git-blame-idle-timer))
- (setq git-blame-mode nil))
-
-;;;###autoload
-(defun git-reblame ()
- "Recalculate all blame information in the current buffer"
- (interactive)
- (unless git-blame-mode
- (error "Git-blame is not active"))
-
- (git-blame-cleanup)
- (git-blame-run))
-
-(defun git-blame-run (&optional startline endline)
- (if git-blame-proc
- ;; Should maybe queue up a new run here
- (message "Already running git blame")
- (let ((display-buf (current-buffer))
- (blame-buf (get-buffer-create
- (concat " git blame for " (buffer-name))))
- (args '("--incremental" "--contents" "-")))
- (if startline
- (setq args (append args
- (list "-L" (format "%d,%d" startline endline)))))
- (setq args (append args
- (list (file-name-nondirectory buffer-file-name))))
- (setq git-blame-proc
- (apply 'start-process
- "git-blame" blame-buf
- "git" "blame"
- args))
- (with-current-buffer blame-buf
- (erase-buffer)
- (make-local-variable 'git-blame-file)
- (make-local-variable 'git-blame-current)
- (setq git-blame-file display-buf)
- (setq git-blame-current nil))
- (set-process-filter git-blame-proc 'git-blame-filter)
- (set-process-sentinel git-blame-proc 'git-blame-sentinel)
- (process-send-region git-blame-proc (point-min) (point-max))
- (process-send-eof git-blame-proc))))
-
-(defun remove-git-blame-text-properties (start end)
- (let ((modified (buffer-modified-p))
- (inhibit-read-only t))
- (remove-text-properties start end '(point-entered nil))
- (set-buffer-modified-p modified)))
-
-(defun git-blame-cleanup ()
- "Remove all blame properties"
- (mapc 'delete-overlay git-blame-overlays)
- (setq git-blame-overlays nil)
- (remove-git-blame-text-properties (point-min) (point-max)))
-
-(defun git-blame-update-region (start end)
- "Rerun blame to get updates between START and END"
- (let ((overlays (overlays-in start end)))
- (while overlays
- (let ((overlay (pop overlays)))
- (if (< (overlay-start overlay) start)
- (setq start (overlay-start overlay)))
- (if (> (overlay-end overlay) end)
- (setq end (overlay-end overlay)))
- (setq git-blame-overlays (delete overlay git-blame-overlays))
- (delete-overlay overlay))))
- (remove-git-blame-text-properties start end)
- ;; We can be sure that start and end are at line breaks
- (git-blame-run (1+ (count-lines (point-min) start))
- (count-lines (point-min) end)))
-
-(defun git-blame-sentinel (proc status)
- (with-current-buffer (process-buffer proc)
- (with-current-buffer git-blame-file
- (setq git-blame-proc nil)
- (if git-blame-update-queue
- (git-blame-delayed-update))))
- ;;(kill-buffer (process-buffer proc))
- ;;(message "git blame finished")
- )
-
-(defvar in-blame-filter nil)
-
-(defun git-blame-filter (proc str)
- (with-current-buffer (process-buffer proc)
- (save-excursion
- (goto-char (process-mark proc))
- (insert-before-markers str)
- (goto-char (point-min))
- (unless in-blame-filter
- (let ((more t)
- (in-blame-filter t))
- (while more
- (setq more (git-blame-parse))))))))
-
-(defun git-blame-parse ()
- (cond ((looking-at "\\([0-9a-f]\\{40\\}\\) \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\)\n")
- (let ((hash (match-string 1))
- (src-line (string-to-number (match-string 2)))
- (res-line (string-to-number (match-string 3)))
- (num-lines (string-to-number (match-string 4))))
- (delete-region (point) (match-end 0))
- (setq git-blame-current (list (git-blame-new-commit hash)
- src-line res-line num-lines)))
- t)
- ((looking-at "\\([a-z-]+\\) \\(.+\\)\n")
- (let ((key (match-string 1))
- (value (match-string 2)))
- (delete-region (point) (match-end 0))
- (git-blame-add-info (car git-blame-current) key value)
- (when (string= key "filename")
- (git-blame-create-overlay (car git-blame-current)
- (caddr git-blame-current)
- (cadddr git-blame-current))
- (setq git-blame-current nil)))
- t)
- (t
- nil)))
-
-(defun git-blame-new-commit (hash)
- (with-current-buffer git-blame-file
- (or (gethash hash git-blame-cache)
- ;; Assign a random color to each new commit info
- ;; Take care not to select the same color multiple times
- (let* ((color (if git-blame-colors
- (git-blame-random-pop git-blame-colors)
- git-blame-ancient-color))
- (info `(,hash (color . ,color))))
- (puthash hash info git-blame-cache)
- info))))
-
-(defun git-blame-create-overlay (info start-line num-lines)
- (with-current-buffer git-blame-file
- (save-excursion
- (let ((inhibit-point-motion-hooks t)
- (inhibit-modification-hooks t))
- (goto-char (point-min))
- (forward-line (1- start-line))
- (let* ((start (point))
- (end (progn (forward-line num-lines) (point)))
- (ovl (make-overlay start end))
- (hash (car info))
- (spec `((?h . ,(substring hash 0 6))
- (?H . ,hash)
- (?a . ,(git-blame-get-info info 'author))
- (?A . ,(git-blame-get-info info 'author-mail))
- (?c . ,(git-blame-get-info info 'committer))
- (?C . ,(git-blame-get-info info 'committer-mail))
- (?s . ,(git-blame-get-info info 'summary)))))
- (push ovl git-blame-overlays)
- (overlay-put ovl 'git-blame info)
- (overlay-put ovl 'help-echo
- (format-spec git-blame-mouseover-format spec))
- (if git-blame-use-colors
- (overlay-put ovl 'face (list :background
- (cdr (assq 'color (cdr info))))))
- (overlay-put ovl 'line-prefix
- (propertize (format-spec git-blame-prefix-format spec)
- 'face 'git-blame-prefix-face)))))))
-
-(defun git-blame-add-info (info key value)
- (nconc info (list (cons (intern key) value))))
-
-(defun git-blame-get-info (info key)
- (cdr (assq key (cdr info))))
-
-(defun git-blame-current-commit ()
- (let ((info (get-char-property (point) 'git-blame)))
- (if info
- (car info)
- (error "No commit info"))))
-
-(defun git-describe-commit (hash)
- (with-temp-buffer
- (call-process "git" nil t nil
- "log" "-1"
- (concat "--pretty=" git-blame-log-oneline-format)
- hash)
- (buffer-substring (point-min) (point-max))))
-
-(defvar git-blame-last-identification nil)
-(make-variable-buffer-local 'git-blame-last-identification)
-(defun git-blame-identify (&optional hash)
- (interactive)
- (let ((info (gethash (or hash (git-blame-current-commit)) git-blame-cache)))
- (when (and info (not (eq info git-blame-last-identification)))
- (message "%s" (nth 4 info))
- (setq git-blame-last-identification info))))
-
-;; (defun git-blame-after-save ()
-;; (when git-blame-mode
-;; (git-blame-cleanup)
-;; (git-blame-run)))
-;; (add-hook 'after-save-hook 'git-blame-after-save)
-
-(defun git-blame-after-change (start end length)
- (when git-blame-mode
- (git-blame-enq-update start end)))
-
-(defvar git-blame-last-update nil)
-(make-variable-buffer-local 'git-blame-last-update)
-(defun git-blame-enq-update (start end)
- "Mark the region between START and END as needing blame update"
- ;; Try to be smart and avoid multiple callouts for sequential
- ;; editing
- (cond ((and git-blame-last-update
- (= start (cdr git-blame-last-update)))
- (setcdr git-blame-last-update end))
- ((and git-blame-last-update
- (= end (car git-blame-last-update)))
- (setcar git-blame-last-update start))
- (t
- (setq git-blame-last-update (cons start end))
- (setq git-blame-update-queue (nconc git-blame-update-queue
- (list git-blame-last-update)))))
- (unless (or git-blame-proc git-blame-idle-timer)
- (setq git-blame-idle-timer
- (run-with-idle-timer 0.5 nil 'git-blame-delayed-update))))
-
-(defun git-blame-delayed-update ()
- (setq git-blame-idle-timer nil)
- (if git-blame-update-queue
- (let ((first (pop git-blame-update-queue))
- (inhibit-point-motion-hooks t))
- (git-blame-update-region (car first) (cdr first)))))
-
-(provide 'git-blame)
-
-;;; git-blame.el ends here
+(error "git-blame.el no longer ships with git. It's recommended
+to replace its use with Emacs's own vc-annotate. See
+contrib/emacs/README in git's
+sources (https://github.com/git/git/blob/master/contrib/emacs/README)
+for more info on suggested alternatives and for why this
+happened.")
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
index 5ffc506f6d..03f926281f 100644
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
@@ -1,1705 +1,6 @@
-;;; git.el --- A user interface for git
-
-;; Copyright (C) 2005, 2006, 2007, 2008, 2009 Alexandre Julliard <julliard@winehq.org>
-
-;; Version: 1.0
-
-;; This program is free software; you can redistribute it and/or
-;; modify it under the terms of the GNU General Public License as
-;; published by the Free Software Foundation; either version 2 of
-;; the License, or (at your option) any later version.
-;;
-;; This program is distributed in the hope that it will be
-;; useful, but WITHOUT ANY WARRANTY; without even the implied
-;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-;; PURPOSE. See the GNU General Public License for more details.
-;;
-;; You should have received a copy of the GNU General Public
-;; License along with this program; if not, write to the Free
-;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
-;; MA 02111-1307 USA
-
-;;; Commentary:
-
-;; This file contains an interface for the git version control
-;; system. It provides easy access to the most frequently used git
-;; commands. The user interface is as far as possible identical to
-;; that of the PCL-CVS mode.
-;;
-;; To install: put this file on the load-path and place the following
-;; in your .emacs file:
-;;
-;; (require 'git)
-;;
-;; To start: `M-x git-status'
-;;
-;; TODO
-;; - diff against other branch
-;; - renaming files from the status buffer
-;; - creating tags
-;; - fetch/pull
-;; - revlist browser
-;; - git-show-branch browser
-;;
-
-;;; Compatibility:
-;;
-;; This file works on GNU Emacs 21 or later. It may work on older
-;; versions but this is not guaranteed.
-;;
-;; It may work on XEmacs 21, provided that you first install the ewoc
-;; and log-edit packages.
-;;
-
-(eval-when-compile (require 'cl))
-(require 'ewoc)
-(require 'log-edit)
-(require 'easymenu)
-
-
-;;;; Customizations
-;;;; ------------------------------------------------------------
-
-(defgroup git nil
- "A user interface for the git versioning system."
- :group 'tools)
-
-(defcustom git-committer-name nil
- "User name to use for commits.
-The default is to fall back to the repository config,
-then to `add-log-full-name' and then to `user-full-name'."
- :group 'git
- :type '(choice (const :tag "Default" nil)
- (string :tag "Name")))
-
-(defcustom git-committer-email nil
- "Email address to use for commits.
-The default is to fall back to the git repository config,
-then to `add-log-mailing-address' and then to `user-mail-address'."
- :group 'git
- :type '(choice (const :tag "Default" nil)
- (string :tag "Email")))
-
-(defcustom git-commits-coding-system nil
- "Default coding system for the log message of git commits."
- :group 'git
- :type '(choice (const :tag "From repository config" nil)
- (coding-system)))
-
-(defcustom git-append-signed-off-by nil
- "Whether to append a Signed-off-by line to the commit message before editing."
- :group 'git
- :type 'boolean)
-
-(defcustom git-reuse-status-buffer t
- "Whether `git-status' should try to reuse an existing buffer
-if there is already one that displays the same directory."
- :group 'git
- :type 'boolean)
-
-(defcustom git-per-dir-ignore-file ".gitignore"
- "Name of the per-directory ignore file."
- :group 'git
- :type 'string)
-
-(defcustom git-show-uptodate nil
- "Whether to display up-to-date files."
- :group 'git
- :type 'boolean)
-
-(defcustom git-show-ignored nil
- "Whether to display ignored files."
- :group 'git
- :type 'boolean)
-
-(defcustom git-show-unknown t
- "Whether to display unknown files."
- :group 'git
- :type 'boolean)
-
-
-(defface git-status-face
- '((((class color) (background light)) (:foreground "purple"))
- (((class color) (background dark)) (:foreground "salmon")))
- "Git mode face used to highlight added and modified files."
- :group 'git)
-
-(defface git-unmerged-face
- '((((class color) (background light)) (:foreground "red" :bold t))
- (((class color) (background dark)) (:foreground "red" :bold t)))
- "Git mode face used to highlight unmerged files."
- :group 'git)
-
-(defface git-unknown-face
- '((((class color) (background light)) (:foreground "goldenrod" :bold t))
- (((class color) (background dark)) (:foreground "goldenrod" :bold t)))
- "Git mode face used to highlight unknown files."
- :group 'git)
-
-(defface git-uptodate-face
- '((((class color) (background light)) (:foreground "grey60"))
- (((class color) (background dark)) (:foreground "grey40")))
- "Git mode face used to highlight up-to-date files."
- :group 'git)
-
-(defface git-ignored-face
- '((((class color) (background light)) (:foreground "grey60"))
- (((class color) (background dark)) (:foreground "grey40")))
- "Git mode face used to highlight ignored files."
- :group 'git)
-
-(defface git-mark-face
- '((((class color) (background light)) (:foreground "red" :bold t))
- (((class color) (background dark)) (:foreground "tomato" :bold t)))
- "Git mode face used for the file marks."
- :group 'git)
-
-(defface git-header-face
- '((((class color) (background light)) (:foreground "blue"))
- (((class color) (background dark)) (:foreground "blue")))
- "Git mode face used for commit headers."
- :group 'git)
-
-(defface git-separator-face
- '((((class color) (background light)) (:foreground "brown"))
- (((class color) (background dark)) (:foreground "brown")))
- "Git mode face used for commit separator."
- :group 'git)
-
-(defface git-permission-face
- '((((class color) (background light)) (:foreground "green" :bold t))
- (((class color) (background dark)) (:foreground "green" :bold t)))
- "Git mode face used for permission changes."
- :group 'git)
-
-
-;;;; Utilities
-;;;; ------------------------------------------------------------
-
-(defconst git-log-msg-separator "--- log message follows this line ---")
-
-(defvar git-log-edit-font-lock-keywords
- `(("^\\(Author:\\|Date:\\|Merge:\\|Signed-off-by:\\)\\(.*\\)$"
- (1 font-lock-keyword-face)
- (2 font-lock-function-name-face))
- (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$")
- (1 font-lock-comment-face))))
-
-(defun git-get-env-strings (env)
- "Build a list of NAME=VALUE strings from a list of environment strings."
- (mapcar (lambda (entry) (concat (car entry) "=" (cdr entry))) env))
-
-(defun git-call-process (buffer &rest args)
- "Wrapper for call-process that sets environment strings."
- (apply #'call-process "git" nil buffer nil args))
-
-(defun git-call-process-display-error (&rest args)
- "Wrapper for call-process that displays error messages."
- (let* ((dir default-directory)
- (buffer (get-buffer-create "*Git Command Output*"))
- (ok (with-current-buffer buffer
- (let ((default-directory dir)
- (buffer-read-only nil))
- (erase-buffer)
- (eq 0 (apply #'git-call-process (list buffer t) args))))))
- (unless ok (display-message-or-buffer buffer))
- ok))
-
-(defun git-call-process-string (&rest args)
- "Wrapper for call-process that returns the process output as a string,
-or nil if the git command failed."
- (with-temp-buffer
- (and (eq 0 (apply #'git-call-process t args))
- (buffer-string))))
-
-(defun git-call-process-string-display-error (&rest args)
- "Wrapper for call-process that displays error message and returns
-the process output as a string, or nil if the git command failed."
- (with-temp-buffer
- (if (eq 0 (apply #'git-call-process (list t t) args))
- (buffer-string)
- (display-message-or-buffer (current-buffer))
- nil)))
-
-(defun git-run-process-region (buffer start end program args)
- "Run a git process with a buffer region as input."
- (let ((output-buffer (current-buffer))
- (dir default-directory))
- (with-current-buffer buffer
- (cd dir)
- (apply #'call-process-region start end program
- nil (list output-buffer t) nil args))))
-
-(defun git-run-command-buffer (buffer-name &rest args)
- "Run a git command, sending the output to a buffer named BUFFER-NAME."
- (let ((dir default-directory)
- (buffer (get-buffer-create buffer-name)))
- (message "Running git %s..." (car args))
- (with-current-buffer buffer
- (let ((default-directory dir)
- (buffer-read-only nil))
- (erase-buffer)
- (apply #'git-call-process buffer args)))
- (message "Running git %s...done" (car args))
- buffer))
-
-(defun git-run-command-region (buffer start end env &rest args)
- "Run a git command with specified buffer region as input."
- (with-temp-buffer
- (if (eq 0 (if env
- (git-run-process-region
- buffer start end "env"
- (append (git-get-env-strings env) (list "git") args))
- (git-run-process-region buffer start end "git" args)))
- (buffer-string)
- (display-message-or-buffer (current-buffer))
- nil)))
-
-(defun git-run-hook (hook env &rest args)
- "Run a git hook and display its output if any."
- (let ((dir default-directory)
- (hook-name (expand-file-name (concat ".git/hooks/" hook))))
- (or (not (file-executable-p hook-name))
- (let (status (buffer (get-buffer-create "*Git Hook Output*")))
- (with-current-buffer buffer
- (erase-buffer)
- (cd dir)
- (setq status
- (if env
- (apply #'call-process "env" nil (list buffer t) nil
- (append (git-get-env-strings env) (list hook-name) args))
- (apply #'call-process hook-name nil (list buffer t) nil args))))
- (display-message-or-buffer buffer)
- (eq 0 status)))))
-
-(defun git-get-string-sha1 (string)
- "Read a SHA1 from the specified string."
- (and string
- (string-match "[0-9a-f]\\{40\\}" string)
- (match-string 0 string)))
-
-(defun git-get-committer-name ()
- "Return the name to use as GIT_COMMITTER_NAME."
- ; copied from log-edit
- (or git-committer-name
- (git-config "user.name")
- (and (boundp 'add-log-full-name) add-log-full-name)
- (and (fboundp 'user-full-name) (user-full-name))
- (and (boundp 'user-full-name) user-full-name)))
-
-(defun git-get-committer-email ()
- "Return the email address to use as GIT_COMMITTER_EMAIL."
- ; copied from log-edit
- (or git-committer-email
- (git-config "user.email")
- (and (boundp 'add-log-mailing-address) add-log-mailing-address)
- (and (fboundp 'user-mail-address) (user-mail-address))
- (and (boundp 'user-mail-address) user-mail-address)))
-
-(defun git-get-commits-coding-system ()
- "Return the coding system to use for commits."
- (let ((repo-config (git-config "i18n.commitencoding")))
- (or git-commits-coding-system
- (and repo-config
- (fboundp 'locale-charset-to-coding-system)
- (locale-charset-to-coding-system repo-config))
- 'utf-8)))
-
-(defun git-get-logoutput-coding-system ()
- "Return the coding system used for git-log output."
- (let ((repo-config (or (git-config "i18n.logoutputencoding")
- (git-config "i18n.commitencoding"))))
- (or git-commits-coding-system
- (and repo-config
- (fboundp 'locale-charset-to-coding-system)
- (locale-charset-to-coding-system repo-config))
- 'utf-8)))
-
-(defun git-escape-file-name (name)
- "Escape a file name if necessary."
- (if (string-match "[\n\t\"\\]" name)
- (concat "\""
- (mapconcat (lambda (c)
- (case c
- (?\n "\\n")
- (?\t "\\t")
- (?\\ "\\\\")
- (?\" "\\\"")
- (t (char-to-string c))))
- name "")
- "\"")
- name))
-
-(defun git-success-message (text files)
- "Print a success message after having handled FILES."
- (let ((n (length files)))
- (if (equal n 1)
- (message "%s %s" text (car files))
- (message "%s %d files" text n))))
-
-(defun git-get-top-dir (dir)
- "Retrieve the top-level directory of a git tree."
- (let ((cdup (with-output-to-string
- (with-current-buffer standard-output
- (cd dir)
- (unless (eq 0 (git-call-process t "rev-parse" "--show-cdup"))
- (error "cannot find top-level git tree for %s." dir))))))
- (expand-file-name (concat (file-name-as-directory dir)
- (car (split-string cdup "\n"))))))
-
-;stolen from pcl-cvs
-(defun git-append-to-ignore (file)
- "Add a file name to the ignore file in its directory."
- (let* ((fullname (expand-file-name file))
- (dir (file-name-directory fullname))
- (name (file-name-nondirectory fullname))
- (ignore-name (expand-file-name git-per-dir-ignore-file dir))
- (created (not (file-exists-p ignore-name))))
- (save-window-excursion
- (set-buffer (find-file-noselect ignore-name))
- (goto-char (point-max))
- (unless (zerop (current-column)) (insert "\n"))
- (insert "/" name "\n")
- (sort-lines nil (point-min) (point-max))
- (save-buffer))
- (when created
- (git-call-process nil "update-index" "--add" "--" (file-relative-name ignore-name)))
- (git-update-status-files (list (file-relative-name ignore-name)))))
-
-; propertize definition for XEmacs, stolen from erc-compat
-(eval-when-compile
- (unless (fboundp 'propertize)
- (defun propertize (string &rest props)
- (let ((string (copy-sequence string)))
- (while props
- (put-text-property 0 (length string) (nth 0 props) (nth 1 props) string)
- (setq props (cddr props)))
- string))))
-
-;;;; Wrappers for basic git commands
-;;;; ------------------------------------------------------------
-
-(defun git-rev-parse (rev)
- "Parse a revision name and return its SHA1."
- (git-get-string-sha1
- (git-call-process-string "rev-parse" rev)))
-
-(defun git-config (key)
- "Retrieve the value associated to KEY in the git repository config file."
- (let ((str (git-call-process-string "config" key)))
- (and str (car (split-string str "\n")))))
-
-(defun git-symbolic-ref (ref)
- "Wrapper for the git-symbolic-ref command."
- (let ((str (git-call-process-string "symbolic-ref" ref)))
- (and str (car (split-string str "\n")))))
-
-(defun git-update-ref (ref newval &optional oldval reason)
- "Update a reference by calling git-update-ref."
- (let ((args (and oldval (list oldval))))
- (when newval (push newval args))
- (push ref args)
- (when reason
- (push reason args)
- (push "-m" args))
- (unless newval (push "-d" args))
- (apply 'git-call-process-display-error "update-ref" args)))
-
-(defun git-for-each-ref (&rest specs)
- "Return a list of refs using git-for-each-ref.
-Each entry is a cons of (SHORT-NAME . FULL-NAME)."
- (let (refs)
- (with-temp-buffer
- (apply #'git-call-process t "for-each-ref" "--format=%(refname)" specs)
- (goto-char (point-min))
- (while (re-search-forward "^[^/\n]+/[^/\n]+/\\(.+\\)$" nil t)
- (push (cons (match-string 1) (match-string 0)) refs)))
- (nreverse refs)))
-
-(defun git-read-tree (tree &optional index-file)
- "Read a tree into the index file."
- (let ((process-environment
- (append (and index-file (list (concat "GIT_INDEX_FILE=" index-file))) process-environment)))
- (apply 'git-call-process-display-error "read-tree" (if tree (list tree)))))
-
-(defun git-write-tree (&optional index-file)
- "Call git-write-tree and return the resulting tree SHA1 as a string."
- (let ((process-environment
- (append (and index-file (list (concat "GIT_INDEX_FILE=" index-file))) process-environment)))
- (git-get-string-sha1
- (git-call-process-string-display-error "write-tree"))))
-
-(defun git-commit-tree (buffer tree parent)
- "Create a commit and possibly update HEAD.
-Create a commit with the message in BUFFER using the tree with hash TREE.
-Use PARENT as the parent of the new commit. If PARENT is the current \"HEAD\",
-update the \"HEAD\" reference to the new commit."
- (let ((author-name (git-get-committer-name))
- (author-email (git-get-committer-email))
- (subject "commit (initial): ")
- author-date log-start log-end args coding-system-for-write)
- (when parent
- (setq subject "commit: ")
- (push "-p" args)
- (push parent args))
- (with-current-buffer buffer
- (goto-char (point-min))
- (if
- (setq log-start (re-search-forward (concat "^" (regexp-quote git-log-msg-separator) "\n") nil t))
- (save-restriction
- (narrow-to-region (point-min) log-start)
- (goto-char (point-min))
- (when (re-search-forward "^Author: +\\(.*?\\) *<\\(.*\\)> *$" nil t)
- (setq author-name (match-string 1)
- author-email (match-string 2)))
- (goto-char (point-min))
- (when (re-search-forward "^Date: +\\(.*\\)$" nil t)
- (setq author-date (match-string 1)))
- (goto-char (point-min))
- (when (re-search-forward "^Merge: +\\(.*\\)" nil t)
- (setq subject "commit (merge): ")
- (dolist (parent (split-string (match-string 1) " +" t))
- (push "-p" args)
- (push parent args))))
- (setq log-start (point-min)))
- (setq log-end (point-max))
- (goto-char log-start)
- (when (re-search-forward ".*$" nil t)
- (setq subject (concat subject (match-string 0))))
- (setq coding-system-for-write buffer-file-coding-system))
- (let ((commit
- (git-get-string-sha1
- (let ((env `(("GIT_AUTHOR_NAME" . ,author-name)
- ("GIT_AUTHOR_EMAIL" . ,author-email)
- ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
- ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
- (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
- (apply #'git-run-command-region
- buffer log-start log-end env
- "commit-tree" tree (nreverse args))))))
- (when commit (git-update-ref "HEAD" commit parent subject))
- commit)))
-
-(defun git-empty-db-p ()
- "Check if the git db is empty (no commit done yet)."
- (not (eq 0 (git-call-process nil "rev-parse" "--verify" "HEAD"))))
-
-(defun git-get-merge-heads ()
- "Retrieve the merge heads from the MERGE_HEAD file if present."
- (let (heads)
- (when (file-readable-p ".git/MERGE_HEAD")
- (with-temp-buffer
- (insert-file-contents ".git/MERGE_HEAD" nil nil nil t)
- (goto-char (point-min))
- (while (re-search-forward "[0-9a-f]\\{40\\}" nil t)
- (push (match-string 0) heads))))
- (nreverse heads)))
-
-(defun git-get-commit-description (commit)
- "Get a one-line description of COMMIT."
- (let ((coding-system-for-read (git-get-logoutput-coding-system)))
- (let ((descr (git-call-process-string "log" "--max-count=1" "--pretty=oneline" commit)))
- (if (and descr (string-match "\\`\\([0-9a-f]\\{40\\}\\) *\\(.*\\)$" descr))
- (concat (substring (match-string 1 descr) 0 10) " - " (match-string 2 descr))
- descr))))
-
-;;;; File info structure
-;;;; ------------------------------------------------------------
-
-; fileinfo structure stolen from pcl-cvs
-(defstruct (git-fileinfo
- (:copier nil)
- (:constructor git-create-fileinfo (state name &optional old-perm new-perm rename-state orig-name marked))
- (:conc-name git-fileinfo->))
- marked ;; t/nil
- state ;; current state
- name ;; file name
- old-perm new-perm ;; permission flags
- rename-state ;; rename or copy state
- orig-name ;; original name for renames or copies
- needs-update ;; whether file needs to be updated
- needs-refresh) ;; whether file needs to be refreshed
-
-(defvar git-status nil)
-
-(defun git-set-fileinfo-state (info state)
- "Set the state of a file info."
- (unless (eq (git-fileinfo->state info) state)
- (setf (git-fileinfo->state info) state
- (git-fileinfo->new-perm info) (git-fileinfo->old-perm info)
- (git-fileinfo->rename-state info) nil
- (git-fileinfo->orig-name info) nil
- (git-fileinfo->needs-update info) nil
- (git-fileinfo->needs-refresh info) t)))
-
-(defun git-status-filenames-map (status func files &rest args)
- "Apply FUNC to the status files names in the FILES list.
-The list must be sorted."
- (when files
- (let ((file (pop files))
- (node (ewoc-nth status 0)))
- (while (and file node)
- (let* ((info (ewoc-data node))
- (name (git-fileinfo->name info)))
- (if (string-lessp name file)
- (setq node (ewoc-next status node))
- (if (string-equal name file)
- (apply func info args))
- (setq file (pop files))))))))
-
-(defun git-set-filenames-state (status files state)
- "Set the state of a list of named files. The list must be sorted"
- (when files
- (git-status-filenames-map status #'git-set-fileinfo-state files state)
- (unless state ;; delete files whose state has been set to nil
- (ewoc-filter status (lambda (info) (git-fileinfo->state info))))))
-
-(defun git-state-code (code)
- "Convert from a string to a added/deleted/modified state."
- (case (string-to-char code)
- (?M 'modified)
- (?? 'unknown)
- (?A 'added)
- (?D 'deleted)
- (?U 'unmerged)
- (?T 'modified)
- (t nil)))
-
-(defun git-status-code-as-string (code)
- "Format a git status code as string."
- (case code
- ('modified (propertize "Modified" 'face 'git-status-face))
- ('unknown (propertize "Unknown " 'face 'git-unknown-face))
- ('added (propertize "Added " 'face 'git-status-face))
- ('deleted (propertize "Deleted " 'face 'git-status-face))
- ('unmerged (propertize "Unmerged" 'face 'git-unmerged-face))
- ('uptodate (propertize "Uptodate" 'face 'git-uptodate-face))
- ('ignored (propertize "Ignored " 'face 'git-ignored-face))
- (t "? ")))
-
-(defun git-file-type-as-string (old-perm new-perm)
- "Return a string describing the file type based on its permissions."
- (let* ((old-type (lsh (or old-perm 0) -9))
- (new-type (lsh (or new-perm 0) -9))
- (str (case new-type
- (64 ;; file
- (case old-type
- (64 nil)
- (80 " (type change symlink -> file)")
- (112 " (type change subproject -> file)")))
- (80 ;; symlink
- (case old-type
- (64 " (type change file -> symlink)")
- (112 " (type change subproject -> symlink)")
- (t " (symlink)")))
- (112 ;; subproject
- (case old-type
- (64 " (type change file -> subproject)")
- (80 " (type change symlink -> subproject)")
- (t " (subproject)")))
- (72 nil) ;; directory (internal, not a real git state)
- (0 ;; deleted or unknown
- (case old-type
- (80 " (symlink)")
- (112 " (subproject)")))
- (t (format " (unknown type %o)" new-type)))))
- (cond (str (propertize str 'face 'git-status-face))
- ((eq new-type 72) "/")
- (t ""))))
-
-(defun git-rename-as-string (info)
- "Return a string describing the copy or rename associated with INFO, or an empty string if none."
- (let ((state (git-fileinfo->rename-state info)))
- (if state
- (propertize
- (concat " ("
- (if (eq state 'copy) "copied from "
- (if (eq (git-fileinfo->state info) 'added) "renamed from "
- "renamed to "))
- (git-escape-file-name (git-fileinfo->orig-name info))
- ")") 'face 'git-status-face)
- "")))
-
-(defun git-permissions-as-string (old-perm new-perm)
- "Format a permission change as string."
- (propertize
- (if (or (not old-perm)
- (not new-perm)
- (eq 0 (logand ?\111 (logxor old-perm new-perm))))
- " "
- (if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
- 'face 'git-permission-face))
-
-(defun git-fileinfo-prettyprint (info)
- "Pretty-printer for the git-fileinfo structure."
- (let ((old-perm (git-fileinfo->old-perm info))
- (new-perm (git-fileinfo->new-perm info)))
- (insert (concat " " (if (git-fileinfo->marked info) (propertize "*" 'face 'git-mark-face) " ")
- " " (git-status-code-as-string (git-fileinfo->state info))
- " " (git-permissions-as-string old-perm new-perm)
- " " (git-escape-file-name (git-fileinfo->name info))
- (git-file-type-as-string old-perm new-perm)
- (git-rename-as-string info)))))
-
-(defun git-update-node-fileinfo (node info)
- "Update the fileinfo of the specified node. The names are assumed to match already."
- (let ((data (ewoc-data node)))
- (setf
- ;; preserve the marked flag
- (git-fileinfo->marked info) (git-fileinfo->marked data)
- (git-fileinfo->needs-update data) nil)
- (when (not (equal info data))
- (setf (git-fileinfo->needs-refresh info) t
- (ewoc-data node) info))))
-
-(defun git-insert-info-list (status infolist files)
- "Insert a sorted list of file infos in the status buffer, replacing existing ones if any."
- (let* ((info (pop infolist))
- (node (ewoc-nth status 0))
- (name (and info (git-fileinfo->name info)))
- remaining)
- (while info
- (let ((nodename (and node (git-fileinfo->name (ewoc-data node)))))
- (while (and files (string-lessp (car files) name))
- (push (pop files) remaining))
- (when (and files (string-equal (car files) name))
- (setq files (cdr files)))
- (cond ((not nodename)
- (setq node (ewoc-enter-last status info))
- (setq info (pop infolist))
- (setq name (and info (git-fileinfo->name info))))
- ((string-lessp nodename name)
- (setq node (ewoc-next status node)))
- ((string-equal nodename name)
- ;; preserve the marked flag
- (git-update-node-fileinfo node info)
- (setq info (pop infolist))
- (setq name (and info (git-fileinfo->name info))))
- (t
- (setq node (ewoc-enter-before status node info))
- (setq info (pop infolist))
- (setq name (and info (git-fileinfo->name info)))))))
- (nconc (nreverse remaining) files)))
-
-(defun git-run-diff-index (status files)
- "Run git-diff-index on FILES and parse the results into STATUS.
-Return the list of files that haven't been handled."
- (let (infolist)
- (with-temp-buffer
- (apply #'git-call-process t "diff-index" "-z" "-M" "HEAD" "--" files)
- (goto-char (point-min))
- (while (re-search-forward
- ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMUT]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
- nil t 1)
- (let ((old-perm (string-to-number (match-string 1) 8))
- (new-perm (string-to-number (match-string 2) 8))
- (state (or (match-string 4) (match-string 6)))
- (name (or (match-string 5) (match-string 7)))
- (new-name (match-string 8)))
- (if new-name ; copy or rename
- (if (eq ?C (string-to-char state))
- (push (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) infolist)
- (push (git-create-fileinfo 'deleted name 0 0 'rename new-name) infolist)
- (push (git-create-fileinfo 'added new-name old-perm new-perm 'rename name) infolist))
- (push (git-create-fileinfo (git-state-code state) name old-perm new-perm) infolist)))))
- (setq infolist (sort (nreverse infolist)
- (lambda (info1 info2)
- (string-lessp (git-fileinfo->name info1)
- (git-fileinfo->name info2)))))
- (git-insert-info-list status infolist files)))
-
-(defun git-find-status-file (status file)
- "Find a given file in the status ewoc and return its node."
- (let ((node (ewoc-nth status 0)))
- (while (and node (not (string= file (git-fileinfo->name (ewoc-data node)))))
- (setq node (ewoc-next status node)))
- node))
-
-(defun git-run-ls-files (status files default-state &rest options)
- "Run git-ls-files on FILES and parse the results into STATUS.
-Return the list of files that haven't been handled."
- (let (infolist)
- (with-temp-buffer
- (apply #'git-call-process t "ls-files" "-z" (append options (list "--") files))
- (goto-char (point-min))
- (while (re-search-forward "\\([^\0]*?\\)\\(/?\\)\0" nil t 1)
- (let ((name (match-string 1)))
- (push (git-create-fileinfo default-state name 0
- (if (string-equal "/" (match-string 2)) (lsh ?\110 9) 0))
- infolist))))
- (setq infolist (nreverse infolist)) ;; assume it is sorted already
- (git-insert-info-list status infolist files)))
-
-(defun git-run-ls-files-cached (status files default-state)
- "Run git-ls-files -c on FILES and parse the results into STATUS.
-Return the list of files that haven't been handled."
- (let (infolist)
- (with-temp-buffer
- (apply #'git-call-process t "ls-files" "-z" "-s" "-c" "--" files)
- (goto-char (point-min))
- (while (re-search-forward "\\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} 0\t\\([^\0]+\\)\0" nil t)
- (let* ((new-perm (string-to-number (match-string 1) 8))
- (old-perm (if (eq default-state 'added) 0 new-perm))
- (name (match-string 2)))
- (push (git-create-fileinfo default-state name old-perm new-perm) infolist))))
- (setq infolist (nreverse infolist)) ;; assume it is sorted already
- (git-insert-info-list status infolist files)))
-
-(defun git-run-ls-unmerged (status files)
- "Run git-ls-files -u on FILES and parse the results into STATUS."
- (with-temp-buffer
- (apply #'git-call-process t "ls-files" "-z" "-u" "--" files)
- (goto-char (point-min))
- (let (unmerged-files)
- (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
- (push (match-string 1) unmerged-files))
- (setq unmerged-files (nreverse unmerged-files)) ;; assume it is sorted already
- (git-set-filenames-state status unmerged-files 'unmerged))))
-
-(defun git-get-exclude-files ()
- "Get the list of exclude files to pass to git-ls-files."
- (let (files
- (config (git-config "core.excludesfile")))
- (when (file-readable-p ".git/info/exclude")
- (push ".git/info/exclude" files))
- (when (and config (file-readable-p config))
- (push config files))
- files))
-
-(defun git-run-ls-files-with-excludes (status files default-state &rest options)
- "Run git-ls-files on FILES with appropriate --exclude-from options."
- (let ((exclude-files (git-get-exclude-files)))
- (apply #'git-run-ls-files status files default-state "--directory" "--no-empty-directory"
- (concat "--exclude-per-directory=" git-per-dir-ignore-file)
- (append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
-
-(defun git-update-status-files (&optional files mark-files)
- "Update the status of FILES from the index.
-The FILES list must be sorted."
- (unless git-status (error "Not in git-status buffer."))
- ;; set the needs-update flag on existing files
- (if files
- (git-status-filenames-map
- git-status (lambda (info) (setf (git-fileinfo->needs-update info) t)) files)
- (ewoc-map (lambda (info) (setf (git-fileinfo->needs-update info) t) nil) git-status)
- (git-call-process nil "update-index" "--refresh")
- (when git-show-uptodate
- (git-run-ls-files-cached git-status nil 'uptodate)))
- (let ((remaining-files
- (if (git-empty-db-p) ; we need some special handling for an empty db
- (git-run-ls-files-cached git-status files 'added)
- (git-run-diff-index git-status files))))
- (git-run-ls-unmerged git-status files)
- (when (or remaining-files (and git-show-unknown (not files)))
- (setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'unknown "-o")))
- (when (or remaining-files (and git-show-ignored (not files)))
- (setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'ignored "-o" "-i")))
- (unless files
- (setq remaining-files (git-get-filenames (ewoc-collect git-status #'git-fileinfo->needs-update))))
- (when remaining-files
- (setq remaining-files (git-run-ls-files-cached git-status remaining-files 'uptodate)))
- (git-set-filenames-state git-status remaining-files nil)
- (when mark-files (git-mark-files git-status files))
- (git-refresh-files)
- (git-refresh-ewoc-hf git-status)))
-
-(defun git-mark-files (status files)
- "Mark all the specified FILES, and unmark the others."
- (let ((file (and files (pop files)))
- (node (ewoc-nth status 0)))
- (while node
- (let ((info (ewoc-data node)))
- (if (and file (string-equal (git-fileinfo->name info) file))
- (progn
- (unless (git-fileinfo->marked info)
- (setf (git-fileinfo->marked info) t)
- (setf (git-fileinfo->needs-refresh info) t))
- (setq file (pop files))
- (setq node (ewoc-next status node)))
- (when (git-fileinfo->marked info)
- (setf (git-fileinfo->marked info) nil)
- (setf (git-fileinfo->needs-refresh info) t))
- (if (and file (string-lessp file (git-fileinfo->name info)))
- (setq file (pop files))
- (setq node (ewoc-next status node))))))))
-
-(defun git-marked-files ()
- "Return a list of all marked files, or if none a list containing just the file at cursor position."
- (unless git-status (error "Not in git-status buffer."))
- (or (ewoc-collect git-status (lambda (info) (git-fileinfo->marked info)))
- (list (ewoc-data (ewoc-locate git-status)))))
-
-(defun git-marked-files-state (&rest states)
- "Return a sorted list of marked files that are in the specified states."
- (let ((files (git-marked-files))
- result)
- (dolist (info files)
- (when (memq (git-fileinfo->state info) states)
- (push info result)))
- (nreverse result)))
-
-(defun git-refresh-files ()
- "Refresh all files that need it and clear the needs-refresh flag."
- (unless git-status (error "Not in git-status buffer."))
- (ewoc-map
- (lambda (info)
- (let ((refresh (git-fileinfo->needs-refresh info)))
- (setf (git-fileinfo->needs-refresh info) nil)
- refresh))
- git-status)
- ; move back to goal column
- (when goal-column (move-to-column goal-column)))
-
-(defun git-refresh-ewoc-hf (status)
- "Refresh the ewoc header and footer."
- (let ((branch (git-symbolic-ref "HEAD"))
- (head (if (git-empty-db-p) "Nothing committed yet"
- (git-get-commit-description "HEAD")))
- (merge-heads (git-get-merge-heads)))
- (ewoc-set-hf status
- (format "Directory: %s\nBranch: %s\nHead: %s%s\n"
- default-directory
- (if branch
- (if (string-match "^refs/heads/" branch)
- (substring branch (match-end 0))
- branch)
- "none (detached HEAD)")
- head
- (if merge-heads
- (concat "\nMerging: "
- (mapconcat (lambda (str) (git-get-commit-description str)) merge-heads "\n "))
- ""))
- (if (ewoc-nth status 0) "" " No changes."))))
-
-(defun git-get-filenames (files)
- (mapcar (lambda (info) (git-fileinfo->name info)) files))
-
-(defun git-update-index (index-file files)
- "Run git-update-index on a list of files."
- (let ((process-environment (append (and index-file (list (concat "GIT_INDEX_FILE=" index-file)))
- process-environment))
- added deleted modified)
- (dolist (info files)
- (case (git-fileinfo->state info)
- ('added (push info added))
- ('deleted (push info deleted))
- ('modified (push info modified))))
- (and
- (or (not added) (apply #'git-call-process-display-error "update-index" "--add" "--" (git-get-filenames added)))
- (or (not deleted) (apply #'git-call-process-display-error "update-index" "--remove" "--" (git-get-filenames deleted)))
- (or (not modified) (apply #'git-call-process-display-error "update-index" "--" (git-get-filenames modified))))))
-
-(defun git-run-pre-commit-hook ()
- "Run the pre-commit hook if any."
- (unless git-status (error "Not in git-status buffer."))
- (let ((files (git-marked-files-state 'added 'deleted 'modified)))
- (or (not files)
- (not (file-executable-p ".git/hooks/pre-commit"))
- (let ((index-file (make-temp-file "gitidx")))
- (unwind-protect
- (let ((head-tree (unless (git-empty-db-p) (git-rev-parse "HEAD^{tree}"))))
- (git-read-tree head-tree index-file)
- (git-update-index index-file files)
- (git-run-hook "pre-commit" `(("GIT_INDEX_FILE" . ,index-file))))
- (delete-file index-file))))))
-
-(defun git-do-commit ()
- "Perform the actual commit using the current buffer as log message."
- (interactive)
- (let ((buffer (current-buffer))
- (index-file (make-temp-file "gitidx")))
- (with-current-buffer log-edit-parent-buffer
- (if (git-marked-files-state 'unmerged)
- (message "You cannot commit unmerged files, resolve them first.")
- (unwind-protect
- (let ((files (git-marked-files-state 'added 'deleted 'modified))
- head tree head-tree)
- (unless (git-empty-db-p)
- (setq head (git-rev-parse "HEAD")
- head-tree (git-rev-parse "HEAD^{tree}")))
- (message "Running git commit...")
- (when
- (and
- (git-read-tree head-tree index-file)
- (git-update-index nil files) ;update both the default index
- (git-update-index index-file files) ;and the temporary one
- (setq tree (git-write-tree index-file)))
- (if (or (not (string-equal tree head-tree))
- (yes-or-no-p "The tree was not modified, do you really want to perform an empty commit? "))
- (let ((commit (git-commit-tree buffer tree head)))
- (when commit
- (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
- (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
- (with-current-buffer buffer (erase-buffer))
- (git-update-status-files (git-get-filenames files))
- (git-call-process nil "rerere")
- (git-call-process nil "gc" "--auto")
- (message "Committed %s." commit)
- (git-run-hook "post-commit" nil)))
- (message "Commit aborted."))))
- (delete-file index-file))))))
-
-
-;;;; Interactive functions
-;;;; ------------------------------------------------------------
-
-(defun git-mark-file ()
- "Mark the file that the cursor is on and move to the next one."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let* ((pos (ewoc-locate git-status))
- (info (ewoc-data pos)))
- (setf (git-fileinfo->marked info) t)
- (ewoc-invalidate git-status pos)
- (ewoc-goto-next git-status 1)))
-
-(defun git-unmark-file ()
- "Unmark the file that the cursor is on and move to the next one."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let* ((pos (ewoc-locate git-status))
- (info (ewoc-data pos)))
- (setf (git-fileinfo->marked info) nil)
- (ewoc-invalidate git-status pos)
- (ewoc-goto-next git-status 1)))
-
-(defun git-unmark-file-up ()
- "Unmark the file that the cursor is on and move to the previous one."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let* ((pos (ewoc-locate git-status))
- (info (ewoc-data pos)))
- (setf (git-fileinfo->marked info) nil)
- (ewoc-invalidate git-status pos)
- (ewoc-goto-prev git-status 1)))
-
-(defun git-mark-all ()
- "Mark all files."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (ewoc-map (lambda (info) (unless (git-fileinfo->marked info)
- (setf (git-fileinfo->marked info) t))) git-status)
- ; move back to goal column after invalidate
- (when goal-column (move-to-column goal-column)))
-
-(defun git-unmark-all ()
- "Unmark all files."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (ewoc-map (lambda (info) (when (git-fileinfo->marked info)
- (setf (git-fileinfo->marked info) nil)
- t)) git-status)
- ; move back to goal column after invalidate
- (when goal-column (move-to-column goal-column)))
-
-(defun git-toggle-all-marks ()
- "Toggle all file marks."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) (not (git-fileinfo->marked info))) t) git-status)
- ; move back to goal column after invalidate
- (when goal-column (move-to-column goal-column)))
-
-(defun git-next-file (&optional n)
- "Move the selection down N files."
- (interactive "p")
- (unless git-status (error "Not in git-status buffer."))
- (ewoc-goto-next git-status n))
-
-(defun git-prev-file (&optional n)
- "Move the selection up N files."
- (interactive "p")
- (unless git-status (error "Not in git-status buffer."))
- (ewoc-goto-prev git-status n))
-
-(defun git-next-unmerged-file (&optional n)
- "Move the selection down N unmerged files."
- (interactive "p")
- (unless git-status (error "Not in git-status buffer."))
- (let* ((last (ewoc-locate git-status))
- (node (ewoc-next git-status last)))
- (while (and node (> n 0))
- (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
- (setq n (1- n))
- (setq last node))
- (setq node (ewoc-next git-status node)))
- (ewoc-goto-node git-status last)))
-
-(defun git-prev-unmerged-file (&optional n)
- "Move the selection up N unmerged files."
- (interactive "p")
- (unless git-status (error "Not in git-status buffer."))
- (let* ((last (ewoc-locate git-status))
- (node (ewoc-prev git-status last)))
- (while (and node (> n 0))
- (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
- (setq n (1- n))
- (setq last node))
- (setq node (ewoc-prev git-status node)))
- (ewoc-goto-node git-status last)))
-
-(defun git-insert-file (file)
- "Insert file(s) into the git-status buffer."
- (interactive "fInsert file: ")
- (git-update-status-files (list (file-relative-name file))))
-
-(defun git-add-file ()
- "Add marked file(s) to the index cache."
- (interactive)
- (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored 'unmerged))))
- ;; FIXME: add support for directories
- (unless files
- (push (file-relative-name (read-file-name "File to add: " nil nil t)) files))
- (when (apply 'git-call-process-display-error "update-index" "--add" "--" files)
- (git-update-status-files files)
- (git-success-message "Added" files))))
-
-(defun git-ignore-file ()
- "Add marked file(s) to the ignore list."
- (interactive)
- (let ((files (git-get-filenames (git-marked-files-state 'unknown))))
- (unless files
- (push (file-relative-name (read-file-name "File to ignore: " nil nil t)) files))
- (dolist (f files) (git-append-to-ignore f))
- (git-update-status-files files)
- (git-success-message "Ignored" files)))
-
-(defun git-remove-file ()
- "Remove the marked file(s)."
- (interactive)
- (let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate 'ignored))))
- (unless files
- (push (file-relative-name (read-file-name "File to remove: " nil nil t)) files))
- (if (yes-or-no-p
- (if (cdr files)
- (format "Remove %d files? " (length files))
- (format "Remove %s? " (car files))))
- (progn
- (dolist (name files)
- (ignore-errors
- (if (file-directory-p name)
- (delete-directory name)
- (delete-file name))))
- (when (apply 'git-call-process-display-error "update-index" "--remove" "--" files)
- (git-update-status-files files)
- (git-success-message "Removed" files)))
- (message "Aborting"))))
-
-(defun git-revert-file ()
- "Revert changes to the marked file(s)."
- (interactive)
- (let ((files (git-marked-files-state 'added 'deleted 'modified 'unmerged))
- added modified)
- (when (and files
- (yes-or-no-p
- (if (cdr files)
- (format "Revert %d files? " (length files))
- (format "Revert %s? " (git-fileinfo->name (car files))))))
- (dolist (info files)
- (case (git-fileinfo->state info)
- ('added (push (git-fileinfo->name info) added))
- ('deleted (push (git-fileinfo->name info) modified))
- ('unmerged (push (git-fileinfo->name info) modified))
- ('modified (push (git-fileinfo->name info) modified))))
- ;; check if a buffer contains one of the files and isn't saved
- (dolist (file modified)
- (let ((buffer (get-file-buffer file)))
- (when (and buffer (buffer-modified-p buffer))
- (error "Buffer %s is modified. Please kill or save modified buffers before reverting." (buffer-name buffer)))))
- (let ((ok (and
- (or (not added)
- (apply 'git-call-process-display-error "update-index" "--force-remove" "--" added))
- (or (not modified)
- (apply 'git-call-process-display-error "checkout" "HEAD" modified))))
- (names (git-get-filenames files)))
- (git-update-status-files names)
- (when ok
- (dolist (file modified)
- (let ((buffer (get-file-buffer file)))
- (when buffer (with-current-buffer buffer (revert-buffer t t t)))))
- (git-success-message "Reverted" names))))))
-
-(defun git-remove-handled ()
- "Remove handled files from the status list."
- (interactive)
- (ewoc-filter git-status
- (lambda (info)
- (case (git-fileinfo->state info)
- ('ignored git-show-ignored)
- ('uptodate git-show-uptodate)
- ('unknown git-show-unknown)
- (t t))))
- (unless (ewoc-nth git-status 0) ; refresh header if list is empty
- (git-refresh-ewoc-hf git-status)))
-
-(defun git-toggle-show-uptodate ()
- "Toogle the option for showing up-to-date files."
- (interactive)
- (if (setq git-show-uptodate (not git-show-uptodate))
- (git-refresh-status)
- (git-remove-handled)))
-
-(defun git-toggle-show-ignored ()
- "Toogle the option for showing ignored files."
- (interactive)
- (if (setq git-show-ignored (not git-show-ignored))
- (progn
- (message "Inserting ignored files...")
- (git-run-ls-files-with-excludes git-status nil 'ignored "-o" "-i")
- (git-refresh-files)
- (git-refresh-ewoc-hf git-status)
- (message "Inserting ignored files...done"))
- (git-remove-handled)))
-
-(defun git-toggle-show-unknown ()
- "Toogle the option for showing unknown files."
- (interactive)
- (if (setq git-show-unknown (not git-show-unknown))
- (progn
- (message "Inserting unknown files...")
- (git-run-ls-files-with-excludes git-status nil 'unknown "-o")
- (git-refresh-files)
- (git-refresh-ewoc-hf git-status)
- (message "Inserting unknown files...done"))
- (git-remove-handled)))
-
-(defun git-expand-directory (info)
- "Expand the directory represented by INFO to list its files."
- (when (eq (lsh (git-fileinfo->new-perm info) -9) ?\110)
- (let ((dir (git-fileinfo->name info)))
- (git-set-filenames-state git-status (list dir) nil)
- (git-run-ls-files-with-excludes git-status (list (concat dir "/")) 'unknown "-o")
- (git-refresh-files)
- (git-refresh-ewoc-hf git-status)
- t)))
-
-(defun git-setup-diff-buffer (buffer)
- "Setup a buffer for displaying a diff."
- (let ((dir default-directory))
- (with-current-buffer buffer
- (diff-mode)
- (goto-char (point-min))
- (setq default-directory dir)
- (setq buffer-read-only t)))
- (display-buffer buffer)
- ; shrink window only if it displays the status buffer
- (when (eq (window-buffer) (current-buffer))
- (shrink-window-if-larger-than-buffer)))
-
-(defun git-diff-file ()
- "Diff the marked file(s) against HEAD."
- (interactive)
- (let ((files (git-marked-files)))
- (git-setup-diff-buffer
- (apply #'git-run-command-buffer "*git-diff*" "diff-index" "-p" "-M" "HEAD" "--" (git-get-filenames files)))))
-
-(defun git-diff-file-merge-head (arg)
- "Diff the marked file(s) against the first merge head (or the nth one with a numeric prefix)."
- (interactive "p")
- (let ((files (git-marked-files))
- (merge-heads (git-get-merge-heads)))
- (unless merge-heads (error "No merge in progress"))
- (git-setup-diff-buffer
- (apply #'git-run-command-buffer "*git-diff*" "diff-index" "-p" "-M"
- (or (nth (1- arg) merge-heads) "HEAD") "--" (git-get-filenames files)))))
-
-(defun git-diff-unmerged-file (stage)
- "Diff the marked unmerged file(s) against the specified stage."
- (let ((files (git-marked-files)))
- (git-setup-diff-buffer
- (apply #'git-run-command-buffer "*git-diff*" "diff-files" "-p" stage "--" (git-get-filenames files)))))
-
-(defun git-diff-file-base ()
- "Diff the marked unmerged file(s) against the common base file."
- (interactive)
- (git-diff-unmerged-file "-1"))
-
-(defun git-diff-file-mine ()
- "Diff the marked unmerged file(s) against my pre-merge version."
- (interactive)
- (git-diff-unmerged-file "-2"))
-
-(defun git-diff-file-other ()
- "Diff the marked unmerged file(s) against the other's pre-merge version."
- (interactive)
- (git-diff-unmerged-file "-3"))
-
-(defun git-diff-file-combined ()
- "Do a combined diff of the marked unmerged file(s)."
- (interactive)
- (git-diff-unmerged-file "-c"))
-
-(defun git-diff-file-idiff ()
- "Perform an interactive diff on the current file."
- (interactive)
- (let ((files (git-marked-files-state 'added 'deleted 'modified)))
- (unless (eq 1 (length files))
- (error "Cannot perform an interactive diff on multiple files."))
- (let* ((filename (car (git-get-filenames files)))
- (buff1 (find-file-noselect filename))
- (buff2 (git-run-command-buffer (concat filename ".~HEAD~") "cat-file" "blob" (concat "HEAD:" filename))))
- (ediff-buffers buff1 buff2))))
-
-(defun git-log-file ()
- "Display a log of changes to the marked file(s)."
- (interactive)
- (let* ((files (git-marked-files))
- (coding-system-for-read git-commits-coding-system)
- (buffer (apply #'git-run-command-buffer "*git-log*" "rev-list" "--pretty" "HEAD" "--" (git-get-filenames files))))
- (with-current-buffer buffer
- ; (git-log-mode) FIXME: implement log mode
- (goto-char (point-min))
- (setq buffer-read-only t))
- (display-buffer buffer)))
-
-(defun git-log-edit-files ()
- "Return a list of marked files for use in the log-edit buffer."
- (with-current-buffer log-edit-parent-buffer
- (git-get-filenames (git-marked-files-state 'added 'deleted 'modified))))
-
-(defun git-log-edit-diff ()
- "Run a diff of the current files being committed from a log-edit buffer."
- (with-current-buffer log-edit-parent-buffer
- (git-diff-file)))
-
-(defun git-append-sign-off (name email)
- "Append a Signed-off-by entry to the current buffer, avoiding duplicates."
- (let ((sign-off (format "Signed-off-by: %s <%s>" name email))
- (case-fold-search t))
- (goto-char (point-min))
- (unless (re-search-forward (concat "^" (regexp-quote sign-off)) nil t)
- (goto-char (point-min))
- (unless (re-search-forward "^Signed-off-by: " nil t)
- (setq sign-off (concat "\n" sign-off)))
- (goto-char (point-max))
- (insert sign-off "\n"))))
-
-(defun git-setup-log-buffer (buffer &optional merge-heads author-name author-email subject date msg)
- "Setup the log buffer for a commit."
- (unless git-status (error "Not in git-status buffer."))
- (let ((dir default-directory)
- (committer-name (git-get-committer-name))
- (committer-email (git-get-committer-email))
- (sign-off git-append-signed-off-by))
- (with-current-buffer buffer
- (cd dir)
- (erase-buffer)
- (insert
- (propertize
- (format "Author: %s <%s>\n%s%s"
- (or author-name committer-name)
- (or author-email committer-email)
- (if date (format "Date: %s\n" date) "")
- (if merge-heads
- (format "Merge: %s\n"
- (mapconcat 'identity merge-heads " "))
- ""))
- 'face 'git-header-face)
- (propertize git-log-msg-separator 'face 'git-separator-face)
- "\n")
- (when subject (insert subject "\n\n"))
- (cond (msg (insert msg "\n"))
- ((file-readable-p ".git/rebase-apply/msg")
- (insert-file-contents ".git/rebase-apply/msg"))
- ((file-readable-p ".git/MERGE_MSG")
- (insert-file-contents ".git/MERGE_MSG")))
- ; delete empty lines at end
- (goto-char (point-min))
- (when (re-search-forward "\n+\\'" nil t)
- (replace-match "\n" t t))
- (when sign-off (git-append-sign-off committer-name committer-email)))
- buffer))
-
-(define-derived-mode git-log-edit-mode log-edit-mode "Git-Log-Edit"
- "Major mode for editing git log messages.
-
-Set up git-specific `font-lock-keywords' for `log-edit-mode'."
- (set (make-local-variable 'font-lock-defaults)
- '(git-log-edit-font-lock-keywords t t)))
-
-(defun git-commit-file ()
- "Commit the marked file(s), asking for a commit message."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (when (git-run-pre-commit-hook)
- (let ((buffer (get-buffer-create "*git-commit*"))
- (coding-system (git-get-commits-coding-system))
- author-name author-email subject date)
- (when (eq 0 (buffer-size buffer))
- (when (file-readable-p ".git/rebase-apply/info")
- (with-temp-buffer
- (insert-file-contents ".git/rebase-apply/info")
- (goto-char (point-min))
- (when (re-search-forward "^Author: \\(.*\\)\nEmail: \\(.*\\)$" nil t)
- (setq author-name (match-string 1))
- (setq author-email (match-string 2)))
- (goto-char (point-min))
- (when (re-search-forward "^Subject: \\(.*\\)$" nil t)
- (setq subject (match-string 1)))
- (goto-char (point-min))
- (when (re-search-forward "^Date: \\(.*\\)$" nil t)
- (setq date (match-string 1)))))
- (git-setup-log-buffer buffer (git-get-merge-heads) author-name author-email subject date))
- (if (boundp 'log-edit-diff-function)
- (log-edit 'git-do-commit nil '((log-edit-listfun . git-log-edit-files)
- (log-edit-diff-function . git-log-edit-diff)) buffer 'git-log-edit-mode)
- (log-edit 'git-do-commit nil 'git-log-edit-files buffer
- 'git-log-edit-mode))
- (setq paragraph-separate (concat (regexp-quote git-log-msg-separator) "$\\|Author: \\|Date: \\|Merge: \\|Signed-off-by: \\|\f\\|[ ]*$"))
- (setq buffer-file-coding-system coding-system)
- (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
-
-(defun git-setup-commit-buffer (commit)
- "Setup the commit buffer with the contents of COMMIT."
- (let (parents author-name author-email subject date msg)
- (with-temp-buffer
- (let ((coding-system (git-get-logoutput-coding-system)))
- (git-call-process t "log" "-1" "--pretty=medium" "--abbrev=40" commit)
- (goto-char (point-min))
- (when (re-search-forward "^Merge: *\\(.*\\)$" nil t)
- (setq parents (cdr (split-string (match-string 1) " +"))))
- (when (re-search-forward "^Author: *\\(.*\\) <\\(.*\\)>$" nil t)
- (setq author-name (match-string 1))
- (setq author-email (match-string 2)))
- (when (re-search-forward "^Date: *\\(.*\\)$" nil t)
- (setq date (match-string 1)))
- (while (re-search-forward "^ \\(.*\\)$" nil t)
- (push (match-string 1) msg))
- (setq msg (nreverse msg))
- (setq subject (pop msg))
- (while (and msg (zerop (length (car msg))) (pop msg)))))
- (git-setup-log-buffer (get-buffer-create "*git-commit*")
- parents author-name author-email subject date
- (mapconcat #'identity msg "\n"))))
-
-(defun git-get-commit-files (commit)
- "Retrieve a sorted list of files modified by COMMIT."
- (let (files)
- (with-temp-buffer
- (git-call-process t "diff-tree" "-m" "-r" "-z" "--name-only" "--no-commit-id" "--root" commit)
- (goto-char (point-min))
- (while (re-search-forward "\\([^\0]*\\)\0" nil t 1)
- (push (match-string 1) files)))
- (sort files #'string-lessp)))
-
-(defun git-read-commit-name (prompt &optional default)
- "Ask for a commit name, with completion for local branch, remote branch and tag."
- (completing-read prompt
- (list* "HEAD" "ORIG_HEAD" "FETCH_HEAD" (mapcar #'car (git-for-each-ref)))
- nil nil nil nil default))
-
-(defun git-checkout (branch &optional merge)
- "Checkout a branch, tag, or any commit.
-Use a prefix arg if git should merge while checking out."
- (interactive
- (list (git-read-commit-name "Checkout: ")
- current-prefix-arg))
- (unless git-status (error "Not in git-status buffer."))
- (let ((args (list branch "--")))
- (when merge (push "-m" args))
- (when (apply #'git-call-process-display-error "checkout" args)
- (git-update-status-files))))
-
-(defun git-branch (branch)
- "Create a branch from the current HEAD and switch to it."
- (interactive (list (git-read-commit-name "Branch: ")))
- (unless git-status (error "Not in git-status buffer."))
- (if (git-rev-parse (concat "refs/heads/" branch))
- (if (yes-or-no-p (format "Branch %s already exists, replace it? " branch))
- (and (git-call-process-display-error "branch" "-f" branch)
- (git-call-process-display-error "checkout" branch))
- (message "Canceled."))
- (git-call-process-display-error "checkout" "-b" branch))
- (git-refresh-ewoc-hf git-status))
-
-(defun git-amend-commit ()
- "Undo the last commit on HEAD, and set things up to commit an
-amended version of it."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (when (git-empty-db-p) (error "No commit to amend."))
- (let* ((commit (git-rev-parse "HEAD"))
- (files (git-get-commit-files commit)))
- (when (if (git-rev-parse "HEAD^")
- (git-call-process-display-error "reset" "--soft" "HEAD^")
- (and (git-update-ref "ORIG_HEAD" commit)
- (git-update-ref "HEAD" nil commit)))
- (git-update-status-files files t)
- (git-setup-commit-buffer commit)
- (git-commit-file))))
-
-(defun git-cherry-pick-commit (arg)
- "Cherry-pick a commit."
- (interactive (list (git-read-commit-name "Cherry-pick commit: ")))
- (unless git-status (error "Not in git-status buffer."))
- (let ((commit (git-rev-parse (concat arg "^0"))))
- (unless commit (error "Not a valid commit '%s'." arg))
- (when (git-rev-parse (concat commit "^2"))
- (error "Cannot cherry-pick a merge commit."))
- (let ((files (git-get-commit-files commit))
- (ok (git-call-process-display-error "cherry-pick" "-n" commit)))
- (git-update-status-files files ok)
- (with-current-buffer (git-setup-commit-buffer commit)
- (goto-char (point-min))
- (if (re-search-forward "^\n*Signed-off-by:" nil t 1)
- (goto-char (match-beginning 0))
- (goto-char (point-max)))
- (insert "(cherry picked from commit " commit ")\n"))
- (when ok (git-commit-file)))))
-
-(defun git-revert-commit (arg)
- "Revert a commit."
- (interactive (list (git-read-commit-name "Revert commit: ")))
- (unless git-status (error "Not in git-status buffer."))
- (let ((commit (git-rev-parse (concat arg "^0"))))
- (unless commit (error "Not a valid commit '%s'." arg))
- (when (git-rev-parse (concat commit "^2"))
- (error "Cannot revert a merge commit."))
- (let ((files (git-get-commit-files commit))
- (subject (git-get-commit-description commit))
- (ok (git-call-process-display-error "revert" "-n" commit)))
- (git-update-status-files files ok)
- (when (string-match "^[0-9a-f]+ - \\(.*\\)$" subject)
- (setq subject (match-string 1 subject)))
- (git-setup-log-buffer (get-buffer-create "*git-commit*")
- (git-get-merge-heads) nil nil (format "Revert \"%s\"" subject) nil
- (format "This reverts commit %s.\n" commit))
- (when ok (git-commit-file)))))
-
-(defun git-find-file ()
- "Visit the current file in its own buffer."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let ((info (ewoc-data (ewoc-locate git-status))))
- (unless (git-expand-directory info)
- (find-file (git-fileinfo->name info))
- (when (eq 'unmerged (git-fileinfo->state info))
- (smerge-mode 1)))))
-
-(defun git-find-file-other-window ()
- "Visit the current file in its own buffer in another window."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let ((info (ewoc-data (ewoc-locate git-status))))
- (find-file-other-window (git-fileinfo->name info))
- (when (eq 'unmerged (git-fileinfo->state info))
- (smerge-mode))))
-
-(defun git-find-file-imerge ()
- "Visit the current file in interactive merge mode."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let ((info (ewoc-data (ewoc-locate git-status))))
- (find-file (git-fileinfo->name info))
- (smerge-ediff)))
-
-(defun git-view-file ()
- "View the current file in its own buffer."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (let ((info (ewoc-data (ewoc-locate git-status))))
- (view-file (git-fileinfo->name info))))
-
-(defun git-refresh-status ()
- "Refresh the git status buffer."
- (interactive)
- (unless git-status (error "Not in git-status buffer."))
- (message "Refreshing git status...")
- (git-update-status-files)
- (message "Refreshing git status...done"))
-
-(defun git-status-quit ()
- "Quit git-status mode."
- (interactive)
- (bury-buffer))
-
-;;;; Major Mode
-;;;; ------------------------------------------------------------
-
-(defvar git-status-mode-hook nil
- "Run after `git-status-mode' is setup.")
-
-(defvar git-status-mode-map nil
- "Keymap for git major mode.")
-
-(defvar git-status nil
- "List of all files managed by the git-status mode.")
-
-(unless git-status-mode-map
- (let ((map (make-keymap))
- (commit-map (make-sparse-keymap))
- (diff-map (make-sparse-keymap))
- (toggle-map (make-sparse-keymap)))
- (suppress-keymap map)
- (define-key map "?" 'git-help)
- (define-key map "h" 'git-help)
- (define-key map " " 'git-next-file)
- (define-key map "a" 'git-add-file)
- (define-key map "c" 'git-commit-file)
- (define-key map "\C-c" commit-map)
- (define-key map "d" diff-map)
- (define-key map "=" 'git-diff-file)
- (define-key map "f" 'git-find-file)
- (define-key map "\r" 'git-find-file)
- (define-key map "g" 'git-refresh-status)
- (define-key map "i" 'git-ignore-file)
- (define-key map "I" 'git-insert-file)
- (define-key map "l" 'git-log-file)
- (define-key map "m" 'git-mark-file)
- (define-key map "M" 'git-mark-all)
- (define-key map "n" 'git-next-file)
- (define-key map "N" 'git-next-unmerged-file)
- (define-key map "o" 'git-find-file-other-window)
- (define-key map "p" 'git-prev-file)
- (define-key map "P" 'git-prev-unmerged-file)
- (define-key map "q" 'git-status-quit)
- (define-key map "r" 'git-remove-file)
- (define-key map "t" toggle-map)
- (define-key map "T" 'git-toggle-all-marks)
- (define-key map "u" 'git-unmark-file)
- (define-key map "U" 'git-revert-file)
- (define-key map "v" 'git-view-file)
- (define-key map "x" 'git-remove-handled)
- (define-key map "\C-?" 'git-unmark-file-up)
- (define-key map "\M-\C-?" 'git-unmark-all)
- ; the commit submap
- (define-key commit-map "\C-a" 'git-amend-commit)
- (define-key commit-map "\C-b" 'git-branch)
- (define-key commit-map "\C-o" 'git-checkout)
- (define-key commit-map "\C-p" 'git-cherry-pick-commit)
- (define-key commit-map "\C-v" 'git-revert-commit)
- ; the diff submap
- (define-key diff-map "b" 'git-diff-file-base)
- (define-key diff-map "c" 'git-diff-file-combined)
- (define-key diff-map "=" 'git-diff-file)
- (define-key diff-map "e" 'git-diff-file-idiff)
- (define-key diff-map "E" 'git-find-file-imerge)
- (define-key diff-map "h" 'git-diff-file-merge-head)
- (define-key diff-map "m" 'git-diff-file-mine)
- (define-key diff-map "o" 'git-diff-file-other)
- ; the toggle submap
- (define-key toggle-map "u" 'git-toggle-show-uptodate)
- (define-key toggle-map "i" 'git-toggle-show-ignored)
- (define-key toggle-map "k" 'git-toggle-show-unknown)
- (define-key toggle-map "m" 'git-toggle-all-marks)
- (setq git-status-mode-map map))
- (easy-menu-define git-menu git-status-mode-map
- "Git Menu"
- `("Git"
- ["Refresh" git-refresh-status t]
- ["Commit" git-commit-file t]
- ["Checkout..." git-checkout t]
- ["New Branch..." git-branch t]
- ["Cherry-pick Commit..." git-cherry-pick-commit t]
- ["Revert Commit..." git-revert-commit t]
- ("Merge"
- ["Next Unmerged File" git-next-unmerged-file t]
- ["Prev Unmerged File" git-prev-unmerged-file t]
- ["Interactive Merge File" git-find-file-imerge t]
- ["Diff Against Common Base File" git-diff-file-base t]
- ["Diff Combined" git-diff-file-combined t]
- ["Diff Against Merge Head" git-diff-file-merge-head t]
- ["Diff Against Mine" git-diff-file-mine t]
- ["Diff Against Other" git-diff-file-other t])
- "--------"
- ["Add File" git-add-file t]
- ["Revert File" git-revert-file t]
- ["Ignore File" git-ignore-file t]
- ["Remove File" git-remove-file t]
- ["Insert File" git-insert-file t]
- "--------"
- ["Find File" git-find-file t]
- ["View File" git-view-file t]
- ["Diff File" git-diff-file t]
- ["Interactive Diff File" git-diff-file-idiff t]
- ["Log" git-log-file t]
- "--------"
- ["Mark" git-mark-file t]
- ["Mark All" git-mark-all t]
- ["Unmark" git-unmark-file t]
- ["Unmark All" git-unmark-all t]
- ["Toggle All Marks" git-toggle-all-marks t]
- ["Hide Handled Files" git-remove-handled t]
- "--------"
- ["Show Uptodate Files" git-toggle-show-uptodate :style toggle :selected git-show-uptodate]
- ["Show Ignored Files" git-toggle-show-ignored :style toggle :selected git-show-ignored]
- ["Show Unknown Files" git-toggle-show-unknown :style toggle :selected git-show-unknown]
- "--------"
- ["Quit" git-status-quit t])))
-
-
-;; git mode should only run in the *git status* buffer
-(put 'git-status-mode 'mode-class 'special)
-
-(defun git-status-mode ()
- "Major mode for interacting with Git.
-Commands:
-\\{git-status-mode-map}"
- (kill-all-local-variables)
- (buffer-disable-undo)
- (setq mode-name "git status"
- major-mode 'git-status-mode
- goal-column 17
- buffer-read-only t)
- (use-local-map git-status-mode-map)
- (let ((buffer-read-only nil))
- (erase-buffer)
- (let ((status (ewoc-create 'git-fileinfo-prettyprint "" "")))
- (set (make-local-variable 'git-status) status))
- (set (make-local-variable 'list-buffers-directory) default-directory)
- (make-local-variable 'git-show-uptodate)
- (make-local-variable 'git-show-ignored)
- (make-local-variable 'git-show-unknown)
- (run-hooks 'git-status-mode-hook)))
-
-(defun git-find-status-buffer (dir)
- "Find the git status buffer handling a specified directory."
- (let ((list (buffer-list))
- (fulldir (expand-file-name dir))
- found)
- (while (and list (not found))
- (let ((buffer (car list)))
- (with-current-buffer buffer
- (when (and list-buffers-directory
- (string-equal fulldir (expand-file-name list-buffers-directory))
- (eq major-mode 'git-status-mode))
- (setq found buffer))))
- (setq list (cdr list)))
- found))
-
-(defun git-status (dir)
- "Entry point into git-status mode."
- (interactive "DSelect directory: ")
- (setq dir (git-get-top-dir dir))
- (if (file-exists-p (concat (file-name-as-directory dir) ".git"))
- (let ((buffer (or (and git-reuse-status-buffer (git-find-status-buffer dir))
- (create-file-buffer (expand-file-name "*git-status*" dir)))))
- (switch-to-buffer buffer)
- (cd dir)
- (git-status-mode)
- (git-refresh-status)
- (goto-char (point-min))
- (add-hook 'after-save-hook 'git-update-saved-file))
- (message "%s is not a git working tree." dir)))
-
-(defun git-update-saved-file ()
- "Update the corresponding git-status buffer when a file is saved.
-Meant to be used in `after-save-hook'."
- (let* ((file (expand-file-name buffer-file-name))
- (dir (condition-case nil (git-get-top-dir (file-name-directory file)) (error nil)))
- (buffer (and dir (git-find-status-buffer dir))))
- (when buffer
- (with-current-buffer buffer
- (let ((filename (file-relative-name file dir)))
- ; skip files located inside the .git directory
- (unless (string-match "^\\.git/" filename)
- (git-call-process nil "add" "--refresh" "--" filename)
- (git-update-status-files (list filename))))))))
-
-(defun git-help ()
- "Display help for Git mode."
- (interactive)
- (describe-function 'git-status-mode))
-
-(provide 'git)
-;;; git.el ends here
+(error "git.el no longer ships with git. It's recommended to
+replace its use with Magit, or simply delete references to git.el
+in your initialization file(s). See contrib/emacs/README in git's
+sources (https://github.com/git/git/blob/master/contrib/emacs/README)
+for suggested alternatives and for why this happened. Emacs's own
+VC mode and Magit are viable alternatives.")
diff --git a/contrib/examples/README b/contrib/examples/README
index 6946f3dd2a..18bc60b021 100644
--- a/contrib/examples/README
+++ b/contrib/examples/README
@@ -1,3 +1,20 @@
-These are original scripted implementations, kept primarily for their
-reference value to any aspiring plumbing users who want to learn how
-pieces can be fit together.
+This directory used to contain scripted implementations of builtins
+that have since been rewritten in C.
+
+They have now been removed, but can be retrieved from an older commit
+that removed them from this directory.
+
+They're interesting for their reference value to any aspiring plumbing
+users who want to learn how pieces can be fit together, but in many
+cases have drifted enough from the actual implementations Git uses to
+be instructive.
+
+Other things that can be useful:
+
+ * Some commands such as git-gc wrap other commands, and what they're
+ doing behind the scenes can be seen by running them under
+ GIT_TRACE=1
+
+ * Doing `git log` on paths matching '*--helper.c' will show
+ incremental effort in the direction of moving existing shell
+ scripts to C.
diff --git a/contrib/examples/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c
deleted file mode 100644
index 8bc8c7533a..0000000000
--- a/contrib/examples/builtin-fetch--tool.c
+++ /dev/null
@@ -1,574 +0,0 @@
-#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)) {
- fprintf(stderr, "* %s: fast-forward to %s\n",
- name, note);
- fprintf(stderr, " old..new: %s..%s\n", oldh, newh);
- return update_ref_env("fast-forward", name, sha1_new, sha1_old);
- }
- if (!force) {
- fprintf(stderr,
- "* %s: not updating to non-fast-forward %s\n",
- name, note);
- fprintf(stderr,
- " old...new: %s...%s\n", oldh, newh);
- return 1;
- }
- fprintf(stderr,
- "* %s: forcing update to non-fast-forward %s\n",
- name, note);
- fprintf(stderr, " old...new: %s...%s\n", oldh, newh);
- return update_ref_env("forced-update", name, sha1_new, sha1_old);
-}
-
-static int append_fetch_head(FILE *fp,
- const char *head, const char *remote,
- const char *remote_name, const char *remote_nick,
- const char *local_name, int not_for_merge,
- int verbose, int force)
-{
- struct commit *commit;
- int remote_len, i, note_len;
- unsigned char sha1[20];
- char note[1024];
- const char *what, *kind;
-
- if (get_sha1(head, sha1))
- return error("Not a valid object name: %s", head);
- commit = lookup_commit_reference_gently(sha1, 1);
- if (!commit)
- not_for_merge = 1;
-
- if (!strcmp(remote_name, "HEAD")) {
- kind = "";
- what = "";
- }
- else if (!strncmp(remote_name, "refs/heads/", 11)) {
- kind = "branch";
- what = remote_name + 11;
- }
- else if (!strncmp(remote_name, "refs/tags/", 10)) {
- kind = "tag";
- what = remote_name + 10;
- }
- else if (!strncmp(remote_name, "refs/remotes/", 13)) {
- kind = "remote-tracking branch";
- what = remote_name + 13;
- }
- else {
- kind = "";
- what = remote_name;
- }
-
- remote_len = strlen(remote);
- for (i = remote_len - 1; remote[i] == '/' && 0 <= i; i--)
- ;
- remote_len = i + 1;
- if (4 < i && !strncmp(".git", remote + i - 3, 4))
- remote_len = i - 3;
-
- note_len = 0;
- if (*what) {
- if (*kind)
- note_len += sprintf(note + note_len, "%s ", kind);
- note_len += sprintf(note + note_len, "'%s' of ", what);
- }
- note_len += sprintf(note + note_len, "%.*s", remote_len, remote);
- fprintf(fp, "%s\t%s\t%s\n",
- sha1_to_hex(commit ? commit->object.sha1 : sha1),
- not_for_merge ? "not-for-merge" : "",
- note);
- return update_local_ref(local_name, head, note, verbose, force);
-}
-
-static char *keep;
-static void remove_keep(void)
-{
- if (keep && *keep)
- unlink(keep);
-}
-
-static void remove_keep_on_signal(int signo)
-{
- remove_keep();
- sigchain_pop(signo);
- raise(signo);
-}
-
-static char *find_local_name(const char *remote_name, const char *refs,
- int *force_p, int *not_for_merge_p)
-{
- const char *ref = refs;
- int len = strlen(remote_name);
-
- while (ref) {
- const char *next;
- int single_force, not_for_merge;
-
- while (*ref == '\n')
- ref++;
- if (!*ref)
- break;
- next = strchr(ref, '\n');
-
- single_force = not_for_merge = 0;
- if (*ref == '+') {
- single_force = 1;
- ref++;
- }
- if (*ref == '.') {
- not_for_merge = 1;
- ref++;
- if (*ref == '+') {
- single_force = 1;
- ref++;
- }
- }
- if (!strncmp(remote_name, ref, len) && ref[len] == ':') {
- const char *local_part = ref + len + 1;
- int retlen;
-
- if (!next)
- retlen = strlen(local_part);
- else
- retlen = next - local_part;
- *force_p = single_force;
- *not_for_merge_p = not_for_merge;
- return xmemdupz(local_part, retlen);
- }
- ref = next;
- }
- return NULL;
-}
-
-static int fetch_native_store(FILE *fp,
- const char *remote,
- const char *remote_nick,
- const char *refs,
- int verbose, int force)
-{
- char buffer[1024];
- int err = 0;
-
- sigchain_push_common(remove_keep_on_signal);
- atexit(remove_keep);
-
- while (fgets(buffer, sizeof(buffer), stdin)) {
- int len;
- char *cp;
- char *local_name;
- int single_force, not_for_merge;
-
- for (cp = buffer; *cp && !isspace(*cp); cp++)
- ;
- if (*cp)
- *cp++ = 0;
- len = strlen(cp);
- if (len && cp[len-1] == '\n')
- cp[--len] = 0;
- if (!strcmp(buffer, "failed"))
- die("Fetch failure: %s", remote);
- if (!strcmp(buffer, "pack"))
- continue;
- if (!strcmp(buffer, "keep")) {
- char *od = get_object_directory();
- int len = strlen(od) + strlen(cp) + 50;
- keep = xmalloc(len);
- sprintf(keep, "%s/pack/pack-%s.keep", od, cp);
- continue;
- }
-
- local_name = find_local_name(cp, refs,
- &single_force, &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", 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", 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-checkout.sh b/contrib/examples/git-checkout.sh
deleted file mode 100755
index 1a7689a48f..0000000000
--- a/contrib/examples/git-checkout.sh
+++ /dev/null
@@ -1,302 +0,0 @@
-#!/bin/sh
-
-OPTIONS_KEEPDASHDASH=t
-OPTIONS_SPEC="\
-git-checkout [options] [<branch>] [<paths>...]
---
-b= create a new branch started at <branch>
-l create the new branch's reflog
-track arrange that the new branch tracks the remote branch
-f proceed even if the index or working tree is not HEAD
-m merge local modifications into the new branch
-q,quiet be quiet
-"
-SUBDIRECTORY_OK=Sometimes
-. git-sh-setup
-require_work_tree
-
-old_name=HEAD
-old=$(git rev-parse --verify $old_name 2>/dev/null)
-oldbranch=$(git symbolic-ref $old_name 2>/dev/null)
-new=
-new_name=
-force=
-branch=
-track=
-newbranch=
-newbranch_log=
-merge=
-quiet=
-v=-v
-LF='
-'
-
-while test $# != 0; do
- case "$1" in
- -b)
- shift
- newbranch="$1"
- [ -z "$newbranch" ] &&
- die "git checkout: -b needs a branch name"
- git show-ref --verify --quiet -- "refs/heads/$newbranch" &&
- die "git checkout: branch $newbranch already exists"
- git check-ref-format "heads/$newbranch" ||
- die "git checkout: we do not like '$newbranch' as a branch name."
- ;;
- -l)
- newbranch_log=-l
- ;;
- --track|--no-track)
- track="$1"
- ;;
- -f)
- force=1
- ;;
- -m)
- merge=1
- ;;
- -q|--quiet)
- quiet=1
- v=
- ;;
- --)
- shift
- break
- ;;
- *)
- usage
- ;;
- esac
- shift
-done
-
-arg="$1"
-rev=$(git rev-parse --verify "$arg" 2>/dev/null)
-if rev=$(git rev-parse --verify "$rev^0" 2>/dev/null)
-then
- [ -z "$rev" ] && die "unknown flag $arg"
- new_name="$arg"
- if git show-ref --verify --quiet -- "refs/heads/$arg"
- then
- rev=$(git rev-parse --verify "refs/heads/$arg^0")
- branch="$arg"
- fi
- new="$rev"
- shift
-elif rev=$(git rev-parse --verify "$rev^{tree}" 2>/dev/null)
-then
- # checking out selected paths from a tree-ish.
- new="$rev"
- new_name="$rev^{tree}"
- shift
-fi
-[ "$1" = "--" ] && shift
-
-case "$newbranch,$track" in
-,--*)
- die "git checkout: --track and --no-track require -b"
-esac
-
-case "$force$merge" in
-11)
- die "git checkout: -f and -m are incompatible"
-esac
-
-# The behaviour of the command with and without explicit path
-# parameters is quite different.
-#
-# Without paths, we are checking out everything in the work tree,
-# possibly switching branches. This is the traditional behaviour.
-#
-# With paths, we are _never_ switching branch, but checking out
-# the named paths from either index (when no rev is given),
-# or the named tree-ish (when rev is given).
-
-if test "$#" -ge 1
-then
- hint=
- if test "$#" -eq 1
- then
- hint="
-Did you intend to checkout '$@' which can not be resolved as commit?"
- fi
- if test '' != "$newbranch$force$merge"
- then
- die "git checkout: updating paths is incompatible with switching branches/forcing$hint"
- fi
- if test '' != "$new"
- then
- # from a specific tree-ish; note that this is for
- # rescuing paths and is never meant to remove what
- # is not in the named tree-ish.
- git ls-tree --full-name -r "$new" "$@" |
- git update-index --index-info || exit $?
- fi
-
- # Make sure the request is about existing paths.
- git ls-files --full-name --error-unmatch -- "$@" >/dev/null || exit
- git ls-files --full-name -- "$@" |
- (cd_to_toplevel && git checkout-index -f -u --stdin)
-
- # Run a post-checkout hook -- the HEAD does not change so the
- # current HEAD is passed in for both args
- if test -x "$GIT_DIR"/hooks/post-checkout; then
- "$GIT_DIR"/hooks/post-checkout $old $old 0
- fi
-
- exit $?
-else
- # Make sure we did not fall back on $arg^{tree} codepath
- # since we are not checking out from an arbitrary tree-ish,
- # but switching branches.
- if test '' != "$new"
- then
- git rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
- die "Cannot switch branch to a non-commit."
- fi
-fi
-
-# We are switching branches and checking out trees, so
-# we *NEED* to be at the toplevel.
-cd_to_toplevel
-
-[ -z "$new" ] && new=$old && new_name="$old_name"
-
-# If we don't have an existing branch that we're switching to,
-# and we don't have a new branch name for the target we
-# are switching to, then we are detaching our HEAD from any
-# branch. However, if "git checkout HEAD" detaches the HEAD
-# from the current branch, even though that may be logically
-# correct, it feels somewhat funny. More importantly, we do not
-# want "git checkout" nor "git checkout -f" to detach HEAD.
-
-detached=
-detach_warn=
-
-describe_detached_head () {
- test -n "$quiet" || {
- printf >&2 "$1 "
- GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2" --
- }
-}
-
-if test -z "$branch$newbranch" && test "$new_name" != "$old_name"
-then
- detached="$new"
- if test -n "$oldbranch" && test -z "$quiet"
- then
- detach_warn="Note: moving to \"$new_name\" which isn't a local branch
-If you want to create a new branch from this checkout, you may do so
-(now or later) by using -b with the checkout command again. Example:
- git checkout -b <new_branch_name>"
- fi
-elif test -z "$oldbranch" && test "$new" != "$old"
-then
- describe_detached_head 'Previous HEAD position was' "$old"
-fi
-
-if [ "X$old" = X ]
-then
- if test -z "$quiet"
- then
- echo >&2 "warning: You appear to be on a branch yet to be born."
- echo >&2 "warning: Forcing checkout of $new_name."
- fi
- force=1
-fi
-
-if [ "$force" ]
-then
- git read-tree $v --reset -u $new
-else
- git update-index --refresh >/dev/null
- git read-tree $v -m -u --exclude-per-directory=.gitignore $old $new || (
- case "$merge,$v" in
- ,*)
- exit 1 ;;
- 1,)
- ;; # quiet
- *)
- echo >&2 "Falling back to 3-way merge..." ;;
- esac
-
- # Match the index to the working tree, and do a three-way.
- git diff-files --name-only | git update-index --remove --stdin &&
- work=`git write-tree` &&
- git read-tree $v --reset -u $new || exit
-
- eval GITHEAD_$new='${new_name:-${branch:-$new}}' &&
- eval GITHEAD_$work=local &&
- export GITHEAD_$new GITHEAD_$work &&
- git merge-recursive $old -- $new $work
-
- # Do not register the cleanly merged paths in the index yet.
- # this is not a real merge before committing, but just carrying
- # the working tree changes along.
- unmerged=`git ls-files -u`
- git read-tree $v --reset $new
- case "$unmerged" in
- '') ;;
- *)
- (
- z40=0000000000000000000000000000000000000000
- echo "$unmerged" |
- sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /"
- echo "$unmerged"
- ) | git update-index --index-info
- ;;
- esac
- exit 0
- )
- saved_err=$?
- if test "$saved_err" = 0 && test -z "$quiet"
- then
- git diff-index --name-status "$new"
- fi
- (exit $saved_err)
-fi
-
-#
-# Switch the HEAD pointer to the new branch if we
-# checked out a branch head, and remove any potential
-# old MERGE_HEAD's (subsequent commits will clearly not
-# be based on them, since we re-set the index)
-#
-if [ "$?" -eq 0 ]; then
- if [ "$newbranch" ]; then
- git branch $track $newbranch_log "$newbranch" "$new_name" || exit
- branch="$newbranch"
- fi
- if test -n "$branch"
- then
- old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
- GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from ${old_branch_name:-$old} to $branch" HEAD "refs/heads/$branch"
- if test -n "$quiet"
- then
- true # nothing
- elif test "refs/heads/$branch" = "$oldbranch"
- then
- echo >&2 "Already on branch \"$branch\""
- else
- echo >&2 "Switched to${newbranch:+ a new} branch \"$branch\""
- fi
- elif test -n "$detached"
- then
- old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
- git update-ref --no-deref -m "checkout: moving from ${old_branch_name:-$old} to $arg" HEAD "$detached" ||
- die "Cannot detach HEAD"
- if test -n "$detach_warn"
- then
- echo >&2 "$detach_warn"
- fi
- describe_detached_head 'HEAD is now at' HEAD
- fi
- rm -f "$GIT_DIR/MERGE_HEAD"
-else
- exit 1
-fi
-
-# Run a post-checkout hook
-if test -x "$GIT_DIR"/hooks/post-checkout; then
- "$GIT_DIR"/hooks/post-checkout $old $new 1
-fi
diff --git a/contrib/examples/git-clean.sh b/contrib/examples/git-clean.sh
deleted file mode 100755
index 01c95e9fe8..0000000000
--- a/contrib/examples/git-clean.sh
+++ /dev/null
@@ -1,118 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005-2006 Pavel Roskin
-#
-
-OPTIONS_KEEPDASHDASH=
-OPTIONS_SPEC="\
-git-clean [options] <paths>...
-
-Clean untracked files from the working directory
-
-When optional <paths>... arguments are given, the paths
-affected are further limited to those that match them.
---
-d remove directories as well
-f override clean.requireForce and clean anyway
-n don't remove anything, just show what would be done
-q be quiet, only report errors
-x remove ignored files as well
-X remove only ignored files"
-
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-require_work_tree
-
-ignored=
-ignoredonly=
-cleandir=
-rmf="rm -f --"
-rmrf="rm -rf --"
-rm_refuse="echo Not removing"
-echo1="echo"
-
-disabled=$(git config --bool clean.requireForce)
-
-while test $# != 0
-do
- case "$1" in
- -d)
- cleandir=1
- ;;
- -f)
- disabled=false
- ;;
- -n)
- disabled=false
- rmf="echo Would remove"
- rmrf="echo Would remove"
- rm_refuse="echo Would not remove"
- echo1=":"
- ;;
- -q)
- echo1=":"
- ;;
- -x)
- ignored=1
- ;;
- -X)
- ignoredonly=1
- ;;
- --)
- shift
- break
- ;;
- *)
- usage # should not happen
- ;;
- esac
- shift
-done
-
-# requireForce used to default to false but now it defaults to true.
-# IOW, lack of explicit "clean.requireForce = false" is taken as
-# "clean.requireForce = true".
-case "$disabled" in
-"")
- die "clean.requireForce not set and -n or -f not given; refusing to clean"
- ;;
-"true")
- die "clean.requireForce set and -n or -f not given; refusing to clean"
- ;;
-esac
-
-if [ "$ignored,$ignoredonly" = "1,1" ]; then
- die "-x and -X cannot be set together"
-fi
-
-if [ -z "$ignored" ]; then
- excl="--exclude-per-directory=.gitignore"
- excl_info= excludes_file=
- if [ -f "$GIT_DIR/info/exclude" ]; then
- excl_info="--exclude-from=$GIT_DIR/info/exclude"
- fi
- if cfg_excl=$(git config core.excludesfile) && test -f "$cfg_excl"
- then
- excludes_file="--exclude-from=$cfg_excl"
- fi
- if [ "$ignoredonly" ]; then
- excl="$excl --ignored"
- fi
-fi
-
-git ls-files --others --directory \
- $excl ${excl_info:+"$excl_info"} ${excludes_file:+"$excludes_file"} \
- -- "$@" |
-while read -r file; do
- if [ -d "$file" -a ! -L "$file" ]; then
- if [ -z "$cleandir" ]; then
- $rm_refuse "$file"
- continue
- fi
- $echo1 "Removing $file"
- $rmrf "$file"
- else
- $echo1 "Removing $file"
- $rmf "$file"
- fi
-done
diff --git a/contrib/examples/git-clone.sh b/contrib/examples/git-clone.sh
deleted file mode 100755
index 547228e13c..0000000000
--- a/contrib/examples/git-clone.sh
+++ /dev/null
@@ -1,525 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005, Linus Torvalds
-# Copyright (c) 2005, Junio C Hamano
-#
-# Clone a repository into a different directory that does not yet exist.
-
-# See git-sh-setup why.
-unset CDPATH
-
-OPTIONS_SPEC="\
-git-clone [options] [--] <repo> [<dir>]
---
-n,no-checkout don't create a checkout
-bare create a bare repository
-naked create a bare repository
-l,local to clone from a local repository
-no-hardlinks don't use local hardlinks, always copy
-s,shared setup as a shared repository
-template= path to the template directory
-q,quiet be quiet
-reference= reference repository
-o,origin= use <name> instead of 'origin' to track upstream
-u,upload-pack= path to git-upload-pack on the remote
-depth= create a shallow clone of that depth
-
-use-separate-remote compatibility, do not use
-no-separate-remote compatibility, do not use"
-
-die() {
- echo >&2 "$@"
- exit 1
-}
-
-usage() {
- exec "$0" -h
-}
-
-eval "$(echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
-
-get_repo_base() {
- (
- cd "`/bin/pwd`" &&
- cd "$1" || cd "$1.git" &&
- {
- cd .git
- pwd
- }
- ) 2>/dev/null
-}
-
-if [ -n "$GIT_SSL_NO_VERIFY" -o \
- "`git config --bool http.sslVerify`" = false ]; then
- curl_extra_args="-k"
-fi
-
-http_fetch () {
- # $1 = Remote, $2 = Local
- curl -nsfL $curl_extra_args "$1" >"$2"
- curl_exit_status=$?
- case $curl_exit_status in
- 126|127) exit ;;
- *) return $curl_exit_status ;;
- esac
-}
-
-clone_dumb_http () {
- # $1 - remote, $2 - local
- cd "$2" &&
- clone_tmp="$GIT_DIR/clone-tmp" &&
- mkdir -p "$clone_tmp" || exit 1
- if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
- "`git config --bool http.noEPSV`" = true ]; then
- curl_extra_args="${curl_extra_args} --disable-epsv"
- fi
- http_fetch "$1/info/refs" "$clone_tmp/refs" ||
- die "Cannot get remote repository information.
-Perhaps git-update-server-info needs to be run there?"
- test "z$quiet" = z && v=-v || v=
- while read sha1 refname
- do
- name=`expr "z$refname" : 'zrefs/\(.*\)'` &&
- case "$name" in
- *^*) continue;;
- esac
- case "$bare,$name" in
- yes,* | ,heads/* | ,tags/*) ;;
- *) continue ;;
- esac
- if test -n "$use_separate_remote" &&
- branch_name=`expr "z$name" : 'zheads/\(.*\)'`
- then
- tname="remotes/$origin/$branch_name"
- else
- tname=$name
- fi
- git-http-fetch $v -a -w "$tname" "$sha1" "$1" || exit 1
- done <"$clone_tmp/refs"
- rm -fr "$clone_tmp"
- http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" ||
- rm -f "$GIT_DIR/REMOTE_HEAD"
- if test -f "$GIT_DIR/REMOTE_HEAD"; then
- head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
- case "$head_sha1" in
- 'ref: refs/'*)
- ;;
- *)
- git-http-fetch $v -a "$head_sha1" "$1" ||
- rm -f "$GIT_DIR/REMOTE_HEAD"
- ;;
- esac
- fi
-}
-
-quiet=
-local=no
-use_local_hardlink=yes
-local_shared=no
-unset template
-no_checkout=
-upload_pack=
-bare=
-reference=
-origin=
-origin_override=
-use_separate_remote=t
-depth=
-no_progress=
-local_explicitly_asked_for=
-test -t 1 || no_progress=--no-progress
-
-while test $# != 0
-do
- case "$1" in
- -n|--no-checkout)
- no_checkout=yes ;;
- --naked|--bare)
- bare=yes ;;
- -l|--local)
- local_explicitly_asked_for=yes
- use_local_hardlink=yes
- ;;
- --no-hardlinks)
- use_local_hardlink=no ;;
- -s|--shared)
- local_shared=yes ;;
- --template)
- shift; template="--template=$1" ;;
- -q|--quiet)
- quiet=-q ;;
- --use-separate-remote|--no-separate-remote)
- die "clones are always made with separate-remote layout" ;;
- --reference)
- shift; reference="$1" ;;
- -o|--origin)
- shift;
- case "$1" in
- '')
- usage ;;
- */*)
- die "'$1' is not suitable for an origin name"
- esac
- git check-ref-format "heads/$1" ||
- die "'$1' is not suitable for a branch name"
- test -z "$origin_override" ||
- die "Do not give more than one --origin options."
- origin_override=yes
- origin="$1"
- ;;
- -u|--upload-pack)
- shift
- upload_pack="--upload-pack=$1" ;;
- --depth)
- shift
- depth="--depth=$1" ;;
- --)
- shift
- break ;;
- *)
- usage ;;
- esac
- shift
-done
-
-repo="$1"
-test -n "$repo" ||
- die 'you must specify a repository to clone.'
-
-# --bare implies --no-checkout and --no-separate-remote
-if test yes = "$bare"
-then
- if test yes = "$origin_override"
- then
- die '--bare and --origin $origin options are incompatible.'
- fi
- no_checkout=yes
- use_separate_remote=
-fi
-
-if test -z "$origin"
-then
- origin=origin
-fi
-
-# Turn the source into an absolute path if
-# it is local
-if base=$(get_repo_base "$repo"); then
- repo="$base"
- if test -z "$depth"
- then
- local=yes
- fi
-elif test -f "$repo"
-then
- case "$repo" in /*) ;; *) repo="$PWD/$repo" ;; esac
-fi
-
-# Decide the directory name of the new repository
-if test -n "$2"
-then
- dir="$2"
- test $# = 2 || die "excess parameter to git-clone"
-else
- # Derive one from the repository name
- # Try using "humanish" part of source repo if user didn't specify one
- if test -f "$repo"
- then
- # Cloning from a bundle
- dir=$(echo "$repo" | sed -e 's|/*\.bundle$||' -e 's|.*/||g')
- else
- dir=$(echo "$repo" |
- sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
- fi
-fi
-
-[ -e "$dir" ] && die "destination directory '$dir' already exists."
-[ yes = "$bare" ] && unset GIT_WORK_TREE
-[ -n "$GIT_WORK_TREE" ] && [ -e "$GIT_WORK_TREE" ] &&
-die "working tree '$GIT_WORK_TREE' already exists."
-D=
-W=
-cleanup() {
- test -z "$D" && rm -rf "$dir"
- test -z "$W" && test -n "$GIT_WORK_TREE" && rm -rf "$GIT_WORK_TREE"
- cd ..
- test -n "$D" && rm -rf "$D"
- test -n "$W" && rm -rf "$W"
- exit $err
-}
-trap 'err=$?; cleanup' 0
-mkdir -p "$dir" && D=$(cd "$dir" && pwd) || usage
-test -n "$GIT_WORK_TREE" && mkdir -p "$GIT_WORK_TREE" &&
-W=$(cd "$GIT_WORK_TREE" && pwd) && GIT_WORK_TREE="$W" && export GIT_WORK_TREE
-if test yes = "$bare" || test -n "$GIT_WORK_TREE"; then
- GIT_DIR="$D"
-else
- GIT_DIR="$D/.git"
-fi &&
-export GIT_DIR &&
-GIT_CONFIG="$GIT_DIR/config" git-init $quiet ${template+"$template"} || usage
-
-if test -n "$bare"
-then
- GIT_CONFIG="$GIT_DIR/config" git config core.bare true
-fi
-
-if test -n "$reference"
-then
- ref_git=
- if test -d "$reference"
- then
- if test -d "$reference/.git/objects"
- then
- ref_git="$reference/.git"
- elif test -d "$reference/objects"
- then
- ref_git="$reference"
- fi
- fi
- if test -n "$ref_git"
- then
- ref_git=$(cd "$ref_git" && pwd)
- echo "$ref_git/objects" >"$GIT_DIR/objects/info/alternates"
- (
- GIT_DIR="$ref_git" git for-each-ref \
- --format='%(objectname) %(*objectname)'
- ) |
- while read a b
- do
- test -z "$a" ||
- git update-ref "refs/reference-tmp/$a" "$a"
- test -z "$b" ||
- git update-ref "refs/reference-tmp/$b" "$b"
- done
- else
- die "reference repository '$reference' is not a local directory."
- fi
-fi
-
-rm -f "$GIT_DIR/CLONE_HEAD"
-
-# We do local magic only when the user tells us to.
-case "$local" in
-yes)
- ( cd "$repo/objects" ) ||
- die "cannot chdir to local '$repo/objects'."
-
- if test "$local_shared" = yes
- then
- mkdir -p "$GIT_DIR/objects/info"
- echo "$repo/objects" >>"$GIT_DIR/objects/info/alternates"
- else
- cpio_quiet_flag=""
- cpio --help 2>&1 | grep -- --quiet >/dev/null && \
- cpio_quiet_flag=--quiet
- l= &&
- if test "$use_local_hardlink" = yes
- then
- # See if we can hardlink and drop "l" if not.
- sample_file=$(cd "$repo" && \
- find objects -type f -print | sed -e 1q)
- # objects directory should not be empty because
- # we are cloning!
- test -f "$repo/$sample_file" ||
- die "fatal: cannot clone empty repository"
- if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
- then
- rm -f "$GIT_DIR/objects/sample"
- l=l
- elif test -n "$local_explicitly_asked_for"
- then
- echo >&2 "Warning: -l asked but cannot hardlink to $repo"
- fi
- fi &&
- cd "$repo" &&
- # Create dirs using umask and permissions and destination
- find objects -type d -print | (cd "$GIT_DIR" && xargs mkdir -p) &&
- # Copy existing 0444 permissions on content
- find objects ! -type d -print | cpio $cpio_quiet_flag -pumd$l "$GIT_DIR/" || \
- exit 1
- fi
- git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
- ;;
-*)
- case "$repo" in
- rsync://*)
- case "$depth" in
- "") ;;
- *) die "shallow over rsync not supported" ;;
- esac
- rsync $quiet -av --ignore-existing \
- --exclude info "$repo/objects/" "$GIT_DIR/objects/" ||
- exit
- # Look at objects/info/alternates for rsync -- http will
- # support it natively and git native ones will do it on the
- # remote end. Not having that file is not a crime.
- rsync -q "$repo/objects/info/alternates" \
- "$GIT_DIR/TMP_ALT" 2>/dev/null ||
- rm -f "$GIT_DIR/TMP_ALT"
- if test -f "$GIT_DIR/TMP_ALT"
- then
- ( cd "$D" &&
- . git-parse-remote &&
- resolve_alternates "$repo" <"$GIT_DIR/TMP_ALT" ) |
- while read alt
- do
- case "$alt" in 'bad alternate: '*) die "$alt";; esac
- case "$quiet" in
- '') echo >&2 "Getting alternate: $alt" ;;
- esac
- rsync $quiet -av --ignore-existing \
- --exclude info "$alt" "$GIT_DIR/objects" || exit
- done
- rm -f "$GIT_DIR/TMP_ALT"
- fi
- git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
- ;;
- https://*|http://*|ftp://*)
- case "$depth" in
- "") ;;
- *) die "shallow over http or ftp not supported" ;;
- esac
- if test -z "@@NO_CURL@@"
- then
- clone_dumb_http "$repo" "$D"
- else
- die "http transport not supported, rebuild Git with curl support"
- fi
- ;;
- *)
- if [ -f "$repo" ] ; then
- git bundle unbundle "$repo" > "$GIT_DIR/CLONE_HEAD" ||
- die "unbundle from '$repo' failed."
- else
- case "$upload_pack" in
- '') git-fetch-pack --all -k $quiet $depth $no_progress "$repo";;
- *) git-fetch-pack --all -k \
- $quiet "$upload_pack" $depth $no_progress "$repo" ;;
- esac >"$GIT_DIR/CLONE_HEAD" ||
- die "fetch-pack from '$repo' failed."
- fi
- ;;
- esac
- ;;
-esac
-test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp"
-
-if test -f "$GIT_DIR/CLONE_HEAD"
-then
- # Read git-fetch-pack -k output and store the remote branches.
- if [ -n "$use_separate_remote" ]
- then
- branch_top="remotes/$origin"
- else
- branch_top="heads"
- fi
- tag_top="tags"
- while read sha1 name
- do
- case "$name" in
- *'^{}')
- continue ;;
- HEAD)
- destname="REMOTE_HEAD" ;;
- refs/heads/*)
- destname="refs/$branch_top/${name#refs/heads/}" ;;
- refs/tags/*)
- destname="refs/$tag_top/${name#refs/tags/}" ;;
- *)
- continue ;;
- esac
- git update-ref -m "clone: from $repo" "$destname" "$sha1" ""
- done < "$GIT_DIR/CLONE_HEAD"
-fi
-
-if test -n "$W"; then
- cd "$W" || exit
-else
- cd "$D" || exit
-fi
-
-if test -z "$bare"
-then
- # a non-bare repository is always in separate-remote layout
- remote_top="refs/remotes/$origin"
- head_sha1=
- test ! -r "$GIT_DIR/REMOTE_HEAD" || head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
- case "$head_sha1" in
- 'ref: refs/'*)
- # Uh-oh, the remote told us (http transport done against
- # new style repository with a symref HEAD).
- # Ideally we should skip the guesswork but for now
- # opt for minimum change.
- head_sha1=`expr "z$head_sha1" : 'zref: refs/heads/\(.*\)'`
- head_sha1=`cat "$GIT_DIR/$remote_top/$head_sha1"`
- ;;
- esac
-
- # The name under $remote_top the remote HEAD seems to point at.
- head_points_at=$(
- (
- test -f "$GIT_DIR/$remote_top/master" && echo "master"
- cd "$GIT_DIR/$remote_top" &&
- find . -type f -print | sed -e 's/^\.\///'
- ) | (
- done=f
- while read name
- do
- test t = $done && continue
- branch_tip=`cat "$GIT_DIR/$remote_top/$name"`
- if test "$head_sha1" = "$branch_tip"
- then
- echo "$name"
- done=t
- fi
- done
- )
- )
-
- # Upstream URL
- git config remote."$origin".url "$repo" &&
-
- # Set up the mappings to track the remote branches.
- git config remote."$origin".fetch \
- "+refs/heads/*:$remote_top/*" '^$' &&
-
- # Write out remote.$origin config, and update our "$head_points_at".
- case "$head_points_at" in
- ?*)
- # Local default branch
- git symbolic-ref HEAD "refs/heads/$head_points_at" &&
-
- # Tracking branch for the primary branch at the remote.
- git update-ref HEAD "$head_sha1" &&
-
- rm -f "refs/remotes/$origin/HEAD"
- git symbolic-ref "refs/remotes/$origin/HEAD" \
- "refs/remotes/$origin/$head_points_at" &&
-
- git config branch."$head_points_at".remote "$origin" &&
- git config branch."$head_points_at".merge "refs/heads/$head_points_at"
- ;;
- '')
- if test -z "$head_sha1"
- then
- # Source had nonexistent ref in HEAD
- echo >&2 "Warning: Remote HEAD refers to nonexistent ref, unable to checkout."
- no_checkout=t
- else
- # Source had detached HEAD pointing nowhere
- git update-ref --no-deref HEAD "$head_sha1" &&
- rm -f "refs/remotes/$origin/HEAD"
- fi
- ;;
- esac
-
- case "$no_checkout" in
- '')
- test "z$quiet" = z -a "z$no_progress" = z && v=-v || v=
- git read-tree -m -u $v HEAD HEAD
- esac
-fi
-rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
-
-trap - 0
diff --git a/contrib/examples/git-commit.sh b/contrib/examples/git-commit.sh
deleted file mode 100755
index 23ffb028d1..0000000000
--- a/contrib/examples/git-commit.sh
+++ /dev/null
@@ -1,639 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Linus Torvalds
-# Copyright (c) 2006 Junio C Hamano
-
-USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]'
-SUBDIRECTORY_OK=Yes
-OPTIONS_SPEC=
-. git-sh-setup
-require_work_tree
-
-git rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
-
-case "$0" in
-*status)
- status_only=t
- ;;
-*commit)
- status_only=
- ;;
-esac
-
-refuse_partial () {
- echo >&2 "$1"
- echo >&2 "You might have meant to say 'git commit -i paths...', perhaps?"
- exit 1
-}
-
-TMP_INDEX=
-THIS_INDEX="${GIT_INDEX_FILE:-$GIT_DIR/index}"
-NEXT_INDEX="$GIT_DIR/next-index$$"
-rm -f "$NEXT_INDEX"
-save_index () {
- cp -p "$THIS_INDEX" "$NEXT_INDEX"
-}
-
-run_status () {
- # If TMP_INDEX is defined, that means we are doing
- # "--only" partial commit, and that index file is used
- # to build the tree for the commit. Otherwise, if
- # NEXT_INDEX exists, that is the index file used to
- # make the commit. Otherwise we are using as-is commit
- # so the regular index file is what we use to compare.
- if test '' != "$TMP_INDEX"
- then
- GIT_INDEX_FILE="$TMP_INDEX"
- export GIT_INDEX_FILE
- elif test -f "$NEXT_INDEX"
- then
- GIT_INDEX_FILE="$NEXT_INDEX"
- export GIT_INDEX_FILE
- fi
-
- if test "$status_only" = "t" -o "$use_status_color" = "t"; then
- color=
- else
- color=--nocolor
- fi
- git runstatus ${color} \
- ${verbose:+--verbose} \
- ${amend:+--amend} \
- ${untracked_files:+--untracked}
-}
-
-trap '
- test -z "$TMP_INDEX" || {
- test -f "$TMP_INDEX" && rm -f "$TMP_INDEX"
- }
- rm -f "$NEXT_INDEX"
-' 0
-
-################################################################
-# Command line argument parsing and sanity checking
-
-all=
-also=
-allow_empty=f
-interactive=
-only=
-logfile=
-use_commit=
-amend=
-edit_flag=
-no_edit=
-log_given=
-log_message=
-verify=t
-quiet=
-verbose=
-signoff=
-force_author=
-only_include_assumed=
-untracked_files=
-templatefile="`git config commit.template`"
-while test $# != 0
-do
- case "$1" in
- -F|--F|-f|--f|--fi|--fil|--file)
- case "$#" in 1) usage ;; esac
- shift
- no_edit=t
- log_given=t$log_given
- logfile="$1"
- ;;
- -F*|-f*)
- no_edit=t
- log_given=t$log_given
- logfile="${1#-[Ff]}"
- ;;
- --F=*|--f=*|--fi=*|--fil=*|--file=*)
- no_edit=t
- log_given=t$log_given
- logfile="${1#*=}"
- ;;
- -a|--a|--al|--all)
- all=t
- ;;
- --allo|--allow|--allow-|--allow-e|--allow-em|--allow-emp|\
- --allow-empt|--allow-empty)
- allow_empty=t
- ;;
- --au=*|--aut=*|--auth=*|--autho=*|--author=*)
- force_author="${1#*=}"
- ;;
- --au|--aut|--auth|--autho|--author)
- case "$#" in 1) usage ;; esac
- shift
- force_author="$1"
- ;;
- -e|--e|--ed|--edi|--edit)
- edit_flag=t
- ;;
- -i|--i|--in|--inc|--incl|--inclu|--includ|--include)
- also=t
- ;;
- --int|--inte|--inter|--intera|--interac|--interact|--interacti|\
- --interactiv|--interactive)
- interactive=t
- ;;
- -o|--o|--on|--onl|--only)
- only=t
- ;;
- -m|--m|--me|--mes|--mess|--messa|--messag|--message)
- case "$#" in 1) usage ;; esac
- shift
- log_given=m$log_given
- log_message="${log_message:+${log_message}
-
-}$1"
- no_edit=t
- ;;
- -m*)
- log_given=m$log_given
- log_message="${log_message:+${log_message}
-
-}${1#-m}"
- no_edit=t
- ;;
- --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
- log_given=m$log_given
- log_message="${log_message:+${log_message}
-
-}${1#*=}"
- no_edit=t
- ;;
- -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
- --no-verify)
- verify=
- ;;
- --a|--am|--ame|--amen|--amend)
- amend=t
- use_commit=HEAD
- ;;
- -c)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=
- ;;
- --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
- --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
- --reedit-messag=*|--reedit-message=*)
- log_given=t$log_given
- use_commit="${1#*=}"
- no_edit=
- ;;
- --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
- --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\
- --reedit-message)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=
- ;;
- -C)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=t
- ;;
- --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
- --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
- --reuse-message=*)
- log_given=t$log_given
- use_commit="${1#*=}"
- no_edit=t
- ;;
- --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
- --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=t
- ;;
- -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
- signoff=t
- ;;
- -t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
- case "$#" in 1) usage ;; esac
- shift
- templatefile="$1"
- no_edit=
- ;;
- -q|--q|--qu|--qui|--quie|--quiet)
- quiet=t
- ;;
- -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
- verbose=t
- ;;
- -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\
- --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\
- --untracked-file|--untracked-files)
- untracked_files=t
- ;;
- --)
- shift
- break
- ;;
- -*)
- usage
- ;;
- *)
- break
- ;;
- esac
- shift
-done
-case "$edit_flag" in t) no_edit= ;; esac
-
-################################################################
-# Sanity check options
-
-case "$amend,$initial_commit" in
-t,t)
- die "You do not have anything to amend." ;;
-t,)
- if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
- die "You are in the middle of a merge -- cannot amend."
- fi ;;
-esac
-
-case "$log_given" in
-tt*)
- die "Only one of -c/-C/-F can be used." ;;
-*tm*|*mt*)
- die "Option -m cannot be combined with -c/-C/-F." ;;
-esac
-
-case "$#,$also,$only,$amend" in
-*,t,t,*)
- die "Only one of --include/--only can be used." ;;
-0,t,,* | 0,,t,)
- die "No paths with --include/--only does not make sense." ;;
-0,,t,t)
- only_include_assumed="# Clever... amending the last one with dirty index." ;;
-0,,,*)
- ;;
-*,,,*)
- only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
- also=
- ;;
-esac
-unset only
-case "$all,$interactive,$also,$#" in
-*t,*t,*)
- die "Cannot use -a, --interactive or -i at the same time." ;;
-t,,,[1-9]*)
- die "Paths with -a does not make sense." ;;
-,t,,[1-9]*)
- die "Paths with --interactive does not make sense." ;;
-,,t,0)
- die "No paths with -i does not make sense." ;;
-esac
-
-if test ! -z "$templatefile" -a -z "$log_given"
-then
- if test ! -f "$templatefile"
- then
- die "Commit template file does not exist."
- fi
-fi
-
-################################################################
-# Prepare index to have a tree to be committed
-
-case "$all,$also" in
-t,)
- if test ! -f "$THIS_INDEX"
- then
- die 'nothing to commit (use "git add file1 file2" to include for commit)'
- fi
- save_index &&
- (
- cd_to_toplevel &&
- GIT_INDEX_FILE="$NEXT_INDEX" &&
- export GIT_INDEX_FILE &&
- git diff-files --name-only -z |
- git update-index --remove -z --stdin
- ) || exit
- ;;
-,t)
- save_index &&
- git ls-files --error-unmatch -- "$@" >/dev/null || exit
-
- git diff-files --name-only -z -- "$@" |
- (
- cd_to_toplevel &&
- GIT_INDEX_FILE="$NEXT_INDEX" &&
- export GIT_INDEX_FILE &&
- git update-index --remove -z --stdin
- ) || exit
- ;;
-,)
- if test "$interactive" = t; then
- git add --interactive || exit
- fi
- case "$#" in
- 0)
- ;; # commit as-is
- *)
- if test -f "$GIT_DIR/MERGE_HEAD"
- then
- refuse_partial "Cannot do a partial commit during a merge."
- fi
-
- TMP_INDEX="$GIT_DIR/tmp-index$$"
- W=
- test -z "$initial_commit" && W=--with-tree=HEAD
- commit_only=`git ls-files --error-unmatch $W -- "$@"` || exit
-
- # Build a temporary index and update the real index
- # the same way.
- if test -z "$initial_commit"
- then
- GIT_INDEX_FILE="$THIS_INDEX" \
- git read-tree --index-output="$TMP_INDEX" -i -m HEAD
- else
- rm -f "$TMP_INDEX"
- fi || exit
-
- printf '%s\n' "$commit_only" |
- GIT_INDEX_FILE="$TMP_INDEX" \
- git update-index --add --remove --stdin &&
-
- save_index &&
- printf '%s\n' "$commit_only" |
- (
- GIT_INDEX_FILE="$NEXT_INDEX"
- export GIT_INDEX_FILE
- git update-index --add --remove --stdin
- ) || exit
- ;;
- esac
- ;;
-esac
-
-################################################################
-# If we do as-is commit, the index file will be THIS_INDEX,
-# otherwise NEXT_INDEX after we make this commit. We leave
-# the index as is if we abort.
-
-if test -f "$NEXT_INDEX"
-then
- USE_INDEX="$NEXT_INDEX"
-else
- USE_INDEX="$THIS_INDEX"
-fi
-
-case "$status_only" in
-t)
- # This will silently fail in a read-only repository, which is
- # what we want.
- GIT_INDEX_FILE="$USE_INDEX" git update-index -q --unmerged --refresh
- run_status
- exit $?
- ;;
-'')
- GIT_INDEX_FILE="$USE_INDEX" git update-index -q --refresh || exit
- ;;
-esac
-
-################################################################
-# Grab commit message, write out tree and make commit.
-
-if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit
-then
- GIT_INDEX_FILE="${TMP_INDEX:-${USE_INDEX}}" "$GIT_DIR"/hooks/pre-commit \
- || exit
-fi
-
-if test "$log_message" != ''
-then
- printf '%s\n' "$log_message"
-elif test "$logfile" != ""
-then
- if test "$logfile" = -
- then
- test -t 0 &&
- echo >&2 "(reading log message from standard input)"
- cat
- else
- cat <"$logfile"
- fi
-elif test "$use_commit" != ""
-then
- encoding=$(git config i18n.commitencoding || echo UTF-8)
- git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
- sed -e '1,/^$/d' -e 's/^ //'
-elif test -f "$GIT_DIR/MERGE_MSG"
-then
- cat "$GIT_DIR/MERGE_MSG"
-elif test -f "$GIT_DIR/SQUASH_MSG"
-then
- cat "$GIT_DIR/SQUASH_MSG"
-elif test "$templatefile" != ""
-then
- cat "$templatefile"
-fi | git stripspace >"$GIT_DIR"/COMMIT_EDITMSG
-
-case "$signoff" in
-t)
- sign=$(git var GIT_COMMITTER_IDENT | sed -e '
- s/>.*/>/
- s/^/Signed-off-by: /
- ')
- blank_before_signoff=
- tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
- grep 'Signed-off-by:' >/dev/null || blank_before_signoff='
-'
- tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
- grep "$sign"$ >/dev/null ||
- printf '%s%s\n' "$blank_before_signoff" "$sign" \
- >>"$GIT_DIR"/COMMIT_EDITMSG
- ;;
-esac
-
-if test -f "$GIT_DIR/MERGE_HEAD" && test -z "$no_edit"; then
- echo "#"
- echo "# It looks like you may be committing a MERGE."
- echo "# If this is not correct, please remove the file"
- printf '%s\n' "# $GIT_DIR/MERGE_HEAD"
- echo "# and try again"
- echo "#"
-fi >>"$GIT_DIR"/COMMIT_EDITMSG
-
-# Author
-if test '' != "$use_commit"
-then
- eval "$(get_author_ident_from_commit "$use_commit")"
- export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
-fi
-if test '' != "$force_author"
-then
- GIT_AUTHOR_NAME=`expr "z$force_author" : 'z\(.*[^ ]\) *<.*'` &&
- GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` &&
- test '' != "$GIT_AUTHOR_NAME" &&
- test '' != "$GIT_AUTHOR_EMAIL" ||
- die "malformed --author parameter"
- export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
-fi
-
-PARENTS="-p HEAD"
-if test -z "$initial_commit"
-then
- rloga='commit'
- if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
- rloga='commit (merge)'
- PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
- elif test -n "$amend"; then
- rloga='commit (amend)'
- PARENTS=$(git cat-file commit HEAD |
- sed -n -e '/^$/q' -e 's/^parent /-p /p')
- fi
- current="$(git rev-parse --verify HEAD)"
-else
- if [ -z "$(git ls-files)" ]; then
- echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)'
- exit 1
- fi
- PARENTS=""
- rloga='commit (initial)'
- current=''
-fi
-set_reflog_action "$rloga"
-
-if test -z "$no_edit"
-then
- {
- echo ""
- echo "# Please enter the commit message for your changes."
- echo "# (Comment lines starting with '#' will not be included)"
- test -z "$only_include_assumed" || echo "$only_include_assumed"
- run_status
- } >>"$GIT_DIR"/COMMIT_EDITMSG
-else
- # we need to check if there is anything to commit
- run_status >/dev/null
-fi
-case "$allow_empty,$?,$PARENTS" in
-t,* | ?,0,* | ?,*,-p' '?*-p' '?*)
- # an explicit --allow-empty, or a merge commit can record the
- # same tree as its parent. Otherwise having commitable paths
- # is required.
- ;;
-*)
- rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
- use_status_color=t
- run_status
- exit 1
-esac
-
-case "$no_edit" in
-'')
- git var GIT_AUTHOR_IDENT > /dev/null || die
- git var GIT_COMMITTER_IDENT > /dev/null || die
- git_editor "$GIT_DIR/COMMIT_EDITMSG"
- ;;
-esac
-
-case "$verify" in
-t)
- if test -x "$GIT_DIR"/hooks/commit-msg
- then
- "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
- fi
-esac
-
-if test -z "$no_edit"
-then
- sed -e '
- /^diff --git a\/.*/{
- s///
- q
- }
- /^#/d
- ' "$GIT_DIR"/COMMIT_EDITMSG
-else
- cat "$GIT_DIR"/COMMIT_EDITMSG
-fi |
-git stripspace >"$GIT_DIR"/COMMIT_MSG
-
-# Test whether the commit message has any content we didn't supply.
-have_commitmsg=
-grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
- git stripspace > "$GIT_DIR"/COMMIT_BAREMSG
-
-# Is the commit message totally empty?
-if test -s "$GIT_DIR"/COMMIT_BAREMSG
-then
- if test "$templatefile" != ""
- then
- # Test whether this is just the unaltered template.
- if cnt=`sed -e '/^#/d' < "$templatefile" |
- git stripspace |
- diff "$GIT_DIR"/COMMIT_BAREMSG - |
- wc -l` &&
- test 0 -lt $cnt
- then
- have_commitmsg=t
- fi
- else
- # No template, so the content in the commit message must
- # have come from the user.
- have_commitmsg=t
- fi
-fi
-
-rm -f "$GIT_DIR"/COMMIT_BAREMSG
-
-if test "$have_commitmsg" = "t"
-then
- if test -z "$TMP_INDEX"
- then
- tree=$(GIT_INDEX_FILE="$USE_INDEX" git write-tree)
- else
- tree=$(GIT_INDEX_FILE="$TMP_INDEX" git write-tree) &&
- rm -f "$TMP_INDEX"
- fi &&
- commit=$(git commit-tree $tree $PARENTS <"$GIT_DIR/COMMIT_MSG") &&
- rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
- git update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" &&
- rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
- if test -f "$NEXT_INDEX"
- then
- mv "$NEXT_INDEX" "$THIS_INDEX"
- else
- : ;# happy
- fi
-else
- echo >&2 "* no commit message? aborting commit."
- false
-fi
-ret="$?"
-rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
-
-cd_to_toplevel
-
-git rerere
-
-if test "$ret" = 0
-then
- git gc --auto
- if test -x "$GIT_DIR"/hooks/post-commit
- then
- "$GIT_DIR"/hooks/post-commit
- fi
- if test -z "$quiet"
- then
- commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\
- --abbrev --summary --root HEAD --`
- echo "Created${initial_commit:+ initial} commit $commit"
- fi
-fi
-
-exit "$ret"
diff --git a/contrib/examples/git-fetch.sh b/contrib/examples/git-fetch.sh
deleted file mode 100755
index a314273bd5..0000000000
--- a/contrib/examples/git-fetch.sh
+++ /dev/null
@@ -1,379 +0,0 @@
-#!/bin/sh
-#
-
-USAGE='<fetch-options> <repository> <refspec>...'
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-set_reflog_action "fetch $*"
-cd_to_toplevel ;# probably unnecessary...
-
-. git-parse-remote
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
-
-LF='
-'
-IFS="$LF"
-
-no_tags=
-tags=
-append=
-force=
-verbose=
-update_head_ok=
-exec=
-keep=
-shallow_depth=
-no_progress=
-test -t 1 || no_progress=--no-progress
-quiet=
-while test $# != 0
-do
- case "$1" in
- -a|--a|--ap|--app|--appe|--appen|--append)
- append=t
- ;;
- --upl|--uplo|--uploa|--upload|--upload-|--upload-p|\
- --upload-pa|--upload-pac|--upload-pack)
- shift
- exec="--upload-pack=$1"
- ;;
- --upl=*|--uplo=*|--uploa=*|--upload=*|\
- --upload-=*|--upload-p=*|--upload-pa=*|--upload-pac=*|--upload-pack=*)
- exec=--upload-pack=$(expr "z$1" : 'z-[^=]*=\(.*\)')
- shift
- ;;
- -f|--f|--fo|--for|--forc|--force)
- force=t
- ;;
- -t|--t|--ta|--tag|--tags)
- tags=t
- ;;
- -n|--n|--no|--no-|--no-t|--no-ta|--no-tag|--no-tags)
- no_tags=t
- ;;
- -u|--u|--up|--upd|--upda|--updat|--update|--update-|--update-h|\
- --update-he|--update-hea|--update-head|--update-head-|\
- --update-head-o|--update-head-ok)
- update_head_ok=t
- ;;
- -q|--q|--qu|--qui|--quie|--quiet)
- quiet=--quiet
- ;;
- -v|--verbose)
- verbose="$verbose"Yes
- ;;
- -k|--k|--ke|--kee|--keep)
- keep='-k -k'
- ;;
- --depth=*)
- shallow_depth="--depth=`expr "z$1" : 'z-[^=]*=\(.*\)'`"
- ;;
- --depth)
- shift
- shallow_depth="--depth=$1"
- ;;
- -*)
- usage
- ;;
- *)
- break
- ;;
- esac
- shift
-done
-
-case "$#" in
-0)
- origin=$(get_default_remote)
- test -n "$(get_remote_url ${origin})" ||
- die "Where do you want to fetch from today?"
- set x $origin ; shift ;;
-esac
-
-if test -z "$exec"
-then
- # No command line override and we have configuration for the remote.
- exec="--upload-pack=$(get_uploadpack $1)"
-fi
-
-remote_nick="$1"
-remote=$(get_remote_url "$@")
-refs=
-rref=
-rsync_slurped_objects=
-
-if test "" = "$append"
-then
- : >"$GIT_DIR/FETCH_HEAD"
-fi
-
-# Global that is reused later
-ls_remote_result=$(git ls-remote $exec "$remote") ||
- die "Cannot get the repository state from $remote"
-
-append_fetch_head () {
- flags=
- test -n "$verbose" && flags="$flags$LF-v"
- test -n "$force$single_force" && flags="$flags$LF-f"
- GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \
- git fetch--tool $flags append-fetch-head "$@"
-}
-
-# updating the current HEAD with git-fetch in a bare
-# repository is always fine.
-if test -z "$update_head_ok" && test $(is_bare_repository) = false
-then
- orig_head=$(git rev-parse --verify HEAD 2>/dev/null)
-fi
-
-# 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
-esac
-
-# If --tags (and later --heads or --all) is specified, then we are
-# not talking about defaults stored in Pull: line of remotes or
-# branches file, and just fetch those and refspecs explicitly given.
-# Otherwise we do what we always did.
-
-reflist=$(get_remote_refs_for_fetch "$@")
-if test "$tags"
-then
- taglist=`IFS=' ' &&
- echo "$ls_remote_result" |
- git show-ref --exclude-existing=refs/tags/ |
- while read sha1 name
- do
- echo ".${name}:${name}"
- done` || exit
- if test "$#" -gt 1
- then
- # remote URL plus explicit refspecs; we need to merge them.
- reflist="$reflist$LF$taglist"
- else
- # No explicit refspecs; fetch tags only.
- reflist=$taglist
- fi
-fi
-
-fetch_all_at_once () {
-
- eval=$(echo "$1" | git fetch--tool parse-reflist "-")
- eval "$eval"
-
- ( : subshell because we muck with IFS
- IFS=" $LF"
- (
- if test "$remote" = . ; then
- git show-ref $rref || echo failed "$remote"
- elif test -f "$remote" ; then
- test -n "$shallow_depth" &&
- die "shallow clone with bundle is not supported"
- git bundle unbundle "$remote" $rref ||
- echo failed "$remote"
- else
- if test -d "$remote" &&
-
- # The remote might be our alternate. With
- # this optimization we will bypass fetch-pack
- # altogether, which means we cannot be doing
- # the shallow stuff at all.
- test ! -f "$GIT_DIR/shallow" &&
- test -z "$shallow_depth" &&
-
- # See if all of what we are going to fetch are
- # connected to our repository's tips, in which
- # case we do not have to do any fetch.
- theirs=$(echo "$ls_remote_result" | \
- git fetch--tool -s pick-rref "$rref" "-") &&
-
- # This will barf when $theirs reach an object that
- # we do not have in our repository. Otherwise,
- # we already have everything the fetch would bring in.
- git rev-list --objects $theirs --not --all \
- >/dev/null 2>/dev/null
- then
- echo "$ls_remote_result" | \
- git fetch--tool pick-rref "$rref" "-"
- else
- flags=
- case $verbose in
- YesYes*)
- flags="-v"
- ;;
- esac
- git-fetch-pack --thin $exec $keep $shallow_depth \
- $quiet $no_progress $flags "$remote" $rref ||
- echo failed "$remote"
- fi
- fi
- ) |
- (
- flags=
- test -n "$verbose" && flags="$flags -v"
- test -n "$force" && flags="$flags -f"
- GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \
- git fetch--tool $flags native-store \
- "$remote" "$remote_nick" "$refs"
- )
- ) || exit
-
-}
-
-fetch_per_ref () {
- reflist="$1"
- refs=
- rref=
-
- for ref in $reflist
- do
- refs="$refs$LF$ref"
-
- # These are relative path from $GIT_DIR, typically starting at refs/
- # but may be HEAD
- if expr "z$ref" : 'z\.' >/dev/null
- then
- not_for_merge=t
- ref=$(expr "z$ref" : 'z\.\(.*\)')
- else
- not_for_merge=
- fi
- if expr "z$ref" : 'z+' >/dev/null
- then
- single_force=t
- ref=$(expr "z$ref" : 'z+\(.*\)')
- else
- single_force=
- fi
- remote_name=$(expr "z$ref" : 'z\([^:]*\):')
- local_name=$(expr "z$ref" : 'z[^:]*:\(.*\)')
-
- rref="$rref$LF$remote_name"
-
- # There are transports that can fetch only one head at a time...
- case "$remote" in
- http://* | https://* | ftp://*)
- test -n "$shallow_depth" &&
- die "shallow clone with http not supported"
- proto=`expr "$remote" : '\([^:]*\):'`
- if [ -n "$GIT_SSL_NO_VERIFY" ]; then
- curl_extra_args="-k"
- fi
- if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
- "`git config --bool http.noEPSV`" = true ]; then
- noepsv_opt="--disable-epsv"
- fi
-
- # Find $remote_name from ls-remote output.
- head=$(echo "$ls_remote_result" | \
- git fetch--tool -s pick-rref "$remote_name" "-")
- expr "z$head" : "z$_x40\$" >/dev/null ||
- die "No such ref $remote_name at $remote"
- echo >&2 "Fetching $remote_name from $remote using $proto"
- case "$quiet" in '') v=-v ;; *) v= ;; esac
- git-http-fetch $v -a "$head" "$remote" || exit
- ;;
- rsync://*)
- test -n "$shallow_depth" &&
- die "shallow clone with rsync not supported"
- TMP_HEAD="$GIT_DIR/TMP_HEAD"
- rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1
- head=$(git rev-parse --verify TMP_HEAD)
- rm -f "$TMP_HEAD"
- case "$quiet" in '') v=-v ;; *) v= ;; esac
- test "$rsync_slurped_objects" || {
- rsync -a $v --ignore-existing --exclude info \
- "$remote/objects/" "$GIT_OBJECT_DIRECTORY/" || exit
-
- # Look at objects/info/alternates for rsync -- http will
- # support it natively and git native ones will do it on
- # the remote end. Not having that file is not a crime.
- rsync -q "$remote/objects/info/alternates" \
- "$GIT_DIR/TMP_ALT" 2>/dev/null ||
- rm -f "$GIT_DIR/TMP_ALT"
- if test -f "$GIT_DIR/TMP_ALT"
- then
- resolve_alternates "$remote" <"$GIT_DIR/TMP_ALT" |
- while read alt
- do
- case "$alt" in 'bad alternate: '*) die "$alt";; esac
- echo >&2 "Getting alternate: $alt"
- rsync -av --ignore-existing --exclude info \
- "$alt" "$GIT_OBJECT_DIRECTORY/" || exit
- done
- rm -f "$GIT_DIR/TMP_ALT"
- fi
- rsync_slurped_objects=t
- }
- ;;
- esac
-
- append_fetch_head "$head" "$remote" \
- "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" || exit
-
- done
-
-}
-
-fetch_main () {
- case "$remote" in
- http://* | https://* | ftp://* | rsync://* )
- fetch_per_ref "$@"
- ;;
- *)
- fetch_all_at_once "$@"
- ;;
- esac
-}
-
-fetch_main "$reflist" || exit
-
-# automated tag following
-case "$no_tags$tags" in
-'')
- case "$reflist" in
- *:refs/*)
- # effective only when we are following remote branch
- # using local tracking branch.
- taglist=$(IFS=' ' &&
- echo "$ls_remote_result" |
- git show-ref --exclude-existing=refs/tags/ |
- while read sha1 name
- do
- git cat-file -t "$sha1" >/dev/null 2>&1 || continue
- echo >&2 "Auto-following $name"
- echo ".${name}:${name}"
- done)
- esac
- case "$taglist" in
- '') ;;
- ?*)
- # do not deepen a shallow tree when following tags
- shallow_depth=
- fetch_main "$taglist" || exit ;;
- esac
-esac
-
-# If the original head was empty (i.e. no "master" yet), or
-# if we were told not to worry, we do not have to check.
-case "$orig_head" in
-'')
- ;;
-?*)
- curr_head=$(git rev-parse --verify HEAD 2>/dev/null)
- if test "$curr_head" != "$orig_head"
- then
- git update-ref \
- -m "$GIT_REFLOG_ACTION: Undoing incorrectly fetched HEAD." \
- HEAD "$orig_head"
- die "Cannot fetch into the current branch."
- fi
- ;;
-esac
diff --git a/contrib/examples/git-gc.sh b/contrib/examples/git-gc.sh
deleted file mode 100755
index 1597e9f33f..0000000000
--- a/contrib/examples/git-gc.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2006, Shawn O. Pearce
-#
-# Cleanup unreachable files and optimize the repository.
-
-USAGE='[--prune]'
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-
-no_prune=:
-while test $# != 0
-do
- case "$1" in
- --prune)
- no_prune=
- ;;
- --)
- usage
- ;;
- esac
- shift
-done
-
-case "$(git config --get gc.packrefs)" in
-notbare|"")
- test $(is_bare_repository) = true || pack_refs=true;;
-*)
- pack_refs=$(git config --bool --get gc.packrefs)
-esac
-
-test "true" != "$pack_refs" ||
-git pack-refs --prune &&
-git reflog expire --all &&
-git-repack -a -d -l &&
-$no_prune git prune &&
-git rerere gc || exit
diff --git a/contrib/examples/git-ls-remote.sh b/contrib/examples/git-ls-remote.sh
deleted file mode 100755
index fec70bbf88..0000000000
--- a/contrib/examples/git-ls-remote.sh
+++ /dev/null
@@ -1,142 +0,0 @@
-#!/bin/sh
-#
-
-usage () {
- echo >&2 "usage: $0 [--heads] [--tags] [-u|--upload-pack <upload-pack>]"
- echo >&2 " <repository> <refs>..."
- exit 1;
-}
-
-die () {
- echo >&2 "$*"
- exit 1
-}
-
-exec=
-while test $# != 0
-do
- case "$1" in
- -h|--h|--he|--hea|--head|--heads)
- heads=heads; shift ;;
- -t|--t|--ta|--tag|--tags)
- tags=tags; shift ;;
- -u|--u|--up|--upl|--uploa|--upload|--upload-|--upload-p|--upload-pa|\
- --upload-pac|--upload-pack)
- shift
- exec="--upload-pack=$1"
- shift;;
- -u=*|--u=*|--up=*|--upl=*|--uplo=*|--uploa=*|--upload=*|\
- --upload-=*|--upload-p=*|--upload-pa=*|--upload-pac=*|--upload-pack=*)
- exec=--upload-pack=$(expr "z$1" : 'z-[^=]*=\(.*\)')
- shift;;
- --)
- shift; break ;;
- -*)
- usage ;;
- *)
- break ;;
- esac
-done
-
-case "$#" in 0) usage ;; esac
-
-case ",$heads,$tags," in
-,,,) heads=heads tags=tags other=other ;;
-esac
-
-. git-parse-remote
-peek_repo="$(get_remote_url "$@")"
-shift
-
-tmp=.ls-remote-$$
-trap "rm -fr $tmp-*" 0 1 2 3 15
-tmpdir=$tmp-d
-
-case "$peek_repo" in
-http://* | https://* | ftp://* )
- if [ -n "$GIT_SSL_NO_VERIFY" -o \
- "`git config --bool http.sslVerify`" = false ]; then
- curl_extra_args="-k"
- fi
- if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
- "`git config --bool http.noEPSV`" = true ]; then
- curl_extra_args="${curl_extra_args} --disable-epsv"
- fi
- curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" ||
- echo "failed slurping"
- ;;
-
-rsync://* )
- mkdir $tmpdir &&
- rsync -rlq "$peek_repo/HEAD" $tmpdir &&
- rsync -rq "$peek_repo/refs" $tmpdir || {
- echo "failed slurping"
- exit
- }
- head=$(cat "$tmpdir/HEAD") &&
- case "$head" in
- ref:' '*)
- head=$(expr "z$head" : 'zref: \(.*\)') &&
- head=$(cat "$tmpdir/$head") || exit
- esac &&
- echo "$head HEAD"
- (cd $tmpdir && find refs -type f) |
- while read path
- do
- tr -d '\012' <"$tmpdir/$path"
- echo " $path"
- done &&
- rm -fr $tmpdir
- ;;
-
-* )
- if test -f "$peek_repo" ; then
- git bundle list-heads "$peek_repo" ||
- echo "failed slurping"
- else
- git-peek-remote $exec "$peek_repo" ||
- echo "failed slurping"
- fi
- ;;
-esac |
-sort -t ' ' -k 2 |
-while read sha1 path
-do
- case "$sha1" in
- failed)
- exit 1 ;;
- esac
- case "$path" in
- refs/heads/*)
- group=heads ;;
- refs/tags/*)
- group=tags ;;
- *)
- group=other ;;
- esac
- case ",$heads,$tags,$other," in
- *,$group,*)
- ;;
- *)
- continue;;
- esac
- case "$#" in
- 0)
- match=yes ;;
- *)
- match=no
- for pat
- do
- case "/$path" in
- */$pat )
- match=yes
- break ;;
- esac
- done
- esac
- case "$match" in
- no)
- continue ;;
- esac
- echo "$sha1 $path"
-done
diff --git a/contrib/examples/git-merge-ours.sh b/contrib/examples/git-merge-ours.sh
deleted file mode 100755
index 29dba4ba3a..0000000000
--- a/contrib/examples/git-merge-ours.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-# Pretend we resolved the heads, but declare our tree trumps everybody else.
-#
-
-# We need to exit with 2 if the index does not match our HEAD tree,
-# because the current index is what we will be committing as the
-# merge result.
-
-git diff-index --quiet --cached HEAD -- || exit 2
-
-exit 0
diff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh
deleted file mode 100755
index 7b922c3948..0000000000
--- a/contrib/examples/git-merge.sh
+++ /dev/null
@@ -1,620 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-
-OPTIONS_KEEPDASHDASH=
-OPTIONS_SPEC="\
-git merge [options] <remote>...
-git merge [options] <msg> HEAD <remote>
---
-stat show a diffstat at the end of the merge
-n don't show a diffstat at the end of the merge
-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-only abort if fast-forward is not possible
-rerere-autoupdate update index with any reused conflict resolution
-s,strategy= merge strategy to use
-X= option for selected merge strategy
-m,message= message to be used for the merge commit (if any)
-"
-
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-require_work_tree
-cd_to_toplevel
-
-test -z "$(git ls-files -u)" ||
- die "Merge is not possible because you have unmerged files."
-
-! test -e "$GIT_DIR/MERGE_HEAD" ||
- die 'You have not concluded your merge (MERGE_HEAD exists).'
-
-LF='
-'
-
-all_strategies='recur recursive octopus resolve stupid ours subtree'
-all_strategies="$all_strategies recursive-ours recursive-theirs"
-not_strategies='base file index tree'
-default_twohead_strategies='recursive'
-default_octopus_strategies='octopus'
-no_fast_forward_strategies='subtree ours'
-no_trivial_strategies='recursive recur subtree ours recursive-ours recursive-theirs'
-use_strategies=
-xopt=
-
-allow_fast_forward=t
-fast_forward_only=
-allow_trivial_merge=t
-squash= no_commit= log_arg= rr_arg=
-
-dropsave() {
- rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
- "$GIT_DIR/MERGE_STASH" "$GIT_DIR/MERGE_MODE" || exit 1
-}
-
-savestate() {
- # Stash away any local modifications.
- git stash create >"$GIT_DIR/MERGE_STASH"
-}
-
-restorestate() {
- if test -f "$GIT_DIR/MERGE_STASH"
- then
- git reset --hard $head >/dev/null
- git stash apply $(cat "$GIT_DIR/MERGE_STASH")
- git update-index --refresh >/dev/null
- fi
-}
-
-finish_up_to_date () {
- case "$squash" in
- t)
- echo "$1 (nothing to squash)" ;;
- '')
- echo "$1" ;;
- esac
- dropsave
-}
-
-squash_message () {
- echo Squashed commit of the following:
- echo
- git log --no-merges --pretty=medium ^"$head" $remoteheads
-}
-
-finish () {
- if test '' = "$2"
- then
- rlogm="$GIT_REFLOG_ACTION"
- else
- echo "$2"
- rlogm="$GIT_REFLOG_ACTION: $2"
- fi
- case "$squash" in
- t)
- echo "Squash commit -- not updating HEAD"
- squash_message >"$GIT_DIR/SQUASH_MSG"
- ;;
- '')
- case "$merge_msg" in
- '')
- echo "No merge message -- not updating HEAD"
- ;;
- *)
- git update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
- git gc --auto
- ;;
- esac
- ;;
- esac
- case "$1" in
- '')
- ;;
- ?*)
- if test "$show_diffstat" = t
- then
- # We want color (if set), but no pager
- GIT_PAGER='' git diff --stat --summary -M "$head" "$1"
- fi
- ;;
- esac
-
- # Run a post-merge hook
- if test -x "$GIT_DIR"/hooks/post-merge
- then
- case "$squash" in
- t)
- "$GIT_DIR"/hooks/post-merge 1
- ;;
- '')
- "$GIT_DIR"/hooks/post-merge 0
- ;;
- esac
- fi
-}
-
-merge_name () {
- remote="$1"
- rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return
- if truname=$(expr "$remote" : '\(.*\)~[0-9]*$') &&
- git show-ref -q --verify "refs/heads/$truname" 2>/dev/null
- then
- echo "$rh branch '$truname' (early part) of ."
- return
- fi
- if found_ref=$(git rev-parse --symbolic-full-name --verify \
- "$remote" 2>/dev/null)
- then
- expanded=$(git check-ref-format --branch "$remote") ||
- exit
- if test "${found_ref#refs/heads/}" != "$found_ref"
- then
- echo "$rh branch '$expanded' of ."
- return
- elif test "${found_ref#refs/remotes/}" != "$found_ref"
- then
- echo "$rh remote branch '$expanded' of ."
- return
- fi
- fi
- if test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
- then
- sed -e 's/ not-for-merge / /' -e 1q \
- "$GIT_DIR/FETCH_HEAD"
- return
- fi
- echo "$rh commit '$remote'"
-}
-
-parse_config () {
- while test $# != 0; do
- case "$1" in
- -n|--no-stat|--no-summary)
- show_diffstat=false ;;
- --stat|--summary)
- show_diffstat=t ;;
- --log|--no-log)
- log_arg=$1 ;;
- --squash)
- test "$allow_fast_forward" = t ||
- die "You cannot combine --squash with --no-ff."
- squash=t no_commit=t ;;
- --no-squash)
- squash= no_commit= ;;
- --commit)
- no_commit= ;;
- --no-commit)
- no_commit=t ;;
- --ff)
- allow_fast_forward=t ;;
- --no-ff)
- test "$squash" != t ||
- die "You cannot combine --squash with --no-ff."
- test "$fast_forward_only" != t ||
- die "You cannot combine --ff-only with --no-ff."
- allow_fast_forward=f ;;
- --ff-only)
- test "$allow_fast_forward" != f ||
- die "You cannot combine --ff-only with --no-ff."
- fast_forward_only=t ;;
- --rerere-autoupdate|--no-rerere-autoupdate)
- rr_arg=$1 ;;
- -s|--strategy)
- shift
- case " $all_strategies " in
- *" $1 "*)
- use_strategies="$use_strategies$1 "
- ;;
- *)
- case " $not_strategies " in
- *" $1 "*)
- false
- esac &&
- type "git-merge-$1" >/dev/null 2>&1 ||
- die "available strategies are: $all_strategies"
- use_strategies="$use_strategies$1 "
- ;;
- esac
- ;;
- -X)
- shift
- xopt="${xopt:+$xopt }$(git rev-parse --sq-quote "--$1")"
- ;;
- -m|--message)
- shift
- merge_msg="$1"
- have_message=t
- ;;
- --)
- shift
- break ;;
- *) usage ;;
- esac
- shift
- done
- args_left=$#
-}
-
-test $# != 0 || usage
-
-have_message=
-
-if branch=$(git-symbolic-ref -q HEAD)
-then
- mergeopts=$(git config "branch.${branch#refs/heads/}.mergeoptions")
- if test -n "$mergeopts"
- then
- parse_config $mergeopts --
- fi
-fi
-
-parse_config "$@"
-while test $args_left -lt $#; do shift; done
-
-if test -z "$show_diffstat"; then
- test "$(git config --bool merge.diffstat)" = false && show_diffstat=false
- test "$(git config --bool merge.stat)" = false && show_diffstat=false
- test -z "$show_diffstat" && show_diffstat=t
-fi
-
-# This could be traditional "merge <msg> HEAD <commit>..." and the
-# way we can tell it is to see if the second token is HEAD, but some
-# people might have misused the interface and used a committish that
-# is the same as HEAD there instead. Traditional format never would
-# have "-m" so it is an additional safety measure to check for it.
-
-if test -z "$have_message" &&
- second_token=$(git rev-parse --verify "$2^0" 2>/dev/null) &&
- head_commit=$(git rev-parse --verify "HEAD" 2>/dev/null) &&
- test "$second_token" = "$head_commit"
-then
- merge_msg="$1"
- shift
- head_arg="$1"
- shift
-elif ! git rev-parse --verify HEAD >/dev/null 2>&1
-then
- # If the merged head is a valid one there is no reason to
- # forbid "git merge" into a branch yet to be born. We do
- # the same for "git pull".
- if test 1 -ne $#
- then
- echo >&2 "Can merge only exactly one commit into empty head"
- exit 1
- fi
-
- test "$squash" != t ||
- die "Squash commit into empty head not supported yet"
- test "$allow_fast_forward" = t ||
- die "Non-fast-forward into an empty head does not make sense"
- rh=$(git rev-parse --verify "$1^0") ||
- die "$1 - not something we can merge"
-
- git update-ref -m "initial pull" HEAD "$rh" "" &&
- git read-tree --reset -u HEAD
- exit
-
-else
- # We are invoked directly as the first-class UI.
- head_arg=HEAD
-
- # All the rest are the commits being merged; prepare
- # the standard merge summary message to be appended to
- # the given message. If remote is invalid we will die
- # later in the common codepath so we discard the error
- # in this loop.
- merge_msg="$(
- for remote
- do
- merge_name "$remote"
- done |
- if test "$have_message" = t
- then
- git fmt-merge-msg -m "$merge_msg" $log_arg
- else
- git fmt-merge-msg $log_arg
- fi
- )"
-fi
-head=$(git rev-parse --verify "$head_arg"^0) || usage
-
-# All the rest are remote heads
-test "$#" = 0 && usage ;# we need at least one remote head.
-set_reflog_action "merge $*"
-
-remoteheads=
-for remote
-do
- remotehead=$(git rev-parse --verify "$remote"^0 2>/dev/null) ||
- die "$remote - not something we can merge"
- remoteheads="${remoteheads}$remotehead "
- eval GITHEAD_$remotehead='"$remote"'
- export GITHEAD_$remotehead
-done
-set x $remoteheads ; shift
-
-case "$use_strategies" in
-'')
- case "$#" in
- 1)
- var="`git config --get pull.twohead`"
- if test -n "$var"
- then
- use_strategies="$var"
- else
- use_strategies="$default_twohead_strategies"
- fi ;;
- *)
- var="`git config --get pull.octopus`"
- if test -n "$var"
- then
- use_strategies="$var"
- else
- use_strategies="$default_octopus_strategies"
- fi ;;
- esac
- ;;
-esac
-
-for s in $use_strategies
-do
- for ss in $no_fast_forward_strategies
- do
- case " $s " in
- *" $ss "*)
- allow_fast_forward=f
- break
- ;;
- esac
- done
- for ss in $no_trivial_strategies
- do
- case " $s " in
- *" $ss "*)
- allow_trivial_merge=f
- break
- ;;
- esac
- done
-done
-
-case "$#" in
-1)
- common=$(git merge-base --all $head "$@")
- ;;
-*)
- common=$(git merge-base --all --octopus $head "$@")
- ;;
-esac
-echo "$head" >"$GIT_DIR/ORIG_HEAD"
-
-case "$allow_fast_forward,$#,$common,$no_commit" in
-?,*,'',*)
- # No common ancestors found. We need a real merge.
- ;;
-?,1,"$1",*)
- # If head can reach all the merge then we are up to date.
- # but first the most common case of merging one remote.
- finish_up_to_date "Already up-to-date."
- exit 0
- ;;
-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"
- if test -n "$have_message"
- then
- msg="$msg (no commit created; -m option ignored)"
- fi
- new_head=$(git rev-parse --verify "$1^0") &&
- git read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
- finish "$new_head" "$msg" || exit
- dropsave
- exit 0
- ;;
-?,1,?*"$LF"?*,*)
- # 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
- # one common.
- git update-index --refresh 2>/dev/null
- case "$allow_trivial_merge,$fast_forward_only" in
- t,)
- # See if it is really trivial.
- git var GIT_COMMITTER_IDENT >/dev/null || exit
- echo "Trying really trivial in-index merge..."
- if git read-tree --trivial -m -u -v $common $head "$1" &&
- result_tree=$(git write-tree)
- then
- echo "Wonderful."
- result_commit=$(
- printf '%s\n' "$merge_msg" |
- git commit-tree $result_tree -p HEAD -p "$1"
- ) || exit
- finish "$result_commit" "In-index merge"
- dropsave
- exit 0
- fi
- echo "Nope."
- esac
- ;;
-*)
- # An octopus. If we can reach all the remote we are up to date.
- up_to_date=t
- for remote
- do
- common_one=$(git merge-base --all $head $remote)
- if test "$common_one" != "$remote"
- then
- up_to_date=f
- break
- fi
- done
- if test "$up_to_date" = t
- then
- finish_up_to_date "Already up-to-date. Yeeah!"
- exit 0
- fi
- ;;
-esac
-
-if test "$fast_forward_only" = t
-then
- die "Not possible to fast-forward, aborting."
-fi
-
-# We are going to make a new commit.
-git var GIT_COMMITTER_IDENT >/dev/null || exit
-
-# At this point, we need a real merge. No matter what strategy
-# we use, it would operate on the index, possibly affecting the
-# working tree, and when resolved cleanly, have the desired tree
-# in the index -- this means that the index must be in sync with
-# the $head commit. The strategies are responsible to ensure this.
-
-case "$use_strategies" in
-?*' '?*)
- # Stash away the local changes so that we can try more than one.
- savestate
- single_strategy=no
- ;;
-*)
- rm -f "$GIT_DIR/MERGE_STASH"
- single_strategy=yes
- ;;
-esac
-
-result_tree= best_cnt=-1 best_strategy= wt_strategy=
-merge_was_ok=
-for strategy in $use_strategies
-do
- test "$wt_strategy" = '' || {
- echo "Rewinding the tree to pristine..."
- restorestate
- }
- case "$single_strategy" in
- no)
- echo "Trying merge strategy $strategy..."
- ;;
- esac
-
- # Remember which strategy left the state in the working tree
- wt_strategy=$strategy
-
- eval 'git-merge-$strategy '"$xopt"' $common -- "$head_arg" "$@"'
- exit=$?
- if test "$no_commit" = t && test "$exit" = 0
- then
- merge_was_ok=t
- exit=1 ;# pretend it left conflicts.
- fi
-
- test "$exit" = 0 || {
-
- # The backend exits with 1 when conflicts are left to be resolved,
- # with 2 when it does not handle the given merge at all.
-
- if test "$exit" -eq 1
- then
- cnt=`{
- git diff-files --name-only
- git ls-files --unmerged
- } | wc -l`
- if test $best_cnt -le 0 -o $cnt -le $best_cnt
- then
- best_strategy=$strategy
- best_cnt=$cnt
- fi
- fi
- continue
- }
-
- # Automerge succeeded.
- result_tree=$(git write-tree) && break
-done
-
-# If we have a resulting tree, that means the strategy module
-# auto resolved the merge cleanly.
-if test '' != "$result_tree"
-then
- if test "$allow_fast_forward" = "t"
- then
- parents=$(git merge-base --independent "$head" "$@")
- else
- parents=$(git rev-parse "$head" "$@")
- fi
- parents=$(echo "$parents" | sed -e 's/^/-p /')
- result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
- finish "$result_commit" "Merge made by $wt_strategy."
- dropsave
- exit 0
-fi
-
-# Pick the result from the best strategy and have the user fix it up.
-case "$best_strategy" in
-'')
- restorestate
- case "$use_strategies" in
- ?*' '?*)
- echo >&2 "No merge strategy handled the merge."
- ;;
- *)
- echo >&2 "Merge with strategy $use_strategies failed."
- ;;
- esac
- exit 2
- ;;
-"$wt_strategy")
- # We already have its result in the working tree.
- ;;
-*)
- echo "Rewinding the tree to pristine..."
- restorestate
- echo "Using the $best_strategy to prepare resolving by hand."
- git-merge-$best_strategy $common -- "$head_arg" "$@"
- ;;
-esac
-
-if test "$squash" = t
-then
- finish
-else
- for remote
- do
- echo $remote
- done >"$GIT_DIR/MERGE_HEAD"
- printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG" ||
- die "Could not write to $GIT_DIR/MERGE_MSG"
- if test "$allow_fast_forward" != t
- then
- printf "%s" no-ff
- else
- :
- fi >"$GIT_DIR/MERGE_MODE" ||
- die "Could not write to $GIT_DIR/MERGE_MODE"
-fi
-
-if test "$merge_was_ok" = t
-then
- echo >&2 \
- "Automatic merge went well; stopped before committing as requested"
- exit 0
-else
- {
- echo '
-Conflicts:
-'
- git ls-files --unmerged |
- sed -e 's/^[^ ]* / /' |
- uniq
- } >>"$GIT_DIR/MERGE_MSG"
- git rerere $rr_arg
- die "Automatic merge failed; fix conflicts and then commit the result."
-fi
diff --git a/contrib/examples/git-notes.sh b/contrib/examples/git-notes.sh
deleted file mode 100755
index e642e47d9f..0000000000
--- a/contrib/examples/git-notes.sh
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/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-remote.perl b/contrib/examples/git-remote.perl
deleted file mode 100755
index b17952a785..0000000000
--- a/contrib/examples/git-remote.perl
+++ /dev/null
@@ -1,474 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use Git;
-my $git = Git->repository();
-
-sub add_remote_config {
- my ($hash, $name, $what, $value) = @_;
- if ($what eq 'url') {
- # Having more than one is Ok -- it is used for push.
- if (! exists $hash->{'URL'}) {
- $hash->{$name}{'URL'} = $value;
- }
- }
- elsif ($what eq 'fetch') {
- $hash->{$name}{'FETCH'} ||= [];
- push @{$hash->{$name}{'FETCH'}}, $value;
- }
- elsif ($what eq 'push') {
- $hash->{$name}{'PUSH'} ||= [];
- push @{$hash->{$name}{'PUSH'}}, $value;
- }
- if (!exists $hash->{$name}{'SOURCE'}) {
- $hash->{$name}{'SOURCE'} = 'config';
- }
-}
-
-sub add_remote_remotes {
- my ($hash, $file, $name) = @_;
-
- if (exists $hash->{$name}) {
- $hash->{$name}{'WARNING'} = 'ignored due to config';
- return;
- }
-
- my $fh;
- if (!open($fh, '<', $file)) {
- print STDERR "Warning: cannot open $file\n";
- return;
- }
- my $it = { 'SOURCE' => 'remotes' };
- $hash->{$name} = $it;
- while (<$fh>) {
- chomp;
- if (/^URL:\s*(.*)$/) {
- # Having more than one is Ok -- it is used for push.
- if (! exists $it->{'URL'}) {
- $it->{'URL'} = $1;
- }
- }
- elsif (/^Push:\s*(.*)$/) {
- $it->{'PUSH'} ||= [];
- push @{$it->{'PUSH'}}, $1;
- }
- elsif (/^Pull:\s*(.*)$/) {
- $it->{'FETCH'} ||= [];
- push @{$it->{'FETCH'}}, $1;
- }
- elsif (/^\#/) {
- ; # ignore
- }
- else {
- print STDERR "Warning: funny line in $file: $_\n";
- }
- }
- close($fh);
-}
-
-sub list_remote {
- my ($git) = @_;
- my %seen = ();
- my @remotes = eval {
- $git->command(qw(config --get-regexp), '^remote\.');
- };
- for (@remotes) {
- if (/^remote\.(\S+?)\.([^.\s]+)\s+(.*)$/) {
- add_remote_config(\%seen, $1, $2, $3);
- }
- }
-
- my $dir = $git->repo_path() . "/remotes";
- if (opendir(my $dh, $dir)) {
- local $_;
- while ($_ = readdir($dh)) {
- chomp;
- next if (! -f "$dir/$_" || ! -r _);
- add_remote_remotes(\%seen, "$dir/$_", $_);
- }
- }
-
- return \%seen;
-}
-
-sub add_branch_config {
- my ($hash, $name, $what, $value) = @_;
- if ($what eq 'remote') {
- if (exists $hash->{$name}{'REMOTE'}) {
- print STDERR "Warning: more than one branch.$name.remote\n";
- }
- $hash->{$name}{'REMOTE'} = $value;
- }
- elsif ($what eq 'merge') {
- $hash->{$name}{'MERGE'} ||= [];
- push @{$hash->{$name}{'MERGE'}}, $value;
- }
-}
-
-sub list_branch {
- my ($git) = @_;
- my %seen = ();
- my @branches = eval {
- $git->command(qw(config --get-regexp), '^branch\.');
- };
- for (@branches) {
- if (/^branch\.([^.]*)\.(\S*)\s+(.*)$/) {
- add_branch_config(\%seen, $1, $2, $3);
- }
- }
-
- return \%seen;
-}
-
-my $remote = list_remote($git);
-my $branch = list_branch($git);
-
-sub update_ls_remote {
- my ($harder, $info) = @_;
-
- return if (($harder == 0) ||
- (($harder == 1) && exists $info->{'LS_REMOTE'}));
-
- my @ref = map { s|refs/heads/||; $_; } keys %{$git->remote_refs($info->{'URL'}, [ 'heads' ])};
- $info->{'LS_REMOTE'} = \@ref;
-}
-
-sub list_wildcard_mapping {
- my ($forced, $ours, $ls) = @_;
- my %refs;
- for (@$ls) {
- $refs{$_} = 01; # bit #0 to say "they have"
- }
- for ($git->command('for-each-ref', "refs/remotes/$ours")) {
- chomp;
- next unless (s|^[0-9a-f]{40}\s[a-z]+\srefs/remotes/$ours/||);
- next if ($_ eq 'HEAD');
- $refs{$_} ||= 0;
- $refs{$_} |= 02; # bit #1 to say "we have"
- }
- my (@new, @stale, @tracked);
- for (sort keys %refs) {
- my $have = $refs{$_};
- if ($have == 1) {
- push @new, $_;
- }
- elsif ($have == 2) {
- push @stale, $_;
- }
- elsif ($have == 3) {
- push @tracked, $_;
- }
- }
- return \@new, \@stale, \@tracked;
-}
-
-sub list_mapping {
- my ($name, $info) = @_;
- my $fetch = $info->{'FETCH'};
- my $ls = $info->{'LS_REMOTE'};
- my (@new, @stale, @tracked);
-
- for (@$fetch) {
- next unless (/(\+)?([^:]+):(.*)/);
- my ($forced, $theirs, $ours) = ($1, $2, $3);
- if ($theirs eq 'refs/heads/*' &&
- $ours =~ /^refs\/remotes\/(.*)\/\*$/) {
- # wildcard mapping
- my ($w_new, $w_stale, $w_tracked)
- = list_wildcard_mapping($forced, $1, $ls);
- push @new, @$w_new;
- push @stale, @$w_stale;
- push @tracked, @$w_tracked;
- }
- elsif ($theirs =~ /\*/ || $ours =~ /\*/) {
- print STDERR "Warning: unrecognized mapping in remotes.$name.fetch: $_\n";
- }
- elsif ($theirs =~ s|^refs/heads/||) {
- if (!grep { $_ eq $theirs } @$ls) {
- push @stale, $theirs;
- }
- elsif ($ours ne '') {
- push @tracked, $theirs;
- }
- }
- }
- return \@new, \@stale, \@tracked;
-}
-
-sub show_mapping {
- my ($name, $info) = @_;
- my ($new, $stale, $tracked) = list_mapping($name, $info);
- if (@$new) {
- print " New remote branches (next fetch will store in remotes/$name)\n";
- print " @$new\n";
- }
- if (@$stale) {
- print " Stale tracking branches in remotes/$name (use 'git remote prune')\n";
- print " @$stale\n";
- }
- if (@$tracked) {
- print " Tracked remote branches\n";
- print " @$tracked\n";
- }
-}
-
-sub prune_remote {
- my ($name, $ls_remote) = @_;
- if (!exists $remote->{$name}) {
- print STDERR "No such remote $name\n";
- return 1;
- }
- my $info = $remote->{$name};
- update_ls_remote($ls_remote, $info);
-
- my ($new, $stale, $tracked) = list_mapping($name, $info);
- my $prefix = "refs/remotes/$name";
- foreach my $to_prune (@$stale) {
- my @v = $git->command(qw(rev-parse --verify), "$prefix/$to_prune");
- $git->command(qw(update-ref -d), "$prefix/$to_prune", $v[0]);
- }
- return 0;
-}
-
-sub show_remote {
- my ($name, $ls_remote) = @_;
- if (!exists $remote->{$name}) {
- print STDERR "No such remote $name\n";
- return 1;
- }
- my $info = $remote->{$name};
- update_ls_remote($ls_remote, $info);
-
- print "* remote $name\n";
- print " URL: $info->{'URL'}\n";
- for my $branchname (sort keys %$branch) {
- next unless (defined $branch->{$branchname}{'REMOTE'} &&
- $branch->{$branchname}{'REMOTE'} eq $name);
- my @merged = map {
- s|^refs/heads/||;
- $_;
- } split(' ',"@{$branch->{$branchname}{'MERGE'}}");
- next unless (@merged);
- print " Remote branch(es) merged with 'git pull' while on branch $branchname\n";
- print " @merged\n";
- }
- if ($info->{'LS_REMOTE'}) {
- show_mapping($name, $info);
- }
- if ($info->{'PUSH'}) {
- my @pushed = map {
- s|^refs/heads/||;
- s|^\+refs/heads/|+|;
- s|:refs/heads/|:|;
- $_;
- } @{$info->{'PUSH'}};
- print " Local branch(es) pushed with 'git push'\n";
- print " @pushed\n";
- }
- return 0;
-}
-
-sub add_remote {
- my ($name, $url, $opts) = @_;
- if (exists $remote->{$name}) {
- print STDERR "remote $name already exists.\n";
- exit(1);
- }
- $git->command('config', "remote.$name.url", $url);
- my $track = $opts->{'track'} || ["*"];
-
- for (@$track) {
- $git->command('config', '--add', "remote.$name.fetch",
- $opts->{'mirror'} ?
- "+refs/$_:refs/$_" :
- "+refs/heads/$_:refs/remotes/$name/$_");
- }
- if ($opts->{'fetch'}) {
- $git->command('fetch', $name);
- }
- if (exists $opts->{'master'}) {
- $git->command('symbolic-ref', "refs/remotes/$name/HEAD",
- "refs/remotes/$name/$opts->{'master'}");
- }
-}
-
-sub update_remote {
- my ($name) = @_;
- my @remotes;
-
- my $conf = $git->config("remotes." . $name);
- if (defined($conf)) {
- @remotes = split(' ', $conf);
- } elsif ($name eq 'default') {
- @remotes = ();
- for (sort keys %$remote) {
- my $do_fetch = $git->config_bool("remote." . $_ .
- ".skipDefaultUpdate");
- unless ($do_fetch) {
- push @remotes, $_;
- }
- }
- } else {
- print STDERR "Remote group $name does not exist.\n";
- exit(1);
- }
- for (@remotes) {
- print "Updating $_\n";
- $git->command('fetch', "$_");
- }
-}
-
-sub rm_remote {
- my ($name) = @_;
- if (!exists $remote->{$name}) {
- print STDERR "No such remote $name\n";
- return 1;
- }
-
- $git->command('config', '--remove-section', "remote.$name");
-
- eval {
- my @trackers = $git->command('config', '--get-regexp',
- 'branch.*.remote', $name);
- for (@trackers) {
- /^branch\.(.*)?\.remote/;
- $git->config('--unset', "branch.$1.remote");
- $git->config('--unset', "branch.$1.merge");
- }
- };
-
- my @refs = $git->command('for-each-ref',
- '--format=%(refname) %(objectname)', "refs/remotes/$name");
- for (@refs) {
- my ($ref, $object) = split;
- $git->command(qw(update-ref -d), $ref, $object);
- }
- return 0;
-}
-
-sub add_usage {
- print STDERR "Usage: git remote add [-f] [-t track]* [-m master] <name> <url>\n";
- exit(1);
-}
-
-my $VERBOSE = 0;
-@ARGV = grep {
- if ($_ eq '-v' or $_ eq '--verbose') {
- $VERBOSE=1;
- 0
- } else {
- 1
- }
-} @ARGV;
-
-if (!@ARGV) {
- for (sort keys %$remote) {
- print "$_";
- print "\t$remote->{$_}->{URL}" if $VERBOSE;
- print "\n";
- }
-}
-elsif ($ARGV[0] eq 'show') {
- my $ls_remote = 1;
- my $i;
- for ($i = 1; $i < @ARGV; $i++) {
- if ($ARGV[$i] eq '-n') {
- $ls_remote = 0;
- }
- else {
- last;
- }
- }
- if ($i >= @ARGV) {
- print STDERR "Usage: git remote show <remote>\n";
- exit(1);
- }
- my $status = 0;
- for (; $i < @ARGV; $i++) {
- $status |= show_remote($ARGV[$i], $ls_remote);
- }
- exit($status);
-}
-elsif ($ARGV[0] eq 'update') {
- if (@ARGV <= 1) {
- update_remote("default");
- exit(1);
- }
- for (my $i = 1; $i < @ARGV; $i++) {
- update_remote($ARGV[$i]);
- }
-}
-elsif ($ARGV[0] eq 'prune') {
- my $ls_remote = 1;
- my $i;
- for ($i = 1; $i < @ARGV; $i++) {
- if ($ARGV[$i] eq '-n') {
- $ls_remote = 0;
- }
- else {
- last;
- }
- }
- if ($i >= @ARGV) {
- print STDERR "Usage: git remote prune <remote>\n";
- exit(1);
- }
- my $status = 0;
- for (; $i < @ARGV; $i++) {
- $status |= prune_remote($ARGV[$i], $ls_remote);
- }
- exit($status);
-}
-elsif ($ARGV[0] eq 'add') {
- my %opts = ();
- while (1 < @ARGV && $ARGV[1] =~ /^-/) {
- my $opt = $ARGV[1];
- shift @ARGV;
- if ($opt eq '-f' || $opt eq '--fetch') {
- $opts{'fetch'} = 1;
- next;
- }
- if ($opt eq '-t' || $opt eq '--track') {
- if (@ARGV < 1) {
- add_usage();
- }
- $opts{'track'} ||= [];
- push @{$opts{'track'}}, $ARGV[1];
- shift @ARGV;
- next;
- }
- if ($opt eq '-m' || $opt eq '--master') {
- if ((@ARGV < 1) || exists $opts{'master'}) {
- add_usage();
- }
- $opts{'master'} = $ARGV[1];
- shift @ARGV;
- next;
- }
- if ($opt eq '--mirror') {
- $opts{'mirror'} = 1;
- next;
- }
- add_usage();
- }
- if (@ARGV != 3) {
- add_usage();
- }
- add_remote($ARGV[1], $ARGV[2], \%opts);
-}
-elsif ($ARGV[0] eq 'rm') {
- if (@ARGV <= 1) {
- print STDERR "Usage: git remote rm <remote>\n";
- exit(1);
- }
- exit(rm_remote($ARGV[1]));
-}
-else {
- print STDERR "Usage: git remote\n";
- print STDERR " git remote add <name> <url>\n";
- print STDERR " git remote rm <name>\n";
- print STDERR " git remote show <name>\n";
- print STDERR " git remote prune <name>\n";
- print STDERR " git remote update [group]\n";
- exit(1);
-}
diff --git a/contrib/examples/git-rerere.perl b/contrib/examples/git-rerere.perl
deleted file mode 100755
index 4f692091e7..0000000000
--- a/contrib/examples/git-rerere.perl
+++ /dev/null
@@ -1,284 +0,0 @@
-#!/usr/bin/perl
-#
-# REuse REcorded REsolve. This tool records a conflicted automerge
-# result and its hand resolution, and helps to resolve future
-# automerge that results in the same conflict.
-#
-# To enable this feature, create a directory 'rr-cache' under your
-# .git/ directory.
-
-use Digest;
-use File::Path;
-use File::Copy;
-
-my $git_dir = $::ENV{GIT_DIR} || ".git";
-my $rr_dir = "$git_dir/rr-cache";
-my $merge_rr = "$git_dir/rr-cache/MERGE_RR";
-
-my %merge_rr = ();
-
-sub read_rr {
- if (!-f $merge_rr) {
- %merge_rr = ();
- return;
- }
- my $in;
- local $/ = "\0";
- open $in, "<$merge_rr" or die "$!: $merge_rr";
- while (<$in>) {
- chomp;
- my ($name, $path) = /^([0-9a-f]{40})\t(.*)$/s;
- $merge_rr{$path} = $name;
- }
- close $in;
-}
-
-sub write_rr {
- my $out;
- open $out, ">$merge_rr" or die "$!: $merge_rr";
- for my $path (sort keys %merge_rr) {
- my $name = $merge_rr{$path};
- print $out "$name\t$path\0";
- }
- close $out;
-}
-
-sub compute_conflict_name {
- my ($path) = @_;
- my @side = ();
- my $in;
- open $in, "<$path" or die "$!: $path";
-
- my $sha1 = Digest->new("SHA-1");
- my $hunk = 0;
- while (<$in>) {
- if (/^<<<<<<< .*/) {
- $hunk++;
- @side = ([], undef);
- }
- elsif (/^=======$/) {
- $side[1] = [];
- }
- elsif (/^>>>>>>> .*/) {
- my ($one, $two);
- $one = join('', @{$side[0]});
- $two = join('', @{$side[1]});
- if ($two le $one) {
- ($one, $two) = ($two, $one);
- }
- $sha1->add($one);
- $sha1->add("\0");
- $sha1->add($two);
- $sha1->add("\0");
- @side = ();
- }
- elsif (@side == 0) {
- next;
- }
- elsif (defined $side[1]) {
- push @{$side[1]}, $_;
- }
- else {
- push @{$side[0]}, $_;
- }
- }
- close $in;
- return ($sha1->hexdigest, $hunk);
-}
-
-sub record_preimage {
- my ($path, $name) = @_;
- my @side = ();
- my ($in, $out);
- open $in, "<$path" or die "$!: $path";
- open $out, ">$name" or die "$!: $name";
-
- while (<$in>) {
- if (/^<<<<<<< .*/) {
- @side = ([], undef);
- }
- elsif (/^=======$/) {
- $side[1] = [];
- }
- elsif (/^>>>>>>> .*/) {
- my ($one, $two);
- $one = join('', @{$side[0]});
- $two = join('', @{$side[1]});
- if ($two le $one) {
- ($one, $two) = ($two, $one);
- }
- print $out "<<<<<<<\n";
- print $out $one;
- print $out "=======\n";
- print $out $two;
- print $out ">>>>>>>\n";
- @side = ();
- }
- elsif (@side == 0) {
- print $out $_;
- }
- elsif (defined $side[1]) {
- push @{$side[1]}, $_;
- }
- else {
- push @{$side[0]}, $_;
- }
- }
- close $out;
- close $in;
-}
-
-sub find_conflict {
- my $in;
- local $/ = "\0";
- my $pid = open($in, '-|');
- die "$!" unless defined $pid;
- if (!$pid) {
- exec(qw(git ls-files -z -u)) or die "$!: ls-files";
- }
- my %path = ();
- my @path = ();
- while (<$in>) {
- chomp;
- my ($mode, $sha1, $stage, $path) =
- /^([0-7]+) ([0-9a-f]{40}) ([123])\t(.*)$/s;
- $path{$path} |= (1 << $stage);
- }
- close $in;
- while (my ($path, $status) = each %path) {
- if ($status == 14) { push @path, $path; }
- }
- return @path;
-}
-
-sub merge {
- my ($name, $path) = @_;
- record_preimage($path, "$rr_dir/$name/thisimage");
- unless (system('git', 'merge-file', map { "$rr_dir/$name/${_}image" }
- qw(this pre post))) {
- my $in;
- open $in, "<$rr_dir/$name/thisimage" or
- die "$!: $name/thisimage";
- my $out;
- open $out, ">$path" or die "$!: $path";
- while (<$in>) { print $out $_; }
- close $in;
- close $out;
- return 1;
- }
- return 0;
-}
-
-sub garbage_collect_rerere {
- # We should allow specifying these from the command line and
- # that is why the caller gives @ARGV to us, but I am lazy.
-
- my $cutoff_noresolve = 15; # two weeks
- my $cutoff_resolve = 60; # two months
- my @to_remove;
- while (<$rr_dir/*/preimage>) {
- my ($dir) = /^(.*)\/preimage$/;
- my $cutoff = ((-f "$dir/postimage")
- ? $cutoff_resolve
- : $cutoff_noresolve);
- my $age = -M "$_";
- if ($cutoff <= $age) {
- push @to_remove, $dir;
- }
- }
- if (@to_remove) {
- rmtree(\@to_remove);
- }
-}
-
--d "$rr_dir" || exit(0);
-
-read_rr();
-
-if (@ARGV) {
- my $arg = shift @ARGV;
- if ($arg eq 'clear') {
- for my $path (keys %merge_rr) {
- my $name = $merge_rr{$path};
- if (-d "$rr_dir/$name" &&
- ! -f "$rr_dir/$name/postimage") {
- rmtree(["$rr_dir/$name"]);
- }
- }
- unlink $merge_rr;
- }
- elsif ($arg eq 'status') {
- for my $path (keys %merge_rr) {
- print $path, "\n";
- }
- }
- elsif ($arg eq 'diff') {
- for my $path (keys %merge_rr) {
- my $name = $merge_rr{$path};
- system('diff', ((@ARGV == 0) ? ('-u') : @ARGV),
- '-L', "a/$path", '-L', "b/$path",
- "$rr_dir/$name/preimage", $path);
- }
- }
- elsif ($arg eq 'gc') {
- garbage_collect_rerere(@ARGV);
- }
- else {
- die "$0 unknown command: $arg\n";
- }
- exit 0;
-}
-
-my %conflict = map { $_ => 1 } find_conflict();
-
-# MERGE_RR records paths with conflicts immediately after merge
-# failed. Some of the conflicted paths might have been hand resolved
-# in the working tree since then, but the initial run would catch all
-# and register their preimages.
-
-for my $path (keys %conflict) {
- # This path has conflict. If it is not recorded yet,
- # record the pre-image.
- if (!exists $merge_rr{$path}) {
- my ($name, $hunk) = compute_conflict_name($path);
- next unless ($hunk);
- $merge_rr{$path} = $name;
- if (! -d "$rr_dir/$name") {
- mkpath("$rr_dir/$name", 0, 0777);
- print STDERR "Recorded preimage for '$path'\n";
- record_preimage($path, "$rr_dir/$name/preimage");
- }
- }
-}
-
-# Now some of the paths that had conflicts earlier might have been
-# hand resolved. Others may be similar to a conflict already that
-# was resolved before.
-
-for my $path (keys %merge_rr) {
- my $name = $merge_rr{$path};
-
- # We could resolve this automatically if we have images.
- if (-f "$rr_dir/$name/preimage" &&
- -f "$rr_dir/$name/postimage") {
- if (merge($name, $path)) {
- print STDERR "Resolved '$path' using previous resolution.\n";
- # Then we do not have to worry about this path
- # anymore.
- delete $merge_rr{$path};
- next;
- }
- }
-
- # Let's see if we have resolved it.
- (undef, my $hunk) = compute_conflict_name($path);
- next if ($hunk);
-
- print STDERR "Recorded resolution for '$path'.\n";
- copy($path, "$rr_dir/$name/postimage");
- # And we do not have to worry about this path anymore.
- delete $merge_rr{$path};
-}
-
-# Write out the rest.
-write_rr();
diff --git a/contrib/examples/git-reset.sh b/contrib/examples/git-reset.sh
deleted file mode 100755
index bafeb52cd1..0000000000
--- a/contrib/examples/git-reset.sh
+++ /dev/null
@@ -1,106 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
-#
-USAGE='[--mixed | --soft | --hard] [<commit-ish>] [ [--] <paths>...]'
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-set_reflog_action "reset $*"
-require_work_tree
-
-update= reset_type=--mixed
-unset rev
-
-while test $# != 0
-do
- case "$1" in
- --mixed | --soft | --hard)
- reset_type="$1"
- ;;
- --)
- break
- ;;
- -*)
- usage
- ;;
- *)
- rev=$(git rev-parse --verify "$1") || exit
- shift
- break
- ;;
- esac
- shift
-done
-
-: ${rev=HEAD}
-rev=$(git rev-parse --verify $rev^0) || exit
-
-# Skip -- in "git reset HEAD -- foo" and "git reset -- foo".
-case "$1" in --) shift ;; esac
-
-# git reset --mixed tree [--] paths... can be used to
-# load chosen paths from the tree into the index without
-# affecting the working tree nor HEAD.
-if test $# != 0
-then
- test "$reset_type" = "--mixed" ||
- die "Cannot do partial $reset_type reset."
-
- git diff-index --cached $rev -- "$@" |
- sed -e 's/^:\([0-7][0-7]*\) [0-7][0-7]* \([0-9a-f][0-9a-f]*\) [0-9a-f][0-9a-f]* [A-Z] \(.*\)$/\1 \2 \3/' |
- git update-index --add --remove --index-info || exit
- git update-index --refresh
- exit
-fi
-
-cd_to_toplevel
-
-if test "$reset_type" = "--hard"
-then
- update=-u
-fi
-
-# Soft reset does not touch the index file nor the working tree
-# at all, but requires them in a good order. Other resets reset
-# the index file to the tree object we are switching to.
-if test "$reset_type" = "--soft"
-then
- if test -f "$GIT_DIR/MERGE_HEAD" ||
- test "" != "$(git ls-files --unmerged)"
- then
- die "Cannot do a soft reset in the middle of a merge."
- fi
-else
- git read-tree -v --reset $update "$rev" || exit
-fi
-
-# Any resets update HEAD to the head being switched to.
-if orig=$(git rev-parse --verify HEAD 2>/dev/null)
-then
- echo "$orig" >"$GIT_DIR/ORIG_HEAD"
-else
- rm -f "$GIT_DIR/ORIG_HEAD"
-fi
-git update-ref -m "$GIT_REFLOG_ACTION" HEAD "$rev"
-update_ref_status=$?
-
-case "$reset_type" in
---hard )
- test $update_ref_status = 0 && {
- printf "HEAD is now at "
- GIT_PAGER= git log --max-count=1 --pretty=oneline \
- --abbrev-commit HEAD
- }
- ;;
---soft )
- ;; # Nothing else to do
---mixed )
- # Report what has not been updated.
- git update-index --refresh
- ;;
-esac
-
-rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR" \
- "$GIT_DIR/SQUASH_MSG" "$GIT_DIR/MERGE_MSG"
-
-exit $update_ref_status
diff --git a/contrib/examples/git-resolve.sh b/contrib/examples/git-resolve.sh
deleted file mode 100755
index 8f98142f77..0000000000
--- a/contrib/examples/git-resolve.sh
+++ /dev/null
@@ -1,112 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Linus Torvalds
-#
-# Resolve two trees.
-#
-
-echo 'WARNING: This command is DEPRECATED and will be removed very soon.' >&2
-echo 'WARNING: Please use git-merge or git-pull instead.' >&2
-sleep 2
-
-USAGE='<head> <remote> <merge-message>'
-. git-sh-setup
-
-dropheads() {
- rm -f -- "$GIT_DIR/MERGE_HEAD" \
- "$GIT_DIR/LAST_MERGE" || exit 1
-}
-
-head=$(git rev-parse --verify "$1"^0) &&
-merge=$(git rev-parse --verify "$2"^0) &&
-merge_name="$2" &&
-merge_msg="$3" || usage
-
-#
-# The remote name is just used for the message,
-# but we do want it.
-#
-if [ -z "$head" -o -z "$merge" -o -z "$merge_msg" ]; then
- usage
-fi
-
-dropheads
-echo $head > "$GIT_DIR"/ORIG_HEAD
-echo $merge > "$GIT_DIR"/LAST_MERGE
-
-common=$(git merge-base $head $merge)
-if [ -z "$common" ]; then
- die "Unable to find common commit between" $merge $head
-fi
-
-case "$common" in
-"$merge")
- echo "Already up-to-date. Yeeah!"
- dropheads
- exit 0
- ;;
-"$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" \
- HEAD "$merge" "$head"
- git diff-tree -p $head $merge | git apply --stat
- dropheads
- exit 0
- ;;
-esac
-
-# We are going to make a new commit.
-git var GIT_COMMITTER_IDENT >/dev/null || exit
-
-# Find an optimum merge base if there are more than one candidates.
-LF='
-'
-common=$(git merge-base -a $head $merge)
-case "$common" in
-?*"$LF"?*)
- echo "Trying to find the optimum merge base."
- G=.tmp-index$$
- best=
- best_cnt=-1
- for c in $common
- do
- rm -f $G
- GIT_INDEX_FILE=$G git read-tree -m $c $head $merge \
- 2>/dev/null || continue
- # Count the paths that are unmerged.
- cnt=`GIT_INDEX_FILE=$G git ls-files --unmerged | wc -l`
- if test $best_cnt -le 0 -o $cnt -le $best_cnt
- then
- best=$c
- best_cnt=$cnt
- if test "$best_cnt" -eq 0
- then
- # Cannot do any better than all trivial merge.
- break
- fi
- fi
- done
- rm -f $G
- common="$best"
-esac
-
-echo "Trying to merge $merge into $head using $common."
-git update-index --refresh 2>/dev/null
-git read-tree -u -m $common $head $merge || exit 1
-result_tree=$(git write-tree 2> /dev/null)
-if [ $? -ne 0 ]; then
- echo "Simple merge failed, trying Automatic merge"
- git-merge-index -o git-merge-one-file -a
- if [ $? -ne 0 ]; then
- echo $merge > "$GIT_DIR"/MERGE_HEAD
- die "Automatic merge failed, fix up by hand"
- fi
- result_tree=$(git write-tree) || exit 1
-fi
-result_commit=$(echo "$merge_msg" | git commit-tree $result_tree -p $head -p $merge)
-echo "Committed merge $result_commit"
-git update-ref -m "resolve $merge_name: In-index merge" \
- HEAD "$result_commit" "$head"
-git diff-tree -p $head $result_commit | git apply --stat
-dropheads
diff --git a/contrib/examples/git-revert.sh b/contrib/examples/git-revert.sh
deleted file mode 100755
index 6bf155cbdb..0000000000
--- a/contrib/examples/git-revert.sh
+++ /dev/null
@@ -1,207 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Linus Torvalds
-# Copyright (c) 2005 Junio C Hamano
-#
-
-case "$0" in
-*-revert* )
- test -t 0 && edit=-e
- replay=
- me=revert
- USAGE='[--edit | --no-edit] [-n] <commit-ish>' ;;
-*-cherry-pick* )
- replay=t
- edit=
- me=cherry-pick
- USAGE='[--edit] [-n] [-r] [-x] <commit-ish>' ;;
-* )
- echo >&2 "What are you talking about?"
- exit 1 ;;
-esac
-
-SUBDIRECTORY_OK=Yes ;# we will cd up
-. git-sh-setup
-require_work_tree
-cd_to_toplevel
-
-no_commit=
-xopt=
-while case "$#" in 0) break ;; esac
-do
- case "$1" in
- -n|--n|--no|--no-|--no-c|--no-co|--no-com|--no-comm|\
- --no-commi|--no-commit)
- no_commit=t
- ;;
- -e|--e|--ed|--edi|--edit)
- edit=-e
- ;;
- --n|--no|--no-|--no-e|--no-ed|--no-edi|--no-edit)
- edit=
- ;;
- -r)
- : no-op ;;
- -x|--i-really-want-to-expose-my-private-commit-object-name)
- replay=
- ;;
- -X?*)
- xopt="$xopt$(git rev-parse --sq-quote "--${1#-X}")"
- ;;
- --strategy-option=*)
- xopt="$xopt$(git rev-parse --sq-quote "--${1#--strategy-option=}")"
- ;;
- -X|--strategy-option)
- shift
- xopt="$xopt$(git rev-parse --sq-quote "--$1")"
- ;;
- -*)
- usage
- ;;
- *)
- break
- ;;
- esac
- shift
-done
-
-set_reflog_action "$me"
-
-test "$me,$replay" = "revert,t" && usage
-
-case "$no_commit" in
-t)
- # We do not intend to commit immediately. We just want to
- # merge the differences in.
- head=$(git-write-tree) ||
- die "Your index file is unmerged."
- ;;
-*)
- head=$(git-rev-parse --verify HEAD) ||
- die "You do not have a valid HEAD"
- files=$(git-diff-index --cached --name-only $head) || exit
- if [ "$files" ]; then
- die "Dirty index: cannot $me (dirty: $files)"
- fi
- ;;
-esac
-
-rev=$(git-rev-parse --verify "$@") &&
-commit=$(git-rev-parse --verify "$rev^0") ||
- die "Not a single commit $@"
-prev=$(git-rev-parse --verify "$commit^1" 2>/dev/null) ||
- die "Cannot run $me a root commit"
-git-rev-parse --verify "$commit^2" >/dev/null 2>&1 &&
- die "Cannot run $me a multi-parent commit."
-
-encoding=$(git config i18n.commitencoding || echo UTF-8)
-
-# "commit" is an existing commit. We would want to apply
-# the difference it introduces since its first parent "prev"
-# on top of the current HEAD if we are cherry-pick. Or the
-# reverse of it if we are revert.
-
-case "$me" in
-revert)
- git show -s --pretty=oneline --encoding="$encoding" $commit |
- sed -e '
- s/^[^ ]* /Revert "/
- s/$/"/
- '
- echo
- echo "This reverts commit $commit."
- test "$rev" = "$commit" ||
- echo "(original 'git revert' arguments: $@)"
- base=$commit next=$prev
- ;;
-
-cherry-pick)
- pick_author_script='
- /^author /{
- s/'\''/'\''\\'\'\''/g
- h
- s/^author \([^<]*\) <[^>]*> .*$/\1/
- s/'\''/'\''\'\'\''/g
- s/.*/GIT_AUTHOR_NAME='\''&'\''/p
-
- g
- s/^author [^<]* <\([^>]*\)> .*$/\1/
- s/'\''/'\''\'\'\''/g
- s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
-
- g
- s/^author [^<]* <[^>]*> \(.*\)$/\1/
- s/'\''/'\''\'\'\''/g
- s/.*/GIT_AUTHOR_DATE='\''&'\''/p
-
- q
- }'
-
- logmsg=`git show -s --pretty=raw --encoding="$encoding" "$commit"`
- set_author_env=`echo "$logmsg" |
- LANG=C LC_ALL=C sed -ne "$pick_author_script"`
- eval "$set_author_env"
- export GIT_AUTHOR_NAME
- export GIT_AUTHOR_EMAIL
- export GIT_AUTHOR_DATE
-
- echo "$logmsg" |
- sed -e '1,/^$/d' -e 's/^ //'
- case "$replay" in
- '')
- echo "(cherry picked from commit $commit)"
- test "$rev" = "$commit" ||
- echo "(original 'git cherry-pick' arguments: $@)"
- ;;
- esac
- base=$prev next=$commit
- ;;
-
-esac >.msg
-
-eval GITHEAD_$head=HEAD
-eval GITHEAD_$next='`git show -s \
- --pretty=oneline --encoding="$encoding" "$commit" |
- sed -e "s/^[^ ]* //"`'
-export GITHEAD_$head GITHEAD_$next
-
-# This three way merge is an interesting one. We are at
-# $head, and would want to apply the change between $commit
-# and $prev on top of us (when reverting), or the change between
-# $prev and $commit on top of us (when cherry-picking or replaying).
-
-eval "git merge-recursive $xopt $base -- $head $next" &&
-result=$(git-write-tree 2>/dev/null) || {
- mv -f .msg "$GIT_DIR/MERGE_MSG"
- {
- echo '
-Conflicts:
-'
- git ls-files --unmerged |
- sed -e 's/^[^ ]* / /' |
- uniq
- } >>"$GIT_DIR/MERGE_MSG"
- echo >&2 "Automatic $me failed. After resolving the conflicts,"
- echo >&2 "mark the corrected paths with 'git-add <paths>'"
- echo >&2 "and commit the result."
- case "$me" in
- cherry-pick)
- echo >&2 "You may choose to use the following when making"
- echo >&2 "the commit:"
- echo >&2 "$set_author_env"
- esac
- exit 1
-}
-
-# If we are cherry-pick, and if the merge did not result in
-# hand-editing, we will hit this commit and inherit the original
-# author date and name.
-# If we are revert, or if our cherry-pick results in a hand merge,
-# we had better say that the current user is responsible for that.
-
-case "$no_commit" in
-'')
- git-commit -n -F .msg $edit
- rm -f .msg
- ;;
-esac
diff --git a/contrib/examples/git-svnimport.perl b/contrib/examples/git-svnimport.perl
deleted file mode 100755
index b09ff8f12f..0000000000
--- a/contrib/examples/git-svnimport.perl
+++ /dev/null
@@ -1,976 +0,0 @@
-#!/usr/bin/perl
-
-# This tool is copyright (c) 2005, Matthias Urlichs.
-# It is released under the Gnu Public License, version 2.
-#
-# The basic idea is to pull and analyze SVN changes.
-#
-# Checking out the files is done by a single long-running SVN connection.
-#
-# The head revision is on branch "origin" by default.
-# You can change that with the '-o' option.
-
-use strict;
-use warnings;
-use Getopt::Std;
-use File::Copy;
-use File::Spec;
-use File::Temp qw(tempfile);
-use File::Path qw(mkpath);
-use File::Basename qw(basename dirname);
-use Time::Local;
-use IO::Pipe;
-use POSIX qw(strftime dup2);
-use IPC::Open2;
-use SVN::Core;
-use SVN::Ra;
-
-die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1";
-
-$SIG{'PIPE'}="IGNORE";
-$ENV{'TZ'}="UTC";
-
-our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
- $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F,
- $opt_P,$opt_R);
-
-sub usage() {
- print STDERR <<END;
-Usage: ${\basename $0} # fetch/update GIT from SVN
- [-o branch-for-HEAD] [-h] [-v] [-l max_rev] [-R repack_each_revs]
- [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
- [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
- [-m] [-M regex] [-A author_file] [-S] [-F] [-P project_name] [SVN_URL]
-END
- exit(1);
-}
-
-getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:SP:R:uv") or usage();
-usage if $opt_h;
-
-my $tag_name = $opt_t || "tags";
-my $trunk_name = defined $opt_T ? $opt_T : "trunk";
-my $branch_name = $opt_b || "branches";
-my $project_name = $opt_P || "";
-$project_name = "/" . $project_name if ($project_name);
-my $repack_after = $opt_R || 1000;
-my $root_pool = SVN::Pool->new_default;
-
-@ARGV == 1 or @ARGV == 2 or usage();
-
-$opt_o ||= "origin";
-$opt_s ||= 1;
-my $git_tree = $opt_C;
-$git_tree ||= ".";
-
-my $svn_url = $ARGV[0];
-my $svn_dir = $ARGV[1];
-
-our @mergerx = ();
-if ($opt_m) {
- my $branch_esc = quotemeta ($branch_name);
- my $trunk_esc = quotemeta ($trunk_name);
- @mergerx =
- (
- qr!\b(?:merg(?:ed?|ing))\b.*?\b((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
- qr!\b(?:from|of)\W+((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
- qr!\b(?:from|of)\W+(?:the )?([\w\.\-]+)[-\s]branch\b!i
- );
-}
-if ($opt_M) {
- unshift (@mergerx, qr/$opt_M/);
-}
-
-# Absolutize filename now, since we will have chdir'ed by the time we
-# get around to opening it.
-$opt_A = File::Spec->rel2abs($opt_A) if $opt_A;
-
-our %users = ();
-our $users_file = undef;
-sub read_users($) {
- $users_file = File::Spec->rel2abs(@_);
- die "Cannot open $users_file\n" unless -f $users_file;
- open(my $authors,$users_file);
- while(<$authors>) {
- chomp;
- next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
- (my $user,my $name,my $email) = ($1,$2,$3);
- $users{$user} = [$name,$email];
- }
- close($authors);
-}
-
-select(STDERR); $|=1; select(STDOUT);
-
-
-package SVNconn;
-# Basic SVN connection.
-# We're only interested in connecting and downloading, so ...
-
-use File::Spec;
-use File::Temp qw(tempfile);
-use POSIX qw(strftime dup2);
-use Fcntl qw(SEEK_SET);
-
-sub new {
- my($what,$repo) = @_;
- $what=ref($what) if ref($what);
-
- my $self = {};
- $self->{'buffer'} = "";
- bless($self,$what);
-
- $repo =~ s#/+$##;
- $self->{'fullrep'} = $repo;
- $self->conn();
-
- return $self;
-}
-
-sub conn {
- my $self = shift;
- my $repo = $self->{'fullrep'};
- my $auth = SVN::Core::auth_open ([SVN::Client::get_simple_provider,
- SVN::Client::get_ssl_server_trust_file_provider,
- SVN::Client::get_username_provider]);
- my $s = SVN::Ra->new(url => $repo, auth => $auth, pool => $root_pool);
- die "SVN connection to $repo: $!\n" unless defined $s;
- $self->{'svn'} = $s;
- $self->{'repo'} = $repo;
- $self->{'maxrev'} = $s->get_latest_revnum();
-}
-
-sub file {
- my($self,$path,$rev) = @_;
-
- my ($fh, $name) = tempfile('gitsvn.XXXXXX',
- DIR => File::Spec->tmpdir(), UNLINK => 1);
-
- print "... $rev $path ...\n" if $opt_v;
- my (undef, $properties);
- $path =~ s#^/*##;
- my $subpool = SVN::Pool::new_default_sub;
- eval { (undef, $properties)
- = $self->{'svn'}->get_file($path,$rev,$fh); };
- if($@) {
- return undef if $@ =~ /Attempted to get checksum/;
- die $@;
- }
- my $mode;
- if (exists $properties->{'svn:executable'}) {
- $mode = '100755';
- } elsif (exists $properties->{'svn:special'}) {
- my ($special_content, $filesize);
- $filesize = tell $fh;
- seek $fh, 0, SEEK_SET;
- read $fh, $special_content, $filesize;
- if ($special_content =~ s/^link //) {
- $mode = '120000';
- seek $fh, 0, SEEK_SET;
- truncate $fh, 0;
- print $fh $special_content;
- } else {
- die "unexpected svn:special file encountered";
- }
- } else {
- $mode = '100644';
- }
- close ($fh);
-
- return ($name, $mode);
-}
-
-sub ignore {
- my($self,$path,$rev) = @_;
-
- print "... $rev $path ...\n" if $opt_v;
- $path =~ s#^/*##;
- my $subpool = SVN::Pool::new_default_sub;
- my (undef,undef,$properties)
- = $self->{'svn'}->get_dir($path,$rev,undef);
- if (exists $properties->{'svn:ignore'}) {
- my ($fh, $name) = tempfile('gitsvn.XXXXXX',
- DIR => File::Spec->tmpdir(),
- UNLINK => 1);
- print $fh $properties->{'svn:ignore'};
- close($fh);
- return $name;
- } else {
- return undef;
- }
-}
-
-sub dir_list {
- my($self,$path,$rev) = @_;
- $path =~ s#^/*##;
- my $subpool = SVN::Pool::new_default_sub;
- my ($dirents,undef,$properties)
- = $self->{'svn'}->get_dir($path,$rev,undef);
- return $dirents;
-}
-
-package main;
-use URI;
-
-our $svn = $svn_url;
-$svn .= "/$svn_dir" if defined $svn_dir;
-my $svn2 = SVNconn->new($svn);
-$svn = SVNconn->new($svn);
-
-my $lwp_ua;
-if($opt_d or $opt_D) {
- $svn_url = URI->new($svn_url)->canonical;
- if($opt_D) {
- $svn_dir =~ s#/*$#/#;
- } else {
- $svn_dir = "";
- }
- if ($svn_url->scheme eq "http") {
- use LWP::UserAgent;
- $lwp_ua = LWP::UserAgent->new(keep_alive => 1, requests_redirectable => []);
- } else {
- print STDERR "Warning: not HTTP; turning off direct file access\n";
- $opt_d=0;
- }
-}
-
-sub pdate($) {
- my($d) = @_;
- $d =~ m#(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)#
- or die "Unparseable date: $d\n";
- my $y=$1; $y-=1900 if $y>1900;
- return timegm($6||0,$5,$4,$3,$2-1,$y);
-}
-
-sub getwd() {
- my $pwd = `pwd`;
- chomp $pwd;
- return $pwd;
-}
-
-
-sub get_headref($$) {
- my $name = shift;
- my $git_dir = shift;
- my $sha;
-
- if (open(C,"$git_dir/refs/heads/$name")) {
- chomp($sha = <C>);
- close(C);
- length($sha) == 40
- or die "Cannot get head id for $name ($sha): $!\n";
- }
- return $sha;
-}
-
-
--d $git_tree
- or mkdir($git_tree,0777)
- or die "Could not create $git_tree: $!";
-chdir($git_tree);
-
-my $orig_branch = "";
-my $forward_master = 0;
-my %branches;
-
-my $git_dir = $ENV{"GIT_DIR"} || ".git";
-$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
-$ENV{"GIT_DIR"} = $git_dir;
-my $orig_git_index;
-$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
-my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx',
- DIR => File::Spec->tmpdir());
-close ($git_ih);
-$ENV{GIT_INDEX_FILE} = $git_index;
-my $maxnum = 0;
-my $last_rev = "";
-my $last_branch;
-my $current_rev = $opt_s || 1;
-unless(-d $git_dir) {
- system("git init");
- die "Cannot init the GIT db at $git_tree: $?\n" if $?;
- system("git read-tree --empty");
- die "Cannot init an empty tree: $?\n" if $?;
-
- $last_branch = $opt_o;
- $orig_branch = "";
-} else {
- -f "$git_dir/refs/heads/$opt_o"
- or die "Branch '$opt_o' does not exist.\n".
- "Either use the correct '-o branch' option,\n".
- "or import to a new repository.\n";
-
- -f "$git_dir/svn2git"
- or die "'$git_dir/svn2git' does not exist.\n".
- "You need that file for incremental imports.\n";
- open(F, "git symbolic-ref HEAD |") or
- die "Cannot run git-symbolic-ref: $!\n";
- chomp ($last_branch = <F>);
- $last_branch = basename($last_branch);
- close(F);
- unless($last_branch) {
- warn "Cannot read the last branch name: $! -- assuming 'master'\n";
- $last_branch = "master";
- }
- $orig_branch = $last_branch;
- $last_rev = get_headref($orig_branch, $git_dir);
- if (-f "$git_dir/SVN2GIT_HEAD") {
- die <<EOM;
-SVN2GIT_HEAD exists.
-Make sure your working directory corresponds to HEAD and remove SVN2GIT_HEAD.
-You may need to run
-
- git-read-tree -m -u SVN2GIT_HEAD HEAD
-EOM
- }
- system('cp', "$git_dir/HEAD", "$git_dir/SVN2GIT_HEAD");
-
- $forward_master =
- $opt_o ne 'master' && -f "$git_dir/refs/heads/master" &&
- system('cmp', '-s', "$git_dir/refs/heads/master",
- "$git_dir/refs/heads/$opt_o") == 0;
-
- # populate index
- system('git', 'read-tree', $last_rev);
- die "read-tree failed: $?\n" if $?;
-
- # Get the last import timestamps
- open my $B,"<", "$git_dir/svn2git";
- while(<$B>) {
- chomp;
- my($num,$branch,$ref) = split;
- $branches{$branch}{$num} = $ref;
- $branches{$branch}{"LAST"} = $ref;
- $current_rev = $num+1 if $current_rev <= $num;
- }
- close($B);
-}
--d $git_dir
- or die "Could not create git subdir ($git_dir).\n";
-
-my $default_authors = "$git_dir/svn-authors";
-if ($opt_A) {
- read_users($opt_A);
- copy($opt_A,$default_authors) or die "Copy failed: $!";
-} else {
- read_users($default_authors) if -f $default_authors;
-}
-
-open BRANCHES,">>", "$git_dir/svn2git";
-
-sub node_kind($$) {
- my ($svnpath, $revision) = @_;
- $svnpath =~ s#^/*##;
- my $subpool = SVN::Pool::new_default_sub;
- my $kind = $svn->{'svn'}->check_path($svnpath,$revision);
- return $kind;
-}
-
-sub get_file($$$) {
- my($svnpath,$rev,$path) = @_;
-
- # now get it
- my ($name,$mode);
- if($opt_d) {
- my($req,$res);
-
- # /svn/!svn/bc/2/django/trunk/django-docs/build.py
- my $url=$svn_url->clone();
- $url->path($url->path."/!svn/bc/$rev/$svn_dir$svnpath");
- print "... $path...\n" if $opt_v;
- $req = HTTP::Request->new(GET => $url);
- $res = $lwp_ua->request($req);
- if ($res->is_success) {
- my $fh;
- ($fh, $name) = tempfile('gitsvn.XXXXXX',
- DIR => File::Spec->tmpdir(), UNLINK => 1);
- print $fh $res->content;
- close($fh) or die "Could not write $name: $!\n";
- } else {
- return undef if $res->code == 301; # directory?
- die $res->status_line." at $url\n";
- }
- $mode = '0644'; # can't obtain mode via direct http request?
- } else {
- ($name,$mode) = $svn->file("$svnpath",$rev);
- return undef unless defined $name;
- }
-
- my $pid = open(my $F, '-|');
- die $! unless defined $pid;
- if (!$pid) {
- exec("git", "hash-object", "-w", $name)
- or die "Cannot create object: $!\n";
- }
- my $sha = <$F>;
- chomp $sha;
- close $F;
- unlink $name;
- return [$mode, $sha, $path];
-}
-
-sub get_ignore($$$$$) {
- my($new,$old,$rev,$path,$svnpath) = @_;
-
- return unless $opt_I;
- my $name = $svn->ignore("$svnpath",$rev);
- if ($path eq '/') {
- $path = $opt_I;
- } else {
- $path = File::Spec->catfile($path,$opt_I);
- }
- if (defined $name) {
- my $pid = open(my $F, '-|');
- die $! unless defined $pid;
- if (!$pid) {
- exec("git", "hash-object", "-w", $name)
- or die "Cannot create object: $!\n";
- }
- my $sha = <$F>;
- chomp $sha;
- close $F;
- unlink $name;
- push(@$new,['0644',$sha,$path]);
- } elsif (defined $old) {
- push(@$old,$path);
- }
-}
-
-sub project_path($$)
-{
- my ($path, $project) = @_;
-
- $path = "/".$path unless ($path =~ m#^\/#) ;
- return $1 if ($path =~ m#^$project\/(.*)$#);
-
- $path =~ s#\.#\\\.#g;
- $path =~ s#\+#\\\+#g;
- return "/" if ($project =~ m#^$path.*$#);
-
- return undef;
-}
-
-sub split_path($$) {
- my($rev,$path) = @_;
- my $branch;
-
- if($path =~ s#^/\Q$tag_name\E/([^/]+)/?##) {
- $branch = "/$1";
- } elsif($path =~ s#^/\Q$trunk_name\E/?##) {
- $branch = "/";
- } elsif($path =~ s#^/\Q$branch_name\E/([^/]+)/?##) {
- $branch = $1;
- } else {
- my %no_error = (
- "/" => 1,
- "/$tag_name" => 1,
- "/$branch_name" => 1
- );
- print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path});
- return ()
- }
- if ($path eq "") {
- $path = "/";
- } elsif ($project_name) {
- $path = project_path($path, $project_name);
- }
- return ($branch,$path);
-}
-
-sub branch_rev($$) {
-
- my ($srcbranch,$uptorev) = @_;
-
- my $bbranches = $branches{$srcbranch};
- my @revs = reverse sort { ($a eq 'LAST' ? 0 : $a) <=> ($b eq 'LAST' ? 0 : $b) } keys %$bbranches;
- my $therev;
- foreach my $arev(@revs) {
- next if ($arev eq 'LAST');
- if ($arev <= $uptorev) {
- $therev = $arev;
- last;
- }
- }
- return $therev;
-}
-
-sub expand_svndir($$$);
-
-sub expand_svndir($$$)
-{
- my ($svnpath, $rev, $path) = @_;
- my @list;
- get_ignore(\@list, undef, $rev, $path, $svnpath);
- my $dirents = $svn->dir_list($svnpath, $rev);
- foreach my $p(keys %$dirents) {
- my $kind = node_kind($svnpath.'/'.$p, $rev);
- if ($kind eq $SVN::Node::file) {
- my $f = get_file($svnpath.'/'.$p, $rev, $path.'/'.$p);
- push(@list, $f) if $f;
- } elsif ($kind eq $SVN::Node::dir) {
- push(@list,
- expand_svndir($svnpath.'/'.$p, $rev, $path.'/'.$p));
- }
- }
- return @list;
-}
-
-sub copy_path($$$$$$$$) {
- # Somebody copied a whole subdirectory.
- # We need to find the index entries from the old version which the
- # SVN log entry points to, and add them to the new place.
-
- my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_;
-
- my($srcbranch,$srcpath) = split_path($rev,$oldpath);
- unless(defined $srcbranch && defined $srcpath) {
- print "Path not found when copying from $oldpath @ $rev.\n".
- "Will try to copy from original SVN location...\n"
- if $opt_v;
- push (@$new, expand_svndir($oldpath, $rev, $path));
- return;
- }
- my $therev = branch_rev($srcbranch, $rev);
- my $gitrev = $branches{$srcbranch}{$therev};
- unless($gitrev) {
- print STDERR "$newrev:$newbranch: could not find $oldpath \@ $rev\n";
- return;
- }
- if ($srcbranch ne $newbranch) {
- push(@$parents, $branches{$srcbranch}{'LAST'});
- }
- print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v;
- if ($node_kind eq $SVN::Node::dir) {
- $srcpath =~ s#/*$#/#;
- }
-
- my $pid = open my $f,'-|';
- die $! unless defined $pid;
- if (!$pid) {
- exec("git","ls-tree","-r","-z",$gitrev,$srcpath)
- or die $!;
- }
- local $/ = "\0";
- while(<$f>) {
- chomp;
- my($m,$p) = split(/\t/,$_,2);
- my($mode,$type,$sha1) = split(/ /,$m);
- next if $type ne "blob";
- if ($node_kind eq $SVN::Node::dir) {
- $p = $path . substr($p,length($srcpath)-1);
- } else {
- $p = $path;
- }
- push(@$new,[$mode,$sha1,$p]);
- }
- close($f) or
- print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n";
-}
-
-sub commit {
- my($branch, $changed_paths, $revision, $author, $date, $message) = @_;
- my($committer_name,$committer_email,$dest);
- my($author_name,$author_email);
- my(@old,@new,@parents);
-
- if (not defined $author or $author eq "") {
- $committer_name = $committer_email = "unknown";
- } elsif (defined $users_file) {
- die "User $author is not listed in $users_file\n"
- unless exists $users{$author};
- ($committer_name,$committer_email) = @{$users{$author}};
- } elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
- ($committer_name, $committer_email) = ($1, $2);
- } else {
- $author =~ s/^<(.*)>$/$1/;
- $committer_name = $committer_email = $author;
- }
-
- if ($opt_F && $message =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) {
- ($author_name, $author_email) = ($1, $2);
- print "Author from From: $1 <$2>\n" if ($opt_v);;
- } elsif ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) {
- ($author_name, $author_email) = ($1, $2);
- print "Author from Signed-off-by: $1 <$2>\n" if ($opt_v);;
- } else {
- $author_name = $committer_name;
- $author_email = $committer_email;
- }
-
- $date = pdate($date);
-
- my $tag;
- my $parent;
- if($branch eq "/") { # trunk
- $parent = $opt_o;
- } elsif($branch =~ m#^/(.+)#) { # tag
- $tag = 1;
- $parent = $1;
- } else { # "normal" branch
- # nothing to do
- $parent = $branch;
- }
- $dest = $parent;
-
- my $prev = $changed_paths->{"/"};
- if($prev and $prev->[0] eq "A") {
- delete $changed_paths->{"/"};
- my $oldpath = $prev->[1];
- my $rev;
- if(defined $oldpath) {
- my $p;
- ($parent,$p) = split_path($revision,$oldpath);
- if(defined $parent) {
- if($parent eq "/") {
- $parent = $opt_o;
- } else {
- $parent =~ s#^/##; # if it's a tag
- }
- }
- } else {
- $parent = undef;
- }
- }
-
- my $rev;
- if($revision > $opt_s and defined $parent) {
- open(H,'-|',"git","rev-parse","--verify",$parent);
- $rev = <H>;
- close(H) or do {
- print STDERR "$revision: cannot find commit '$parent'!\n";
- return;
- };
- chop $rev;
- if(length($rev) != 40) {
- print STDERR "$revision: cannot find commit '$parent'!\n";
- return;
- }
- $rev = $branches{($parent eq $opt_o) ? "/" : $parent}{"LAST"};
- if($revision != $opt_s and not $rev) {
- print STDERR "$revision: do not know ancestor for '$parent'!\n";
- return;
- }
- } else {
- $rev = undef;
- }
-
-# if($prev and $prev->[0] eq "A") {
-# if(not $tag) {
-# unless(open(H,"> $git_dir/refs/heads/$branch")) {
-# print STDERR "$revision: Could not create branch $branch: $!\n";
-# $state=11;
-# next;
-# }
-# print H "$rev\n"
-# or die "Could not write branch $branch: $!";
-# close(H)
-# or die "Could not write branch $branch: $!";
-# }
-# }
- if(not defined $rev) {
- unlink($git_index);
- } elsif ($rev ne $last_rev) {
- print "Switching from $last_rev to $rev ($branch)\n" if $opt_v;
- system("git", "read-tree", $rev);
- die "read-tree failed for $rev: $?\n" if $?;
- $last_rev = $rev;
- }
-
- push (@parents, $rev) if defined $rev;
-
- my $cid;
- if($tag and not %$changed_paths) {
- $cid = $rev;
- } else {
- my @paths = sort keys %$changed_paths;
- foreach my $path(@paths) {
- my $action = $changed_paths->{$path};
-
- if ($action->[0] eq "R") {
- # refer to a file/tree in an earlier commit
- push(@old,$path); # remove any old stuff
- }
- if(($action->[0] eq "A") || ($action->[0] eq "R")) {
- my $node_kind = node_kind($action->[3], $revision);
- if ($node_kind eq $SVN::Node::file) {
- my $f = get_file($action->[3],
- $revision, $path);
- if ($f) {
- push(@new,$f) if $f;
- } else {
- my $opath = $action->[3];
- print STDERR "$revision: $branch: could not fetch '$opath'\n";
- }
- } elsif ($node_kind eq $SVN::Node::dir) {
- if($action->[1]) {
- copy_path($revision, $branch,
- $path, $action->[1],
- $action->[2], $node_kind,
- \@new, \@parents);
- } else {
- get_ignore(\@new, \@old, $revision,
- $path, $action->[3]);
- }
- }
- } elsif ($action->[0] eq "D") {
- push(@old,$path);
- } elsif ($action->[0] eq "M") {
- my $node_kind = node_kind($action->[3], $revision);
- if ($node_kind eq $SVN::Node::file) {
- my $f = get_file($action->[3],
- $revision, $path);
- push(@new,$f) if $f;
- } elsif ($node_kind eq $SVN::Node::dir) {
- get_ignore(\@new, \@old, $revision,
- $path, $action->[3]);
- }
- } else {
- die "$revision: unknown action '".$action->[0]."' for $path\n";
- }
- }
-
- while(@old) {
- my @o1;
- if(@old > 55) {
- @o1 = splice(@old,0,50);
- } else {
- @o1 = @old;
- @old = ();
- }
- my $pid = open my $F, "-|";
- die "$!" unless defined $pid;
- if (!$pid) {
- exec("git", "ls-files", "-z", @o1) or die $!;
- }
- @o1 = ();
- local $/ = "\0";
- while(<$F>) {
- chomp;
- push(@o1,$_);
- }
- close($F);
-
- while(@o1) {
- my @o2;
- if(@o1 > 55) {
- @o2 = splice(@o1,0,50);
- } else {
- @o2 = @o1;
- @o1 = ();
- }
- system("git","update-index","--force-remove","--",@o2);
- die "Cannot remove files: $?\n" if $?;
- }
- }
- while(@new) {
- my @n2;
- if(@new > 12) {
- @n2 = splice(@new,0,10);
- } else {
- @n2 = @new;
- @new = ();
- }
- system("git","update-index","--add",
- (map { ('--cacheinfo', @$_) } @n2));
- die "Cannot add files: $?\n" if $?;
- }
-
- my $pid = open(C,"-|");
- die "Cannot fork: $!" unless defined $pid;
- unless($pid) {
- exec("git","write-tree");
- die "Cannot exec git-write-tree: $!\n";
- }
- chomp(my $tree = <C>);
- length($tree) == 40
- or die "Cannot get tree id ($tree): $!\n";
- close(C)
- or die "Error running git-write-tree: $?\n";
- print "Tree ID $tree\n" if $opt_v;
-
- my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n";
- my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n";
- $pid = fork();
- die "Fork: $!\n" unless defined $pid;
- unless($pid) {
- $pr->writer();
- $pw->reader();
- open(OUT,">&STDOUT");
- dup2($pw->fileno(),0);
- dup2($pr->fileno(),1);
- $pr->close();
- $pw->close();
-
- my @par = ();
-
- # loose detection of merges
- # based on the commit msg
- foreach my $rx (@mergerx) {
- if ($message =~ $rx) {
- my $mparent = $1;
- if ($mparent eq 'HEAD') { $mparent = $opt_o };
- if ( -e "$git_dir/refs/heads/$mparent") {
- $mparent = get_headref($mparent, $git_dir);
- push (@parents, $mparent);
- print OUT "Merge parent branch: $mparent\n" if $opt_v;
- }
- }
- }
- my %seen_parents = ();
- my @unique_parents = grep { ! $seen_parents{$_} ++ } @parents;
- foreach my $bparent (@unique_parents) {
- push @par, '-p', $bparent;
- print OUT "Merge parent branch: $bparent\n" if $opt_v;
- }
-
- exec("env",
- "GIT_AUTHOR_NAME=$author_name",
- "GIT_AUTHOR_EMAIL=$author_email",
- "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
- "GIT_COMMITTER_NAME=$committer_name",
- "GIT_COMMITTER_EMAIL=$committer_email",
- "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
- "git", "commit-tree", $tree,@par);
- die "Cannot exec git-commit-tree: $!\n";
- }
- $pw->writer();
- $pr->reader();
-
- $message =~ s/[\s\n]+\z//;
- $message = "r$revision: $message" if $opt_r;
-
- print $pw "$message\n"
- or die "Error writing to git-commit-tree: $!\n";
- $pw->close();
-
- print "Committed change $revision:$branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v;
- chomp($cid = <$pr>);
- length($cid) == 40
- or die "Cannot get commit id ($cid): $!\n";
- print "Commit ID $cid\n" if $opt_v;
- $pr->close();
-
- waitpid($pid,0);
- die "Error running git-commit-tree: $?\n" if $?;
- }
-
- if (not defined $cid) {
- $cid = $branches{"/"}{"LAST"};
- }
-
- if(not defined $dest) {
- print "... no known parent\n" if $opt_v;
- } elsif(not $tag) {
- print "Writing to refs/heads/$dest\n" if $opt_v;
- open(C,">$git_dir/refs/heads/$dest") and
- print C ("$cid\n") and
- close(C)
- or die "Cannot write branch $dest for update: $!\n";
- }
-
- if ($tag) {
- $last_rev = "-" if %$changed_paths;
- # the tag was 'complex', i.e. did not refer to a "real" revision
-
- $dest =~ tr/_/\./ if $opt_u;
-
- system('git', 'tag', '-f', $dest, $cid) == 0
- or die "Cannot create tag $dest: $!\n";
-
- print "Created tag '$dest' on '$branch'\n" if $opt_v;
- }
- $branches{$branch}{"LAST"} = $cid;
- $branches{$branch}{$revision} = $cid;
- $last_rev = $cid;
- print BRANCHES "$revision $branch $cid\n";
- print "DONE: $revision $dest $cid\n" if $opt_v;
-}
-
-sub commit_all {
- # Recursive use of the SVN connection does not work
- local $svn = $svn2;
-
- my ($changed_paths, $revision, $author, $date, $message) = @_;
- my %p;
- while(my($path,$action) = each %$changed_paths) {
- $p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ];
- }
- $changed_paths = \%p;
-
- my %done;
- my @col;
- my $pref;
- my $branch;
-
- while(my($path,$action) = each %$changed_paths) {
- ($branch,$path) = split_path($revision,$path);
- next if not defined $branch;
- next if not defined $path;
- $done{$branch}{$path} = $action;
- }
- while(($branch,$changed_paths) = each %done) {
- commit($branch, $changed_paths, $revision, $author, $date, $message);
- }
-}
-
-$opt_l = $svn->{'maxrev'} if not defined $opt_l or $opt_l > $svn->{'maxrev'};
-
-if ($opt_l < $current_rev) {
- print "Up to date: no new revisions to fetch!\n" if $opt_v;
- unlink("$git_dir/SVN2GIT_HEAD");
- exit;
-}
-
-print "Processing from $current_rev to $opt_l ...\n" if $opt_v;
-
-my $from_rev;
-my $to_rev = $current_rev - 1;
-
-my $subpool = SVN::Pool::new_default_sub;
-while ($to_rev < $opt_l) {
- $subpool->clear;
- $from_rev = $to_rev + 1;
- $to_rev = $from_rev + $repack_after;
- $to_rev = $opt_l if $opt_l < $to_rev;
- print "Fetching from $from_rev to $to_rev ...\n" if $opt_v;
- $svn->{'svn'}->get_log("",$from_rev,$to_rev,0,1,1,\&commit_all);
- my $pid = fork();
- die "Fork: $!\n" unless defined $pid;
- unless($pid) {
- exec("git", "repack", "-d")
- or die "Cannot repack: $!\n";
- }
- waitpid($pid, 0);
-}
-
-
-unlink($git_index);
-
-if (defined $orig_git_index) {
- $ENV{GIT_INDEX_FILE} = $orig_git_index;
-} else {
- delete $ENV{GIT_INDEX_FILE};
-}
-
-# Now switch back to the branch we were in before all of this happened
-if($orig_branch) {
- print "DONE\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
- system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
- if $forward_master;
- unless ($opt_i) {
- system('git', 'read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
- die "read-tree failed: $?\n" if $?;
- }
-} else {
- $orig_branch = "master";
- print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
- system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
- unless -f "$git_dir/refs/heads/master";
- system('git', 'update-ref', 'HEAD', "$orig_branch");
- unless ($opt_i) {
- system('git checkout');
- die "checkout failed: $?\n" if $?;
- }
-}
-unlink("$git_dir/SVN2GIT_HEAD");
-close(BRANCHES);
diff --git a/contrib/examples/git-svnimport.txt b/contrib/examples/git-svnimport.txt
deleted file mode 100644
index 3bb871e42f..0000000000
--- a/contrib/examples/git-svnimport.txt
+++ /dev/null
@@ -1,179 +0,0 @@
-git-svnimport(1)
-================
-v0.1, July 2005
-
-NAME
-----
-git-svnimport - Import a SVN repository into git
-
-
-SYNOPSIS
---------
-[verse]
-'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ]
- [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev]
- [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
- [ -s start_chg ] [ -m ] [ -r ] [ -M regex ]
- [ -I <ignorefile_name> ] [ -A <author_file> ]
- [ -R <repack_each_revs>] [ -P <path_from_trunk> ]
- <SVN_repository_URL> [ <path> ]
-
-
-DESCRIPTION
------------
-Imports a SVN repository into git. It will either create a new
-repository, or incrementally import into an existing one.
-
-SVN access is done by the SVN::Perl module.
-
-git-svnimport assumes that SVN repositories are organized into one
-"trunk" directory where the main development happens, "branches/FOO"
-directories for branches, and "/tags/FOO" directories for tags.
-Other subdirectories are ignored.
-
-git-svnimport creates a file ".git/svn2git", which is required for
-incremental SVN imports.
-
-OPTIONS
--------
--C <target-dir>::
- The GIT repository to import to. If the directory doesn't
- exist, it will be created. Default is the current directory.
-
--s <start_rev>::
- Start importing at this SVN change number. The default is 1.
-+
-When importing incrementally, you might need to edit the .git/svn2git file.
-
--i::
- Import-only: don't perform a checkout after importing. This option
- ensures the working directory and index remain untouched and will
- not create them if they do not exist.
-
--T <trunk_subdir>::
- Name the SVN trunk. Default "trunk".
-
--t <tag_subdir>::
- Name the SVN subdirectory for tags. Default "tags".
-
--b <branch_subdir>::
- Name the SVN subdirectory for branches. Default "branches".
-
--o <branch-for-HEAD>::
- The 'trunk' branch from SVN is imported to the 'origin' branch within
- the git repository. Use this option if you want to import into a
- different branch.
-
--r::
- Prepend 'rX: ' to commit messages, where X is the imported
- subversion revision.
-
--u::
- Replace underscores in tag names with periods.
-
--I <ignorefile_name>::
- Import the svn:ignore directory property to files with this
- name in each directory. (The Subversion and GIT ignore
- syntaxes are similar enough that using the Subversion patterns
- directly with "-I .gitignore" will almost always just work.)
-
--A <author_file>::
- Read a file with lines on the form
-+
-------
- username = User's Full Name <email@addr.es>
-
-------
-+
-and use "User's Full Name <email@addr.es>" as the GIT
-author and committer for Subversion commits made by
-"username". If encountering a commit made by a user not in the
-list, abort.
-+
-For convenience, this data is saved to $GIT_DIR/svn-authors
-each time the -A option is provided, and read from that same
-file each time git-svnimport is run with an existing GIT
-repository without -A.
-
--m::
- Attempt to detect merges based on the commit message. This option
- will enable default regexes that try to capture the name source
- branch name from the commit message.
-
--M <regex>::
- Attempt to detect merges based on the commit message with a custom
- regex. It can be used with -m to also see the default regexes.
- You must escape forward slashes.
-
--l <max_rev>::
- Specify a maximum revision number to pull.
-+
-Formerly, this option controlled how many revisions to pull,
-due to SVN memory leaks. (These have been worked around.)
-
--R <repack_each_revs>::
- Specify how often git repository should be repacked.
-+
-The default value is 1000. git-svnimport will do imports in chunks of 1000
-revisions, after each chunk the git repository will be repacked. To disable
-this behavior specify some large value here which is greater than the number of
-revisions to import.
-
--P <path_from_trunk>::
- Partial import of the SVN tree.
-+
-By default, the whole tree on the SVN trunk (/trunk) is imported.
-'-P my/proj' will import starting only from '/trunk/my/proj'.
-This option is useful when you want to import one project from a
-svn repo which hosts multiple projects under the same trunk.
-
--v::
- Verbosity: let 'svnimport' report what it is doing.
-
--d::
- Use direct HTTP requests if possible. The "<path>" argument is used
- only for retrieving the SVN logs; the path to the contents is
- included in the SVN log.
-
--D::
- Use direct HTTP requests if possible. The "<path>" argument is used
- for retrieving the logs, as well as for the contents.
-+
-There's no safe way to automatically find out which of these options to
-use, so you need to try both. Usually, the one that's wrong will die
-with a 40x error pretty quickly.
-
-<SVN_repository_URL>::
- The URL of the SVN module you want to import. For local
- repositories, use "file:///absolute/path".
-+
-If you're using the "-d" or "-D" option, this is the URL of the SVN
-repository itself; it usually ends in "/svn".
-
-<path>::
- The path to the module you want to check out.
-
--h::
- Print a short usage message and exit.
-
-OUTPUT
-------
-If '-v' is specified, the script reports what it is doing.
-
-Otherwise, success is indicated the Unix way, i.e. by simply exiting with
-a zero exit status.
-
-Author
-------
-Written by Matthias Urlichs <smurf@smurf.noris.de>, with help from
-various participants of the git-list <git@vger.kernel.org>.
-
-Based on a cvs2git script by the same author.
-
-Documentation
---------------
-Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
-
-GIT
----
-Part of the gitlink:git[7] suite
diff --git a/contrib/examples/git-tag.sh b/contrib/examples/git-tag.sh
deleted file mode 100755
index 2c15bc955b..0000000000
--- a/contrib/examples/git-tag.sh
+++ /dev/null
@@ -1,205 +0,0 @@
-#!/bin/sh
-# Copyright (c) 2005 Linus Torvalds
-
-USAGE='[-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg>] <tagname> [<head>]'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-message_given=
-annotate=
-signed=
-force=
-message=
-username=
-list=
-verify=
-LINES=0
-while test $# != 0
-do
- case "$1" in
- -a)
- annotate=1
- shift
- ;;
- -s)
- annotate=1
- signed=1
- shift
- ;;
- -f)
- force=1
- shift
- ;;
- -n)
- case "$#,$2" in
- 1,* | *,-*)
- LINES=1 # no argument
- ;;
- *) shift
- LINES=$(expr "$1" : '\([0-9]*\)')
- [ -z "$LINES" ] && LINES=1 # 1 line is default when -n is used
- ;;
- esac
- shift
- ;;
- -l)
- list=1
- shift
- case $# in
- 0) PATTERN=
- ;;
- *)
- PATTERN="$1" # select tags by shell pattern, not re
- shift
- ;;
- esac
- git rev-parse --symbolic --tags | sort |
- while read TAG
- do
- case "$TAG" in
- *$PATTERN*) ;;
- *) continue ;;
- esac
- [ "$LINES" -le 0 ] && { echo "$TAG"; continue ;}
- OBJTYPE=$(git cat-file -t "$TAG")
- case $OBJTYPE in
- tag)
- ANNOTATION=$(git cat-file tag "$TAG" |
- sed -e '1,/^$/d' |
- sed -n -e "
- /^-----BEGIN PGP SIGNATURE-----\$/q
- 2,\$s/^/ /
- p
- ${LINES}q
- ")
- printf "%-15s %s\n" "$TAG" "$ANNOTATION"
- ;;
- *) echo "$TAG"
- ;;
- esac
- done
- ;;
- -m)
- annotate=1
- shift
- message="$1"
- if test "$#" = "0"; then
- die "error: option -m needs an argument"
- else
- message="$1"
- message_given=1
- shift
- fi
- ;;
- -F)
- annotate=1
- shift
- if test "$#" = "0"; then
- die "error: option -F needs an argument"
- else
- message="$(cat "$1")"
- message_given=1
- shift
- fi
- ;;
- -u)
- annotate=1
- signed=1
- shift
- if test "$#" = "0"; then
- die "error: option -u needs an argument"
- else
- username="$1"
- shift
- fi
- ;;
- -d)
- shift
- had_error=0
- for tag
- do
- cur=$(git show-ref --verify --hash -- "refs/tags/$tag") || {
- echo >&2 "Seriously, what tag are you talking about?"
- had_error=1
- continue
- }
- git update-ref -m 'tag: delete' -d "refs/tags/$tag" "$cur" || {
- had_error=1
- continue
- }
- echo "Deleted tag $tag."
- done
- exit $had_error
- ;;
- -v)
- shift
- tag_name="$1"
- tag=$(git show-ref --verify --hash -- "refs/tags/$tag_name") ||
- die "Seriously, what tag are you talking about?"
- git-verify-tag -v "$tag"
- exit $?
- ;;
- -*)
- usage
- ;;
- *)
- break
- ;;
- esac
-done
-
-[ -n "$list" ] && exit 0
-
-name="$1"
-[ "$name" ] || usage
-prev=0000000000000000000000000000000000000000
-if git show-ref --verify --quiet -- "refs/tags/$name"
-then
- test -n "$force" || die "tag '$name' already exists"
- prev=`git rev-parse "refs/tags/$name"`
-fi
-shift
-git check-ref-format "tags/$name" ||
- die "we do not like '$name' as a tag name."
-
-object=$(git rev-parse --verify --default HEAD "$@") || exit 1
-type=$(git cat-file -t $object) || exit 1
-tagger=$(git var GIT_COMMITTER_IDENT) || exit 1
-
-test -n "$username" ||
- username=$(git config user.signingkey) ||
- username=$(expr "z$tagger" : 'z\(.*>\)')
-
-trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0
-
-if [ "$annotate" ]; then
- if [ -z "$message_given" ]; then
- ( echo "#"
- echo "# Write a tag message"
- echo "#" ) > "$GIT_DIR"/TAG_EDITMSG
- git_editor "$GIT_DIR"/TAG_EDITMSG || exit
- else
- printf '%s\n' "$message" >"$GIT_DIR"/TAG_EDITMSG
- fi
-
- grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG |
- git stripspace >"$GIT_DIR"/TAG_FINALMSG
-
- [ -s "$GIT_DIR"/TAG_FINALMSG -o -n "$message_given" ] || {
- echo >&2 "No tag message?"
- exit 1
- }
-
- ( printf 'object %s\ntype %s\ntag %s\ntagger %s\n\n' \
- "$object" "$type" "$name" "$tagger";
- cat "$GIT_DIR"/TAG_FINALMSG ) >"$GIT_DIR"/TAG_TMP
- rm -f "$GIT_DIR"/TAG_TMP.asc "$GIT_DIR"/TAG_FINALMSG
- if [ "$signed" ]; then
- gpg -bsa -u "$username" "$GIT_DIR"/TAG_TMP &&
- cat "$GIT_DIR"/TAG_TMP.asc >>"$GIT_DIR"/TAG_TMP ||
- die "failed to sign the tag with GPG."
- fi
- object=$(git-mktag < "$GIT_DIR"/TAG_TMP)
-fi
-
-git update-ref "refs/tags/$name" "$object" "$prev"
diff --git a/contrib/examples/git-verify-tag.sh b/contrib/examples/git-verify-tag.sh
deleted file mode 100755
index 0902a5c21a..0000000000
--- a/contrib/examples/git-verify-tag.sh
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/bin/sh
-
-USAGE='<tag>'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-verbose=
-while test $# != 0
-do
- case "$1" in
- -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
- verbose=t ;;
- *)
- break ;;
- esac
- shift
-done
-
-if [ "$#" != "1" ]
-then
- usage
-fi
-
-type="$(git cat-file -t "$1" 2>/dev/null)" ||
- die "$1: no such object."
-
-test "$type" = tag ||
- die "$1: cannot verify a non-tag object of type $type."
-
-case "$verbose" in
-t)
- git cat-file -p "$1" |
- sed -n -e '/^-----BEGIN PGP SIGNATURE-----/q' -e p
- ;;
-esac
-
-trap 'rm -f "$GIT_DIR/.tmp-vtag"' 0
-
-git cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1
-sed -n -e '
- /^-----BEGIN PGP SIGNATURE-----$/q
- p
-' <"$GIT_DIR/.tmp-vtag" |
-gpg --verify "$GIT_DIR/.tmp-vtag" - || exit 1
-rm -f "$GIT_DIR/.tmp-vtag"
diff --git a/contrib/fast-import/git-import.perl b/contrib/fast-import/git-import.perl
index f9fef6db28..0891b9e366 100755
--- a/contrib/fast-import/git-import.perl
+++ b/contrib/fast-import/git-import.perl
@@ -7,7 +7,7 @@
use strict;
use File::Find;
-my $USAGE = 'Usage: git-import branch import-message';
+my $USAGE = 'usage: git-import branch import-message';
my $branch = shift or die "$USAGE\n";
my $message = shift or die "$USAGE\n";
diff --git a/contrib/fast-import/git-import.sh b/contrib/fast-import/git-import.sh
index 0ca7718d05..f8d803c5e2 100755
--- a/contrib/fast-import/git-import.sh
+++ b/contrib/fast-import/git-import.sh
@@ -5,7 +5,7 @@
# but is meant to be a simple fast-import example.
if [ -z "$1" -o -z "$2" ]; then
- echo "Usage: git-import branch import-message"
+ echo "usage: git-import branch import-message"
exit 1
fi
diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl
index 7f3afa5ac4..a16f79cfdc 100755
--- a/contrib/fast-import/import-directories.perl
+++ b/contrib/fast-import/import-directories.perl
@@ -14,8 +14,7 @@
# 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.
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
#
# ------------------------------------------------------------------------
@@ -109,8 +108,8 @@ 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.
+a previous revision. There are no restrictions on where the source
+files are located, nor on their names.
[3.files]
; the key is the path inside the repository, the value is the path
diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl
index 95438e1ed4..d50ce26d5d 100755
--- a/contrib/fast-import/import-tars.perl
+++ b/contrib/fast-import/import-tars.perl
@@ -63,6 +63,8 @@ foreach my $tar_file (@ARGV)
my $have_top_dir = 1;
my ($top_dir, %files);
+ my $next_path = '';
+
while (read(I, $_, 512) == 512) {
my ($name, $mode, $uid, $gid, $size, $mtime,
$chksum, $typeflag, $linkname, $magic,
@@ -70,6 +72,13 @@ foreach my $tar_file (@ARGV)
$prefix) = unpack 'Z100 Z8 Z8 Z8 Z12 Z12
Z8 Z1 Z100 Z6
Z2 Z32 Z32 Z8 Z8 Z*', $_;
+
+ unless ($next_path eq '') {
+ # Recover name from previous extended header
+ $name = $next_path;
+ $next_path = '';
+ }
+
last unless length($name);
if ($name eq '././@LongLink') {
# GNU tar extension
@@ -90,24 +99,47 @@ foreach my $tar_file (@ARGV)
Z8 Z1 Z100 Z6
Z2 Z32 Z32 Z8 Z8 Z*', $_;
}
- next if $name =~ m{/\z};
$mode = oct $mode;
$size = oct $size;
$mtime = oct $mtime;
next if $typeflag == 5; # directory
- print FI "blob\n", "mark :$next_mark\n";
- if ($typeflag == 2) { # symbolic link
- print FI "data ", length($linkname), "\n", $linkname;
- $mode = 0120000;
- } else {
- print FI "data $size\n";
+ if ($typeflag eq 'x') { # extended header
+ # If extended header, check for path
+ my $pax_header = '';
while ($size > 0 && read(I, $_, 512) == 512) {
- print FI substr($_, 0, $size);
+ $pax_header = $pax_header . substr($_, 0, $size);
$size -= 512;
}
+
+ my @lines = split /\n/, $pax_header;
+ foreach my $line (@lines) {
+ my ($len, $entry) = split / /, $line;
+ my ($key, $value) = split /=/, $entry;
+ if ($key eq 'path') {
+ $next_path = $value;
+ }
+ }
+ next;
+ } elsif ($name =~ m{/\z}) { # directory
+ next;
+ } elsif ($typeflag != 1) { # handle hard links later
+ print FI "blob\n", "mark :$next_mark\n";
+ if ($typeflag == 2) { # symbolic link
+ print FI "data ", length($linkname), "\n",
+ $linkname;
+ $mode = 0120000;
+ } else {
+ print FI "data $size\n";
+ while ($size > 0 && read(I, $_, 512) == 512) {
+ print FI substr($_, 0, $size);
+ $size -= 512;
+ }
+ }
+ print FI "\n";
}
- print FI "\n";
+
+ next if ($typeflag eq 'g'); # ignore global header
my $path;
if ($prefix) {
@@ -115,7 +147,13 @@ foreach my $tar_file (@ARGV)
} else {
$path = "$name";
}
- $files{$path} = [$next_mark++, $mode];
+
+ if ($typeflag == 1) { # hard link
+ $linkname = "$prefix/$linkname" if $prefix;
+ $files{$path} = [ $files{$linkname}->[0], $mode ];
+ } else {
+ $files{$path} = [$next_mark++, $mode];
+ }
$author_time = $mtime if $mtime > $author_time;
$path =~ m,^([^/]+)/,;
diff --git a/contrib/fast-import/import-zips.py b/contrib/fast-import/import-zips.py
index 82f5ed3ddc..d12c296223 100755
--- a/contrib/fast-import/import-zips.py
+++ b/contrib/fast-import/import-zips.py
@@ -9,13 +9,18 @@
## git log --stat import-zips
from os import popen, path
-from sys import argv, exit
+from sys import argv, exit, hexversion, stderr
from time import mktime
from zipfile import ZipFile
+if hexversion < 0x01060000:
+ # The limiter is the zipfile module
+ stderr.write("import-zips.py: requires Python 1.6.0 or later.\n")
+ exit(1)
+
if len(argv) < 2:
- print 'Usage:', argv[0], '<zipfile>...'
- exit(1)
+ print 'usage:', argv[0], '<zipfile>...'
+ exit(1)
branch_ref = 'refs/heads/import-zips'
committer_name = 'Z Ip Creator'
@@ -23,51 +28,51 @@ committer_email = 'zip@example.com'
fast_import = popen('git fast-import --quiet', 'w')
def printlines(list):
- for str in list:
- fast_import.write(str + "\n")
+ for str in list:
+ fast_import.write(str + "\n")
for zipfile in argv[1:]:
- commit_time = 0
- next_mark = 1
- common_prefix = None
- mark = dict()
-
- zip = ZipFile(zipfile, 'r')
- for name in zip.namelist():
- if name.endswith('/'):
- continue
- info = zip.getinfo(name)
-
- if commit_time < info.date_time:
- commit_time = info.date_time
- if common_prefix == None:
- common_prefix = name[:name.rfind('/') + 1]
- else:
- while not name.startswith(common_prefix):
- last_slash = common_prefix[:-1].rfind('/') + 1
- common_prefix = common_prefix[:last_slash]
-
- mark[name] = ':' + str(next_mark)
- next_mark += 1
-
- printlines(('blob', 'mark ' + mark[name], \
- 'data ' + str(info.file_size)))
- fast_import.write(zip.read(name) + "\n")
-
- committer = committer_name + ' <' + committer_email + '> %d +0000' % \
- mktime(commit_time + (0, 0, 0))
-
- printlines(('commit ' + branch_ref, 'committer ' + committer, \
- 'data <<EOM', 'Imported from ' + zipfile + '.', 'EOM', \
- '', 'deleteall'))
-
- for name in mark.keys():
- fast_import.write('M 100644 ' + mark[name] + ' ' +
- name[len(common_prefix):] + "\n")
-
- printlines(('', 'tag ' + path.basename(zipfile), \
- 'from ' + branch_ref, 'tagger ' + committer, \
- 'data <<EOM', 'Package ' + zipfile, 'EOM', ''))
+ commit_time = 0
+ next_mark = 1
+ common_prefix = None
+ mark = dict()
+
+ zip = ZipFile(zipfile, 'r')
+ for name in zip.namelist():
+ if name.endswith('/'):
+ continue
+ info = zip.getinfo(name)
+
+ if commit_time < info.date_time:
+ commit_time = info.date_time
+ if common_prefix == None:
+ common_prefix = name[:name.rfind('/') + 1]
+ else:
+ while not name.startswith(common_prefix):
+ last_slash = common_prefix[:-1].rfind('/') + 1
+ common_prefix = common_prefix[:last_slash]
+
+ mark[name] = ':' + str(next_mark)
+ next_mark += 1
+
+ printlines(('blob', 'mark ' + mark[name], \
+ 'data ' + str(info.file_size)))
+ fast_import.write(zip.read(name) + "\n")
+
+ committer = committer_name + ' <' + committer_email + '> %d +0000' % \
+ mktime(commit_time + (0, 0, 0))
+
+ printlines(('commit ' + branch_ref, 'committer ' + committer, \
+ 'data <<EOM', 'Imported from ' + zipfile + '.', 'EOM', \
+ '', 'deleteall'))
+
+ for name in mark.keys():
+ fast_import.write('M 100644 ' + mark[name] + ' ' +
+ name[len(common_prefix):] + "\n")
+
+ printlines(('', 'tag ' + path.basename(zipfile), \
+ 'from ' + branch_ref, 'tagger ' + committer, \
+ 'data <<EOM', 'Package ' + zipfile, 'EOM', ''))
if fast_import.close():
- exit(1)
+ exit(1)
diff --git a/contrib/git-jump/README b/contrib/git-jump/README
index 1cebc328cb..2f618a7f97 100644
--- a/contrib/git-jump/README
+++ b/contrib/git-jump/README
@@ -25,17 +25,27 @@ git-jump will feed this to the editor:
foo.c:2: printf("hello word!\n");
-----------------------------------
+Or, when running 'git jump grep', column numbers will also be emitted,
+e.g. `git jump grep "hello"` would return:
+
+-----------------------------------
+foo.c:2:9: printf("hello word!\n");
+-----------------------------------
+
Obviously this trivial case isn't that interesting; you could just open
`foo.c` yourself. But when you have many changes scattered across a
project, you can use the editor's support to "jump" from point to point.
-Git-jump can generate three types of interesting lists:
+Git-jump can generate four types of interesting lists:
1. The beginning of any diff hunks.
2. The beginning of any merge conflict markers.
- 3. Any grep matches.
+ 3. Any grep matches, including the column of the first match on a
+ line.
+
+ 4. Any whitespace errors detected by `git diff --check`.
Using git-jump
@@ -61,6 +71,9 @@ git jump grep foo_bar
# same as above, but case-insensitive; you can give
# arbitrary grep options
git jump grep -i foo_bar
+
+# use the silver searcher for git jump grep
+git config jump.grepCmd "ag --column"
--------------------------------------------------
@@ -77,16 +90,23 @@ which does something similar to `git jump grep`. However, it is limited
to positioning the cursor to the correct line in only the first file,
leaving you to locate subsequent hits in that file or other files using
the editor or pager. By contrast, git-jump provides the editor with a
-complete list of files and line numbers for each match.
+complete list of files, lines, and a column number for each match.
Limitations
-----------
-This scripts was written and tested with vim. Given that the quickfix
+This script was written and tested with vim. Given that the quickfix
format is the same as what gcc produces, I expect emacs users have a
similar feature for iterating through the list, but I know nothing about
how to activate it.
The shell snippets to generate the quickfix lines will almost certainly
choke on filenames with exotic characters (like newlines).
+
+Contributing
+------------
+
+Bug fixes, bug reports, and feature requests should be discussed on the
+Git mailing list <git@vger.kernel.org>, and cc'd to the git-jump
+maintainer, Jeff King <peff@peff.net>.
diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump
index dc90cd6379..931b0fe3a9 100755
--- a/contrib/git-jump/git-jump
+++ b/contrib/git-jump/git-jump
@@ -11,7 +11,10 @@ diff: elements are diff hunks. Arguments are given to diff.
merge: elements are merge conflicts. Arguments are ignored.
-grep: elements are grep hits. Arguments are given to grep.
+grep: elements are grep hits. Arguments are given to git grep or, if
+ configured, to the command in `jump.grepCmd`.
+
+ws: elements are whitespace errors. Arguments are given to diff --check.
EOF
}
@@ -25,7 +28,7 @@ mode_diff() {
perl -ne '
if (m{^\+\+\+ (.*)}) { $file = $1; next }
defined($file) or next;
- if (m/^@@ .*\+(\d+)/) { $line = $1; next }
+ if (m/^@@ .*?\+(\d+)/) { $line = $1; next }
defined($line) or next;
if (/^ /) { $line++; next }
if (/^[-+]\s*(.*)/) {
@@ -48,13 +51,19 @@ mode_merge() {
# but let's clean up extra whitespace, so they look better if the
# editor shows them to us in the status bar.
mode_grep() {
- git grep -n "$@" |
+ cmd=$(git config jump.grepCmd)
+ test -n "$cmd" || cmd="git grep -n --column"
+ $cmd "$@" |
perl -pe '
s/[ \t]+/ /g;
s/^ *//;
'
}
+mode_ws() {
+ git diff --check "$@"
+}
+
if test $# -lt 1; then
usage >&2
exit 1
diff --git a/contrib/git-resurrect.sh b/contrib/git-resurrect.sh
index a4ed4c3c62..d843df3afd 100755
--- a/contrib/git-resurrect.sh
+++ b/contrib/git-resurrect.sh
@@ -10,6 +10,7 @@ is rather slow but allows you to resurrect other people's topic
branches."
OPTIONS_KEEPDASHDASH=
+OPTIONS_STUCKLONG=
OPTIONS_SPEC="\
git resurrect $USAGE
--
@@ -25,30 +26,29 @@ n,dry-run don't recreate the branch"
. git-sh-setup
search_reflog () {
- sed -ne 's~^\([^ ]*\) .*\tcheckout: moving from '"$1"' .*~\1~p' \
- < "$GIT_DIR"/logs/HEAD
+ sed -ne 's~^\([^ ]*\) .* checkout: moving from '"$1"' .*~\1~p' \
+ < "$GIT_DIR"/logs/HEAD
}
search_reflog_merges () {
git rev-parse $(
- sed -ne 's~^[^ ]* \([^ ]*\) .*\tmerge '"$1"':.*~\1^2~p' \
+ sed -ne 's~^[^ ]* \([^ ]*\) .* merge '"$1"':.*~\1^2~p' \
< "$GIT_DIR"/logs/HEAD
)
}
-_x40="[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]"
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+oid_pattern=$(git hash-object --stdin </dev/null | sed -e 's/./[0-9a-f]/g')
search_merges () {
- git rev-list --all --grep="Merge branch '$1'" \
- --pretty=tformat:"%P %s" |
- sed -ne "/^$_x40 \($_x40\) Merge .*/ {s//\1/p;$early_exit}"
+ git rev-list --all --grep="Merge branch '$1'" \
+ --pretty=tformat:"%P %s" |
+ sed -ne "/^$oid_pattern \($oid_pattern\) Merge .*/ {s//\1/p;$early_exit}"
}
search_merge_targets () {
git rev-list --all --grep="Merge branch '[^']*' into $branch\$" \
--pretty=tformat:"%H %s" --all |
- sed -ne "/^\($_x40\) Merge .*/ {s//\1/p;$early_exit} "
+ sed -ne "/^\($oid_pattern\) Merge .*/ {s//\1/p;$early_exit} "
}
dry_run=
diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview
deleted file mode 100755
index 4c99dfb903..0000000000
--- a/contrib/gitview/gitview
+++ /dev/null
@@ -1,1305 +0,0 @@
-#! /usr/bin/env python
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-
-""" gitview
-GUI browser for git repository
-This program is based on bzrk by Scott James Remnant <scott@ubuntu.com>
-"""
-__copyright__ = "Copyright (C) 2006 Hewlett-Packard Development Company, L.P."
-__copyright__ = "Copyright (C) 2007 Aneesh Kumar K.V <aneesh.kumar@gmail.com"
-__author__ = "Aneesh Kumar K.V <aneesh.kumar@gmail.com>"
-
-
-import sys
-import os
-import gtk
-import pygtk
-import pango
-import re
-import time
-import gobject
-import cairo
-import math
-import string
-import fcntl
-
-have_gtksourceview2 = False
-have_gtksourceview = False
-try:
- import gtksourceview2
- have_gtksourceview2 = True
-except ImportError:
- try:
- import gtksourceview
- have_gtksourceview = True
- except ImportError:
- print "Running without gtksourceview2 or gtksourceview module"
-
-re_ident = re.compile('(author|committer) (?P<ident>.*) (?P<epoch>\d+) (?P<tz>[+-]\d{4})')
-
-def list_to_string(args, skip):
- count = len(args)
- i = skip
- str_arg=" "
- while (i < count ):
- str_arg = str_arg + args[i]
- str_arg = str_arg + " "
- i = i+1
-
- return str_arg
-
-def show_date(epoch, tz):
- secs = float(epoch)
- tzsecs = float(tz[1:3]) * 3600
- tzsecs += float(tz[3:5]) * 60
- if (tz[0] == "+"):
- secs += tzsecs
- else:
- secs -= tzsecs
-
- return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(secs))
-
-def get_source_buffer_and_view():
- if have_gtksourceview2:
- buffer = gtksourceview2.Buffer()
- slm = gtksourceview2.LanguageManager()
- gsl = slm.get_language("diff")
- buffer.set_highlight_syntax(True)
- buffer.set_language(gsl)
- view = gtksourceview2.View(buffer)
- elif have_gtksourceview:
- buffer = gtksourceview.SourceBuffer()
- slm = gtksourceview.SourceLanguagesManager()
- gsl = slm.get_language_from_mime_type("text/x-patch")
- buffer.set_highlight(True)
- buffer.set_language(gsl)
- view = gtksourceview.SourceView(buffer)
- else:
- buffer = gtk.TextBuffer()
- view = gtk.TextView(buffer)
- return (buffer, view)
-
-
-class CellRendererGraph(gtk.GenericCellRenderer):
- """Cell renderer for directed graph.
-
- This module contains the implementation of a custom GtkCellRenderer that
- draws part of the directed graph based on the lines suggested by the code
- in graph.py.
-
- Because we're shiny, we use Cairo to do this, and because we're naughty
- we cheat and draw over the bits of the TreeViewColumn that are supposed to
- just be for the background.
-
- Properties:
- node (column, colour, [ names ]) tuple to draw revision node,
- in_lines (start, end, colour) tuple list to draw inward lines,
- out_lines (start, end, colour) tuple list to draw outward lines.
- """
-
- __gproperties__ = {
- "node": ( gobject.TYPE_PYOBJECT, "node",
- "revision node instruction",
- gobject.PARAM_WRITABLE
- ),
- "in-lines": ( gobject.TYPE_PYOBJECT, "in-lines",
- "instructions to draw lines into the cell",
- gobject.PARAM_WRITABLE
- ),
- "out-lines": ( gobject.TYPE_PYOBJECT, "out-lines",
- "instructions to draw lines out of the cell",
- gobject.PARAM_WRITABLE
- ),
- }
-
- def do_set_property(self, property, value):
- """Set properties from GObject properties."""
- if property.name == "node":
- self.node = value
- elif property.name == "in-lines":
- self.in_lines = value
- elif property.name == "out-lines":
- self.out_lines = value
- else:
- raise AttributeError, "no such property: '%s'" % property.name
-
- def box_size(self, widget):
- """Calculate box size based on widget's font.
-
- Cache this as it's probably expensive to get. It ensures that we
- draw the graph at least as large as the text.
- """
- try:
- return self._box_size
- except AttributeError:
- pango_ctx = widget.get_pango_context()
- font_desc = widget.get_style().font_desc
- metrics = pango_ctx.get_metrics(font_desc)
-
- ascent = pango.PIXELS(metrics.get_ascent())
- descent = pango.PIXELS(metrics.get_descent())
-
- self._box_size = ascent + descent + 6
- return self._box_size
-
- def set_colour(self, ctx, colour, bg, fg):
- """Set the context source colour.
-
- Picks a distinct colour based on an internal wheel; the bg
- parameter provides the value that should be assigned to the 'zero'
- colours and the fg parameter provides the multiplier that should be
- applied to the foreground colours.
- """
- colours = [
- ( 1.0, 0.0, 0.0 ),
- ( 1.0, 1.0, 0.0 ),
- ( 0.0, 1.0, 0.0 ),
- ( 0.0, 1.0, 1.0 ),
- ( 0.0, 0.0, 1.0 ),
- ( 1.0, 0.0, 1.0 ),
- ]
-
- colour %= len(colours)
- red = (colours[colour][0] * fg) or bg
- green = (colours[colour][1] * fg) or bg
- blue = (colours[colour][2] * fg) or bg
-
- ctx.set_source_rgb(red, green, blue)
-
- def on_get_size(self, widget, cell_area):
- """Return the size we need for this cell.
-
- Each cell is drawn individually and is only as wide as it needs
- to be, we let the TreeViewColumn take care of making them all
- line up.
- """
- box_size = self.box_size(widget)
-
- cols = self.node[0]
- for start, end, colour in self.in_lines + self.out_lines:
- cols = int(max(cols, start, end))
-
- (column, colour, names) = self.node
- names_len = 0
- if (len(names) != 0):
- for item in names:
- names_len += len(item)
-
- width = box_size * (cols + 1 ) + names_len
- height = box_size
-
- # FIXME I have no idea how to use cell_area properly
- return (0, 0, width, height)
-
- def on_render(self, window, widget, bg_area, cell_area, exp_area, flags):
- """Render an individual cell.
-
- Draws the cell contents using cairo, taking care to clip what we
- do to within the background area so we don't draw over other cells.
- Note that we're a bit naughty there and should really be drawing
- in the cell_area (or even the exposed area), but we explicitly don't
- want any gutter.
-
- We try and be a little clever, if the line we need to draw is going
- to cross other columns we actually draw it as in the .---' style
- instead of a pure diagonal ... this reduces confusion by an
- incredible amount.
- """
- ctx = window.cairo_create()
- ctx.rectangle(bg_area.x, bg_area.y, bg_area.width, bg_area.height)
- ctx.clip()
-
- box_size = self.box_size(widget)
-
- ctx.set_line_width(box_size / 8)
- ctx.set_line_cap(cairo.LINE_CAP_SQUARE)
-
- # Draw lines into the cell
- for start, end, colour in self.in_lines:
- ctx.move_to(cell_area.x + box_size * start + box_size / 2,
- bg_area.y - bg_area.height / 2)
-
- if start - end > 1:
- ctx.line_to(cell_area.x + box_size * start, bg_area.y)
- ctx.line_to(cell_area.x + box_size * end + box_size, bg_area.y)
- elif start - end < -1:
- ctx.line_to(cell_area.x + box_size * start + box_size,
- bg_area.y)
- ctx.line_to(cell_area.x + box_size * end, bg_area.y)
-
- ctx.line_to(cell_area.x + box_size * end + box_size / 2,
- bg_area.y + bg_area.height / 2)
-
- self.set_colour(ctx, colour, 0.0, 0.65)
- ctx.stroke()
-
- # Draw lines out of the cell
- for start, end, colour in self.out_lines:
- ctx.move_to(cell_area.x + box_size * start + box_size / 2,
- bg_area.y + bg_area.height / 2)
-
- if start - end > 1:
- ctx.line_to(cell_area.x + box_size * start,
- bg_area.y + bg_area.height)
- ctx.line_to(cell_area.x + box_size * end + box_size,
- bg_area.y + bg_area.height)
- elif start - end < -1:
- ctx.line_to(cell_area.x + box_size * start + box_size,
- bg_area.y + bg_area.height)
- ctx.line_to(cell_area.x + box_size * end,
- bg_area.y + bg_area.height)
-
- ctx.line_to(cell_area.x + box_size * end + box_size / 2,
- bg_area.y + bg_area.height / 2 + bg_area.height)
-
- self.set_colour(ctx, colour, 0.0, 0.65)
- ctx.stroke()
-
- # Draw the revision node in the right column
- (column, colour, names) = self.node
- ctx.arc(cell_area.x + box_size * column + box_size / 2,
- cell_area.y + cell_area.height / 2,
- box_size / 4, 0, 2 * math.pi)
-
-
- self.set_colour(ctx, colour, 0.0, 0.5)
- ctx.stroke_preserve()
-
- self.set_colour(ctx, colour, 0.5, 1.0)
- ctx.fill_preserve()
-
- if (len(names) != 0):
- name = " "
- for item in names:
- name = name + item + " "
-
- ctx.set_font_size(13)
- if (flags & 1):
- self.set_colour(ctx, colour, 0.5, 1.0)
- else:
- self.set_colour(ctx, colour, 0.0, 0.5)
- ctx.show_text(name)
-
-class Commit(object):
- """ This represent a commit object obtained after parsing the git-rev-list
- output """
-
- __slots__ = ['children_sha1', 'message', 'author', 'date', 'committer',
- 'commit_date', 'commit_sha1', 'parent_sha1']
-
- children_sha1 = {}
-
- def __init__(self, commit_lines):
- self.message = ""
- self.author = ""
- self.date = ""
- self.committer = ""
- self.commit_date = ""
- self.commit_sha1 = ""
- self.parent_sha1 = [ ]
- self.parse_commit(commit_lines)
-
-
- def parse_commit(self, commit_lines):
-
- # First line is the sha1 lines
- line = string.strip(commit_lines[0])
- sha1 = re.split(" ", line)
- self.commit_sha1 = sha1[0]
- self.parent_sha1 = sha1[1:]
-
- #build the child list
- for parent_id in self.parent_sha1:
- try:
- Commit.children_sha1[parent_id].append(self.commit_sha1)
- except KeyError:
- Commit.children_sha1[parent_id] = [self.commit_sha1]
-
- # IF we don't have parent
- if (len(self.parent_sha1) == 0):
- self.parent_sha1 = [0]
-
- for line in commit_lines[1:]:
- m = re.match("^ ", line)
- if (m != None):
- # First line of the commit message used for short log
- if self.message == "":
- self.message = string.strip(line)
- continue
-
- m = re.match("tree", line)
- if (m != None):
- continue
-
- m = re.match("parent", line)
- if (m != None):
- continue
-
- m = re_ident.match(line)
- if (m != None):
- date = show_date(m.group('epoch'), m.group('tz'))
- if m.group(1) == "author":
- self.author = m.group('ident')
- self.date = date
- elif m.group(1) == "committer":
- self.committer = m.group('ident')
- self.commit_date = date
-
- continue
-
- def get_message(self, with_diff=0):
- if (with_diff == 1):
- message = self.diff_tree()
- else:
- fp = os.popen("git cat-file commit " + self.commit_sha1)
- message = fp.read()
- fp.close()
-
- return message
-
- def diff_tree(self):
- fp = os.popen("git diff-tree --pretty --cc -v -p --always " + self.commit_sha1)
- diff = fp.read()
- fp.close()
- return diff
-
-class AnnotateWindow(object):
- """Annotate window.
- This object represents and manages a single window containing the
- annotate information of the file
- """
-
- def __init__(self):
- self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
- self.window.set_border_width(0)
- self.window.set_title("Git repository browser annotation window")
- self.prev_read = ""
-
- # Use two thirds of the screen by default
- screen = self.window.get_screen()
- monitor = screen.get_monitor_geometry(0)
- width = int(monitor.width * 0.66)
- height = int(monitor.height * 0.66)
- self.window.set_default_size(width, height)
-
- def add_file_data(self, filename, commit_sha1, line_num):
- fp = os.popen("git cat-file blob " + commit_sha1 +":"+filename)
- i = 1;
- for line in fp.readlines():
- line = string.rstrip(line)
- self.model.append(None, ["HEAD", filename, line, i])
- i = i+1
- fp.close()
-
- # now set the cursor position
- self.treeview.set_cursor(line_num-1)
- self.treeview.grab_focus()
-
- def _treeview_cursor_cb(self, *args):
- """Callback for when the treeview cursor changes."""
- (path, col) = self.treeview.get_cursor()
- commit_sha1 = self.model[path][0]
- commit_msg = ""
- fp = os.popen("git cat-file commit " + commit_sha1)
- for line in fp.readlines():
- commit_msg = commit_msg + line
- fp.close()
-
- self.commit_buffer.set_text(commit_msg)
-
- def _treeview_row_activated(self, *args):
- """Callback for when the treeview row gets selected."""
- (path, col) = self.treeview.get_cursor()
- commit_sha1 = self.model[path][0]
- filename = self.model[path][1]
- line_num = self.model[path][3]
-
- window = AnnotateWindow();
- fp = os.popen("git rev-parse "+ commit_sha1 + "~1")
- commit_sha1 = string.strip(fp.readline())
- fp.close()
- window.annotate(filename, commit_sha1, line_num)
-
- def data_ready(self, source, condition):
- while (1):
- try :
- # A simple readline doesn't work
- # a readline bug ??
- buffer = source.read(100)
-
- except:
- # resource temporary not available
- return True
-
- if (len(buffer) == 0):
- gobject.source_remove(self.io_watch_tag)
- source.close()
- return False
-
- if (self.prev_read != ""):
- buffer = self.prev_read + buffer
- self.prev_read = ""
-
- if (buffer[len(buffer) -1] != '\n'):
- try:
- newline_index = buffer.rindex("\n")
- except ValueError:
- newline_index = 0
-
- self.prev_read = buffer[newline_index:(len(buffer))]
- buffer = buffer[0:newline_index]
-
- for buff in buffer.split("\n"):
- annotate_line = re.compile('^([0-9a-f]{40}) (.+) (.+) (.+)$')
- m = annotate_line.match(buff)
- if not m:
- annotate_line = re.compile('^(filename) (.+)$')
- m = annotate_line.match(buff)
- if not m:
- continue
- filename = m.group(2)
- else:
- self.commit_sha1 = m.group(1)
- self.source_line = int(m.group(2))
- self.result_line = int(m.group(3))
- self.count = int(m.group(4))
- #set the details only when we have the file name
- continue
-
- while (self.count > 0):
- # set at result_line + count-1 the sha1 as commit_sha1
- self.count = self.count - 1
- iter = self.model.iter_nth_child(None, self.result_line + self.count-1)
- self.model.set(iter, 0, self.commit_sha1, 1, filename, 3, self.source_line)
-
-
- def annotate(self, filename, commit_sha1, line_num):
- # verify the commit_sha1 specified has this filename
-
- fp = os.popen("git ls-tree "+ commit_sha1 + " -- " + filename)
- line = string.strip(fp.readline())
- if line == '':
- # pop up the message the file is not there as a part of the commit
- fp.close()
- dialog = gtk.MessageDialog(parent=None, flags=0,
- type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,
- message_format=None)
- dialog.set_markup("The file %s is not present in the parent commit %s" % (filename, commit_sha1))
- dialog.run()
- dialog.destroy()
- return
-
- fp.close()
-
- vpan = gtk.VPaned();
- self.window.add(vpan);
- vpan.show()
-
- scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrollwin.set_shadow_type(gtk.SHADOW_IN)
- vpan.pack1(scrollwin, True, True);
- scrollwin.show()
-
- self.model = gtk.TreeStore(str, str, str, int)
- self.treeview = gtk.TreeView(self.model)
- self.treeview.set_rules_hint(True)
- self.treeview.set_search_column(0)
- self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
- self.treeview.connect("row-activated", self._treeview_row_activated)
- scrollwin.add(self.treeview)
- self.treeview.show()
-
- cell = gtk.CellRendererText()
- cell.set_property("width-chars", 10)
- cell.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("Commit")
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 0)
- self.treeview.append_column(column)
-
- cell = gtk.CellRendererText()
- cell.set_property("width-chars", 20)
- cell.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("File Name")
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 1)
- self.treeview.append_column(column)
-
- cell = gtk.CellRendererText()
- cell.set_property("width-chars", 20)
- cell.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("Data")
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 2)
- self.treeview.append_column(column)
-
- # The commit message window
- scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrollwin.set_shadow_type(gtk.SHADOW_IN)
- vpan.pack2(scrollwin, True, True);
- scrollwin.show()
-
- commit_text = gtk.TextView()
- self.commit_buffer = gtk.TextBuffer()
- commit_text.set_buffer(self.commit_buffer)
- scrollwin.add(commit_text)
- commit_text.show()
-
- self.window.show()
-
- self.add_file_data(filename, commit_sha1, line_num)
-
- fp = os.popen("git blame --incremental -C -C -- " + filename + " " + commit_sha1)
- flags = fcntl.fcntl(fp.fileno(), fcntl.F_GETFL)
- fcntl.fcntl(fp.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)
- self.io_watch_tag = gobject.io_add_watch(fp, gobject.IO_IN, self.data_ready)
-
-
-class DiffWindow(object):
- """Diff window.
- This object represents and manages a single window containing the
- differences between two revisions on a branch.
- """
-
- def __init__(self):
- self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
- self.window.set_border_width(0)
- self.window.set_title("Git repository browser diff window")
-
- # Use two thirds of the screen by default
- screen = self.window.get_screen()
- monitor = screen.get_monitor_geometry(0)
- width = int(monitor.width * 0.66)
- height = int(monitor.height * 0.66)
- self.window.set_default_size(width, height)
-
-
- self.construct()
-
- def construct(self):
- """Construct the window contents."""
- vbox = gtk.VBox()
- self.window.add(vbox)
- vbox.show()
-
- menu_bar = gtk.MenuBar()
- save_menu = gtk.ImageMenuItem(gtk.STOCK_SAVE)
- save_menu.connect("activate", self.save_menu_response, "save")
- save_menu.show()
- menu_bar.append(save_menu)
- vbox.pack_start(menu_bar, expand=False, fill=True)
- menu_bar.show()
-
- hpan = gtk.HPaned()
-
- scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrollwin.set_shadow_type(gtk.SHADOW_IN)
- hpan.pack1(scrollwin, True, True)
- scrollwin.show()
-
- (self.buffer, sourceview) = get_source_buffer_and_view()
-
- sourceview.set_editable(False)
- sourceview.modify_font(pango.FontDescription("Monospace"))
- scrollwin.add(sourceview)
- sourceview.show()
-
- # The file hierarchy: a scrollable treeview
- scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrollwin.set_shadow_type(gtk.SHADOW_IN)
- scrollwin.set_size_request(20, -1)
- hpan.pack2(scrollwin, True, True)
- scrollwin.show()
-
- self.model = gtk.TreeStore(str, str, str)
- self.treeview = gtk.TreeView(self.model)
- self.treeview.set_search_column(1)
- self.treeview.connect("cursor-changed", self._treeview_clicked)
- scrollwin.add(self.treeview)
- self.treeview.show()
-
- cell = gtk.CellRendererText()
- cell.set_property("width-chars", 20)
- column = gtk.TreeViewColumn("Select to annotate")
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 0)
- self.treeview.append_column(column)
-
- vbox.pack_start(hpan, expand=True, fill=True)
- hpan.show()
-
- def _treeview_clicked(self, *args):
- """Callback for when the treeview cursor changes."""
- (path, col) = self.treeview.get_cursor()
- specific_file = self.model[path][1]
- commit_sha1 = self.model[path][2]
- if specific_file == None :
- return
- elif specific_file == "" :
- specific_file = None
-
- window = AnnotateWindow();
- window.annotate(specific_file, commit_sha1, 1)
-
-
- def commit_files(self, commit_sha1, parent_sha1):
- self.model.clear()
- add = self.model.append(None, [ "Added", None, None])
- dele = self.model.append(None, [ "Deleted", None, None])
- mod = self.model.append(None, [ "Modified", None, None])
- diff_tree = re.compile('^(:.{6}) (.{6}) (.{40}) (.{40}) (A|D|M)\s(.+)$')
- fp = os.popen("git diff-tree -r --no-commit-id " + parent_sha1 + " " + commit_sha1)
- while 1:
- line = string.strip(fp.readline())
- if line == '':
- break
- m = diff_tree.match(line)
- if not m:
- continue
-
- attr = m.group(5)
- filename = m.group(6)
- if attr == "A":
- self.model.append(add, [filename, filename, commit_sha1])
- elif attr == "D":
- self.model.append(dele, [filename, filename, commit_sha1])
- elif attr == "M":
- self.model.append(mod, [filename, filename, commit_sha1])
- fp.close()
-
- self.treeview.expand_all()
-
- def set_diff(self, commit_sha1, parent_sha1, encoding):
- """Set the differences showed by this window.
- Compares the two trees and populates the window with the
- differences.
- """
- # Diff with the first commit or the last commit shows nothing
- if (commit_sha1 == 0 or parent_sha1 == 0 ):
- return
-
- fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1)
- self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8'))
- fp.close()
- self.commit_files(commit_sha1, parent_sha1)
- self.window.show()
-
- def save_menu_response(self, widget, string):
- dialog = gtk.FileChooserDialog("Save..", None, gtk.FILE_CHOOSER_ACTION_SAVE,
- (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
- gtk.STOCK_SAVE, gtk.RESPONSE_OK))
- dialog.set_default_response(gtk.RESPONSE_OK)
- response = dialog.run()
- if response == gtk.RESPONSE_OK:
- patch_buffer = self.buffer.get_text(self.buffer.get_start_iter(),
- self.buffer.get_end_iter())
- fp = open(dialog.get_filename(), "w")
- fp.write(patch_buffer)
- fp.close()
- dialog.destroy()
-
-class GitView(object):
- """ This is the main class
- """
- version = "0.9"
-
- def __init__(self, with_diff=0):
- self.with_diff = with_diff
- self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
- self.window.set_border_width(0)
- self.window.set_title("Git repository browser")
-
- self.get_encoding()
- self.get_bt_sha1()
-
- # Use three-quarters of the screen by default
- screen = self.window.get_screen()
- monitor = screen.get_monitor_geometry(0)
- width = int(monitor.width * 0.75)
- height = int(monitor.height * 0.75)
- self.window.set_default_size(width, height)
-
- # FIXME AndyFitz!
- icon = self.window.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
- self.window.set_icon(icon)
-
- self.accel_group = gtk.AccelGroup()
- self.window.add_accel_group(self.accel_group)
- self.accel_group.connect_group(0xffc2, 0, gtk.ACCEL_LOCKED, self.refresh);
- self.accel_group.connect_group(0xffc1, 0, gtk.ACCEL_LOCKED, self.maximize);
- self.accel_group.connect_group(0xffc8, 0, gtk.ACCEL_LOCKED, self.fullscreen);
- self.accel_group.connect_group(0xffc9, 0, gtk.ACCEL_LOCKED, self.unfullscreen);
-
- self.window.add(self.construct())
-
- def refresh(self, widget, event=None, *arguments, **keywords):
- self.get_encoding()
- self.get_bt_sha1()
- Commit.children_sha1 = {}
- self.set_branch(sys.argv[without_diff:])
- self.window.show()
- return True
-
- def maximize(self, widget, event=None, *arguments, **keywords):
- self.window.maximize()
- return True
-
- def fullscreen(self, widget, event=None, *arguments, **keywords):
- self.window.fullscreen()
- return True
-
- def unfullscreen(self, widget, event=None, *arguments, **keywords):
- self.window.unfullscreen()
- return True
-
- def get_bt_sha1(self):
- """ Update the bt_sha1 dictionary with the
- respective sha1 details """
-
- self.bt_sha1 = { }
- ls_remote = re.compile('^(.{40})\trefs/([^^]+)(?:\\^(..))?$');
- fp = os.popen('git ls-remote "${GIT_DIR-.git}"')
- while 1:
- line = string.strip(fp.readline())
- if line == '':
- break
- m = ls_remote.match(line)
- if not m:
- continue
- (sha1, name) = (m.group(1), m.group(2))
- if not self.bt_sha1.has_key(sha1):
- self.bt_sha1[sha1] = []
- self.bt_sha1[sha1].append(name)
- fp.close()
-
- def get_encoding(self):
- fp = os.popen("git config --get i18n.commitencoding")
- self.encoding=string.strip(fp.readline())
- fp.close()
- if (self.encoding == ""):
- self.encoding = "utf-8"
-
-
- def construct(self):
- """Construct the window contents."""
- vbox = gtk.VBox()
- paned = gtk.VPaned()
- paned.pack1(self.construct_top(), resize=False, shrink=True)
- paned.pack2(self.construct_bottom(), resize=False, shrink=True)
- menu_bar = gtk.MenuBar()
- menu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL)
- help_menu = gtk.MenuItem("Help")
- menu = gtk.Menu()
- about_menu = gtk.MenuItem("About")
- menu.append(about_menu)
- about_menu.connect("activate", self.about_menu_response, "about")
- about_menu.show()
- help_menu.set_submenu(menu)
- help_menu.show()
- menu_bar.append(help_menu)
- menu_bar.show()
- vbox.pack_start(menu_bar, expand=False, fill=True)
- vbox.pack_start(paned, expand=True, fill=True)
- paned.show()
- vbox.show()
- return vbox
-
-
- def construct_top(self):
- """Construct the top-half of the window."""
- vbox = gtk.VBox(spacing=6)
- vbox.set_border_width(12)
- vbox.show()
-
-
- scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrollwin.set_shadow_type(gtk.SHADOW_IN)
- vbox.pack_start(scrollwin, expand=True, fill=True)
- scrollwin.show()
-
- self.treeview = gtk.TreeView()
- self.treeview.set_rules_hint(True)
- self.treeview.set_search_column(4)
- self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
- scrollwin.add(self.treeview)
- self.treeview.show()
-
- cell = CellRendererGraph()
- column = gtk.TreeViewColumn()
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "node", 1)
- column.add_attribute(cell, "in-lines", 2)
- column.add_attribute(cell, "out-lines", 3)
- self.treeview.append_column(column)
-
- cell = gtk.CellRendererText()
- cell.set_property("width-chars", 65)
- cell.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("Message")
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 4)
- self.treeview.append_column(column)
-
- cell = gtk.CellRendererText()
- cell.set_property("width-chars", 40)
- cell.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("Author")
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 5)
- self.treeview.append_column(column)
-
- cell = gtk.CellRendererText()
- cell.set_property("ellipsize", pango.ELLIPSIZE_END)
- column = gtk.TreeViewColumn("Date")
- column.set_resizable(True)
- column.pack_start(cell, expand=True)
- column.add_attribute(cell, "text", 6)
- self.treeview.append_column(column)
-
- return vbox
-
- def about_menu_response(self, widget, string):
- dialog = gtk.AboutDialog()
- dialog.set_name("Gitview")
- dialog.set_version(GitView.version)
- dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@gmail.com>"])
- dialog.set_website("http://www.kernel.org/pub/software/scm/git/")
- dialog.set_copyright("Use and distribute under the terms of the GNU General Public License")
- dialog.set_wrap_license(True)
- dialog.run()
- dialog.destroy()
-
-
- def construct_bottom(self):
- """Construct the bottom half of the window."""
- vbox = gtk.VBox(False, spacing=6)
- vbox.set_border_width(12)
- (width, height) = self.window.get_size()
- vbox.set_size_request(width, int(height / 2.5))
- vbox.show()
-
- self.table = gtk.Table(rows=4, columns=4)
- self.table.set_row_spacings(6)
- self.table.set_col_spacings(6)
- vbox.pack_start(self.table, expand=False, fill=True)
- self.table.show()
-
- align = gtk.Alignment(0.0, 0.5)
- label = gtk.Label()
- label.set_markup("<b>Revision:</b>")
- align.add(label)
- self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
- label.show()
- align.show()
-
- align = gtk.Alignment(0.0, 0.5)
- self.revid_label = gtk.Label()
- self.revid_label.set_selectable(True)
- align.add(self.revid_label)
- self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
- self.revid_label.show()
- align.show()
-
- align = gtk.Alignment(0.0, 0.5)
- label = gtk.Label()
- label.set_markup("<b>Committer:</b>")
- align.add(label)
- self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
- label.show()
- align.show()
-
- align = gtk.Alignment(0.0, 0.5)
- self.committer_label = gtk.Label()
- self.committer_label.set_selectable(True)
- align.add(self.committer_label)
- self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
- self.committer_label.show()
- align.show()
-
- align = gtk.Alignment(0.0, 0.5)
- label = gtk.Label()
- label.set_markup("<b>Timestamp:</b>")
- align.add(label)
- self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
- label.show()
- align.show()
-
- align = gtk.Alignment(0.0, 0.5)
- self.timestamp_label = gtk.Label()
- self.timestamp_label.set_selectable(True)
- align.add(self.timestamp_label)
- self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
- self.timestamp_label.show()
- align.show()
-
- align = gtk.Alignment(0.0, 0.5)
- label = gtk.Label()
- label.set_markup("<b>Parents:</b>")
- align.add(label)
- self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
- label.show()
- align.show()
- self.parents_widgets = []
-
- align = gtk.Alignment(0.0, 0.5)
- label = gtk.Label()
- label.set_markup("<b>Children:</b>")
- align.add(label)
- self.table.attach(align, 2, 3, 3, 4, gtk.FILL, gtk.FILL)
- label.show()
- align.show()
- self.children_widgets = []
-
- scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrollwin.set_shadow_type(gtk.SHADOW_IN)
- vbox.pack_start(scrollwin, expand=True, fill=True)
- scrollwin.show()
-
- (self.message_buffer, sourceview) = get_source_buffer_and_view()
-
- sourceview.set_editable(False)
- sourceview.modify_font(pango.FontDescription("Monospace"))
- scrollwin.add(sourceview)
- sourceview.show()
-
- return vbox
-
- def _treeview_cursor_cb(self, *args):
- """Callback for when the treeview cursor changes."""
- (path, col) = self.treeview.get_cursor()
- commit = self.model[path][0]
-
- if commit.committer is not None:
- committer = commit.committer
- timestamp = commit.commit_date
- message = commit.get_message(self.with_diff)
- revid_label = commit.commit_sha1
- else:
- committer = ""
- timestamp = ""
- message = ""
- revid_label = ""
-
- self.revid_label.set_text(revid_label)
- self.committer_label.set_text(committer)
- self.timestamp_label.set_text(timestamp)
- self.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8'))
-
- for widget in self.parents_widgets:
- self.table.remove(widget)
-
- self.parents_widgets = []
- self.table.resize(4 + len(commit.parent_sha1) - 1, 4)
- for idx, parent_id in enumerate(commit.parent_sha1):
- self.table.set_row_spacing(idx + 3, 0)
-
- align = gtk.Alignment(0.0, 0.0)
- self.parents_widgets.append(align)
- self.table.attach(align, 1, 2, idx + 3, idx + 4,
- gtk.EXPAND | gtk.FILL, gtk.FILL)
- align.show()
-
- hbox = gtk.HBox(False, 0)
- align.add(hbox)
- hbox.show()
-
- label = gtk.Label(parent_id)
- label.set_selectable(True)
- hbox.pack_start(label, expand=False, fill=True)
- label.show()
-
- image = gtk.Image()
- image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
- image.show()
-
- button = gtk.Button()
- button.add(image)
- button.set_relief(gtk.RELIEF_NONE)
- button.connect("clicked", self._go_clicked_cb, parent_id)
- hbox.pack_start(button, expand=False, fill=True)
- button.show()
-
- image = gtk.Image()
- image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
- image.show()
-
- button = gtk.Button()
- button.add(image)
- button.set_relief(gtk.RELIEF_NONE)
- button.set_sensitive(True)
- button.connect("clicked", self._show_clicked_cb,
- commit.commit_sha1, parent_id, self.encoding)
- hbox.pack_start(button, expand=False, fill=True)
- button.show()
-
- # Populate with child details
- for widget in self.children_widgets:
- self.table.remove(widget)
-
- self.children_widgets = []
- try:
- child_sha1 = Commit.children_sha1[commit.commit_sha1]
- except KeyError:
- # We don't have child
- child_sha1 = [ 0 ]
-
- if ( len(child_sha1) > len(commit.parent_sha1)):
- self.table.resize(4 + len(child_sha1) - 1, 4)
-
- for idx, child_id in enumerate(child_sha1):
- self.table.set_row_spacing(idx + 3, 0)
-
- align = gtk.Alignment(0.0, 0.0)
- self.children_widgets.append(align)
- self.table.attach(align, 3, 4, idx + 3, idx + 4,
- gtk.EXPAND | gtk.FILL, gtk.FILL)
- align.show()
-
- hbox = gtk.HBox(False, 0)
- align.add(hbox)
- hbox.show()
-
- label = gtk.Label(child_id)
- label.set_selectable(True)
- hbox.pack_start(label, expand=False, fill=True)
- label.show()
-
- image = gtk.Image()
- image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
- image.show()
-
- button = gtk.Button()
- button.add(image)
- button.set_relief(gtk.RELIEF_NONE)
- button.connect("clicked", self._go_clicked_cb, child_id)
- hbox.pack_start(button, expand=False, fill=True)
- button.show()
-
- image = gtk.Image()
- image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
- image.show()
-
- button = gtk.Button()
- button.add(image)
- button.set_relief(gtk.RELIEF_NONE)
- button.set_sensitive(True)
- button.connect("clicked", self._show_clicked_cb,
- child_id, commit.commit_sha1, self.encoding)
- hbox.pack_start(button, expand=False, fill=True)
- button.show()
-
- def _destroy_cb(self, widget):
- """Callback for when a window we manage is destroyed."""
- self.quit()
-
-
- def quit(self):
- """Stop the GTK+ main loop."""
- gtk.main_quit()
-
- def run(self, args):
- self.set_branch(args)
- self.window.connect("destroy", self._destroy_cb)
- self.window.show()
- gtk.main()
-
- def set_branch(self, args):
- """Fill in different windows with info from the reposiroty"""
- fp = os.popen("git rev-parse --sq --default HEAD " + list_to_string(args, 1))
- git_rev_list_cmd = fp.read()
- fp.close()
- fp = os.popen("git rev-list --header --topo-order --parents " + git_rev_list_cmd)
- self.update_window(fp)
-
- def update_window(self, fp):
- commit_lines = []
-
- self.model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
- gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str, str, str)
-
- # used for cursor positioning
- self.index = {}
-
- self.colours = {}
- self.nodepos = {}
- self.incomplete_line = {}
- self.commits = []
-
- index = 0
- last_colour = 0
- last_nodepos = -1
- out_line = []
- input_line = fp.readline()
- while (input_line != ""):
- # The commit header ends with '\0'
- # This NULL is immediately followed by the sha1 of the
- # next commit
- if (input_line[0] != '\0'):
- commit_lines.append(input_line)
- input_line = fp.readline()
- continue;
-
- commit = Commit(commit_lines)
- if (commit != None ):
- self.commits.append(commit)
-
- # Skip the '\0
- commit_lines = []
- commit_lines.append(input_line[1:])
- input_line = fp.readline()
-
- fp.close()
-
- for commit in self.commits:
- (out_line, last_colour, last_nodepos) = self.draw_graph(commit,
- index, out_line,
- last_colour,
- last_nodepos)
- self.index[commit.commit_sha1] = index
- index += 1
-
- self.treeview.set_model(self.model)
- self.treeview.show()
-
- def draw_graph(self, commit, index, out_line, last_colour, last_nodepos):
- in_line=[]
-
- # | -> outline
- # X
- # |\ <- inline
-
- # Reset nodepostion
- if (last_nodepos > 5):
- last_nodepos = -1
-
- # Add the incomplete lines of the last cell in this
- try:
- colour = self.colours[commit.commit_sha1]
- except KeyError:
- self.colours[commit.commit_sha1] = last_colour+1
- last_colour = self.colours[commit.commit_sha1]
- colour = self.colours[commit.commit_sha1]
-
- try:
- node_pos = self.nodepos[commit.commit_sha1]
- except KeyError:
- self.nodepos[commit.commit_sha1] = last_nodepos+1
- last_nodepos = self.nodepos[commit.commit_sha1]
- node_pos = self.nodepos[commit.commit_sha1]
-
- #The first parent always continue on the same line
- try:
- # check we alreay have the value
- tmp_node_pos = self.nodepos[commit.parent_sha1[0]]
- except KeyError:
- self.colours[commit.parent_sha1[0]] = colour
- self.nodepos[commit.parent_sha1[0]] = node_pos
-
- for sha1 in self.incomplete_line.keys():
- if (sha1 != commit.commit_sha1):
- self.draw_incomplete_line(sha1, node_pos,
- out_line, in_line, index)
- else:
- del self.incomplete_line[sha1]
-
-
- for parent_id in commit.parent_sha1:
- try:
- tmp_node_pos = self.nodepos[parent_id]
- except KeyError:
- self.colours[parent_id] = last_colour+1
- last_colour = self.colours[parent_id]
- self.nodepos[parent_id] = last_nodepos+1
- last_nodepos = self.nodepos[parent_id]
-
- in_line.append((node_pos, self.nodepos[parent_id],
- self.colours[parent_id]))
- self.add_incomplete_line(parent_id)
-
- try:
- branch_tag = self.bt_sha1[commit.commit_sha1]
- except KeyError:
- branch_tag = [ ]
-
-
- node = (node_pos, colour, branch_tag)
-
- self.model.append([commit, node, out_line, in_line,
- commit.message, commit.author, commit.date])
-
- return (in_line, last_colour, last_nodepos)
-
- def add_incomplete_line(self, sha1):
- try:
- self.incomplete_line[sha1].append(self.nodepos[sha1])
- except KeyError:
- self.incomplete_line[sha1] = [self.nodepos[sha1]]
-
- def draw_incomplete_line(self, sha1, node_pos, out_line, in_line, index):
- for idx, pos in enumerate(self.incomplete_line[sha1]):
- if(pos == node_pos):
- #remove the straight line and add a slash
- if ((pos, pos, self.colours[sha1]) in out_line):
- out_line.remove((pos, pos, self.colours[sha1]))
- out_line.append((pos, pos+0.5, self.colours[sha1]))
- self.incomplete_line[sha1][idx] = pos = pos+0.5
- try:
- next_commit = self.commits[index+1]
- if (next_commit.commit_sha1 == sha1 and pos != int(pos)):
- # join the line back to the node point
- # This need to be done only if we modified it
- in_line.append((pos, pos-0.5, self.colours[sha1]))
- continue;
- except IndexError:
- pass
- in_line.append((pos, pos, self.colours[sha1]))
-
-
- def _go_clicked_cb(self, widget, revid):
- """Callback for when the go button for a parent is clicked."""
- try:
- self.treeview.set_cursor(self.index[revid])
- except KeyError:
- dialog = gtk.MessageDialog(parent=None, flags=0,
- type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,
- message_format=None)
- dialog.set_markup("Revision <b>%s</b> not present in the list" % revid)
- # revid == 0 is the parent of the first commit
- if (revid != 0 ):
- dialog.format_secondary_text("Try running gitview without any options")
- dialog.run()
- dialog.destroy()
-
- self.treeview.grab_focus()
-
- def _show_clicked_cb(self, widget, commit_sha1, parent_sha1, encoding):
- """Callback for when the show button for a parent is clicked."""
- window = DiffWindow()
- window.set_diff(commit_sha1, parent_sha1, encoding)
- self.treeview.grab_focus()
-
-without_diff = 0
-if __name__ == "__main__":
-
- if (len(sys.argv) > 1 ):
- if (sys.argv[1] == "--without-diff"):
- without_diff = 1
-
- view = GitView( without_diff != 1)
- view.run(sys.argv[without_diff:])
diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt
deleted file mode 100644
index 9e12f97842..0000000000
--- a/contrib/gitview/gitview.txt
+++ /dev/null
@@ -1,57 +0,0 @@
-gitview(1)
-==========
-
-NAME
-----
-gitview - A GTK based repository browser for git
-
-SYNOPSIS
---------
-[verse]
-'gitview' [options] [args]
-
-DESCRIPTION
----------
-
-Dependencies:
-
-* Python 2.4
-* PyGTK 2.8 or later
-* PyCairo 1.0 or later
-
-OPTIONS
--------
---without-diff::
-
- If the user doesn't want to list the commit diffs in the main window.
- This may speed up the repository browsing.
-
-<args>::
-
- All the valid option for gitlink:git-rev-list[1].
-
-Key Bindings
-------------
-F4::
- To maximize the window
-
-F5::
- To reread references.
-
-F11::
- Full screen
-
-F12::
- Leave full screen
-
-EXAMPLES
---------
-
-gitview v2.6.12.. include/scsi drivers/scsi::
-
- Show as the changes since version v2.6.12 that changed any file in the
- include/scsi or drivers/scsi subdirectories
-
-gitview --since=2.weeks.ago::
-
- Show the changes during the last two weeks
diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py
index 046cb2b268..7eb1b24cc7 100755
--- a/contrib/hg-to-git/hg-to-git.py
+++ b/contrib/hg-to-git/hg-to-git.py
@@ -15,14 +15,18 @@
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.
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
"""
import os, os.path, sys
import tempfile, pickle, getopt
import re
+if sys.hexversion < 0x02030000:
+ # The behavior of the pickle module changed significantly in 2.3
+ sys.stderr.write("hg-to-git.py: requires Python 2.3 or later.\n")
+ sys.exit(1)
+
# Maps hg version -> git version
hgvers = {}
# List of children for each hg revision
@@ -38,7 +42,7 @@ hgnewcsets = 0
def usage():
- print """\
+ print("""\
%s: [OPTIONS] <hgprj>
options:
@@ -50,7 +54,7 @@ options:
required:
hgprj: name of the HG project to import (directory)
-""" % sys.argv[0]
+""" % sys.argv[0])
#------------------------------------------------------------------------------
@@ -100,22 +104,22 @@ os.chdir(hgprj)
if state:
if os.path.exists(state):
if verbose:
- print 'State does exist, reading'
+ print('State does exist, reading')
f = open(state, 'r')
hgvers = pickle.load(f)
else:
- print 'State does not exist, first run'
+ print('State does not exist, first run')
sock = os.popen('hg tip --template "{rev}"')
tip = sock.read()
if sock.close():
sys.exit(1)
if verbose:
- print 'tip is', tip
+ print('tip is', tip)
# Calculate the branches
if verbose:
- print 'analysing the branches...'
+ print('analysing the branches...')
hgchildren["0"] = ()
hgparents["0"] = (None, None)
hgbranch["0"] = "master"
@@ -150,15 +154,15 @@ for cset in range(1, int(tip) + 1):
else:
hgbranch[str(cset)] = "branch-" + str(cset)
-if not hgvers.has_key("0"):
- print 'creating repository'
+if "0" not in hgvers:
+ print('creating repository')
os.system('git init')
# loop through every hg changeset
for cset in range(int(tip) + 1):
# incremental, already seen
- if hgvers.has_key(str(cset)):
+ if str(cset) in hgvers:
continue
hgnewcsets += 1
@@ -176,27 +180,27 @@ for cset in range(int(tip) + 1):
os.write(fdcomment, csetcomment)
os.close(fdcomment)
- print '-----------------------------------------'
- print 'cset:', cset
- print 'branch:', hgbranch[str(cset)]
- print 'user:', user
- print 'date:', date
- print 'comment:', csetcomment
+ print('-----------------------------------------')
+ print('cset:', cset)
+ print('branch:', hgbranch[str(cset)])
+ print('user:', user)
+ print('date:', date)
+ print('comment:', csetcomment)
if parent:
- print 'parent:', parent
+ print('parent:', parent)
if mparent:
- print 'mparent:', mparent
+ print('mparent:', mparent)
if tag:
- print 'tag:', tag
- print '-----------------------------------------'
+ print('tag:', tag)
+ print('-----------------------------------------')
# checkout the parent if necessary
if cset != 0:
if hgbranch[str(cset)] == "branch-" + str(cset):
- print 'creating new branch', hgbranch[str(cset)]
+ print('creating new branch', hgbranch[str(cset)])
os.system('git checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent]))
else:
- print 'checking out branch', hgbranch[str(cset)]
+ print('checking out branch', hgbranch[str(cset)])
os.system('git checkout %s' % hgbranch[str(cset)])
# merge
@@ -205,7 +209,7 @@ for cset in range(int(tip) + 1):
otherbranch = hgbranch[mparent]
else:
otherbranch = hgbranch[parent]
- print 'merging', otherbranch, 'into', hgbranch[str(cset)]
+ print('merging', otherbranch, 'into', hgbranch[str(cset)])
os.system(getgitenv(user, date) + 'git merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch))
# remove everything except .git and .hg directories
@@ -220,7 +224,7 @@ for cset in range(int(tip) + 1):
os.system('git ls-files -x .hg --deleted | git update-index --remove --stdin')
# commit
- os.system(getgitenv(user, date) + 'git commit --allow-empty -a -F %s' % filecomment)
+ os.system(getgitenv(user, date) + 'git commit --allow-empty --allow-empty-message -a -F %s' % filecomment)
os.unlink(filecomment)
# tag
@@ -229,12 +233,12 @@ for cset in range(int(tip) + 1):
# delete branch if not used anymore...
if mparent and len(hgchildren[str(cset)]):
- print "Deleting unused branch:", otherbranch
+ print("Deleting unused branch:", otherbranch)
os.system('git branch -d %s' % otherbranch)
# retrieve and record the version
vvv = os.popen('git show --quiet --pretty=format:%H').read()
- print 'record', cset, '->', vvv
+ print('record', cset, '->', vvv)
hgvers[str(cset)] = vvv
if hgnewcsets >= opt_nrepack and opt_nrepack != -1:
@@ -243,7 +247,7 @@ if hgnewcsets >= opt_nrepack and opt_nrepack != -1:
# write the state for incrementals
if state:
if verbose:
- print 'Writing state'
+ print('Writing state')
f = open(state, 'w')
pickle.dump(hgvers, f)
diff --git a/contrib/hooks/multimail/README.Git b/contrib/hooks/multimail/README.Git
new file mode 100644
index 0000000000..c427efc7bd
--- /dev/null
+++ b/contrib/hooks/multimail/README.Git
@@ -0,0 +1,7 @@
+git-multimail is developed as an independent project at the following
+website:
+
+ https://github.com/git-multimail/git-multimail
+
+Please refer to that project page for information about how to report
+bugs or contribute to git-multimail.
diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email
index b2171a092e..ff565eb3d8 100755
--- a/contrib/hooks/post-receive-email
+++ b/contrib/hooks/post-receive-email
@@ -2,10 +2,19 @@
#
# Copyright (c) 2007 Andy Parkins
#
-# An example hook script to mail out commit update information. This hook
-# sends emails listing new revisions to the repository introduced by the
-# change being reported. The rule is that (for branch updates) each commit
-# will appear on one email and one email only.
+# An example hook script to mail out commit update information.
+#
+# NOTE: This script is no longer under active development. There
+# is another script, git-multimail, which is more capable and
+# configurable and is largely backwards-compatible with this script;
+# please see "contrib/hooks/multimail/". For instructions on how to
+# migrate from post-receive-email to git-multimail, please see
+# "README.migrate-from-post-receive-email" in that directory.
+#
+# This hook sends emails listing new revisions to the repository
+# introduced by the change being reported. The rule is that (for
+# branch updates) each commit will appear on one email and one email
+# only.
#
# This hook is stored in the contrib/hooks directory. Your distribution
# will have put this somewhere standard. You should make this script
@@ -13,7 +22,6 @@
# For example, on debian the hook is stored in
# /usr/share/git-core/contrib/hooks/post-receive-email:
#
-# chmod a+x post-receive-email
# cd /path/to/your/repository.git
# ln -sf /usr/share/git-core/contrib/hooks/post-receive-email hooks/post-receive
#
@@ -233,10 +241,14 @@ generate_email_header()
cat <<-EOF
To: $recipients
Subject: ${emailprefix}$projectdesc $refname_type $short_refname ${change_type}d. $describe
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset=utf-8
+ Content-Transfer-Encoding: 8bit
X-Git-Refname: $refname
X-Git-Reftype: $refname_type
X-Git-Oldrev: $oldrev
X-Git-Newrev: $newrev
+ Auto-Submitted: auto-generated
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
@@ -317,7 +329,7 @@ generate_update_branch_email()
#
# git rev-parse --not --all | grep -v $(git rev-parse $refname)
#
- # Get's us to something pretty safe (apart from the small time
+ # Gets us to something pretty safe (apart from the small time
# between refname being read, and git rev-parse running - for that,
# I give up)
#
@@ -461,7 +473,7 @@ generate_delete_branch_email()
echo " was $oldrev"
echo ""
echo $LOGBEGIN
- git show -s --pretty=oneline $oldrev
+ git diff-tree -s --always --encoding=UTF-8 --pretty=oneline $oldrev
echo $LOGEND
}
@@ -537,11 +549,11 @@ generate_atag_email()
# performed on them
if [ -n "$prevtag" ]; then
# Show changes since the previous release
- git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
+ git shortlog "$prevtag..$newrev"
else
# No previous tag, show all the changes since time
# began
- git rev-list --pretty=short $newrev | git shortlog
+ git shortlog $newrev
fi
;;
*)
@@ -561,7 +573,7 @@ generate_delete_atag_email()
echo " was $oldrev"
echo ""
echo $LOGBEGIN
- git show -s --pretty=oneline $oldrev
+ git diff-tree -s --always --encoding=UTF-8 --pretty=oneline $oldrev
echo $LOGEND
}
@@ -607,7 +619,7 @@ generate_general_email()
echo ""
if [ "$newrev_type" = "commit" ]; then
echo $LOGBEGIN
- git show --no-color --root -s --pretty=medium $newrev
+ git diff-tree -s --always --encoding=UTF-8 --pretty=medium $newrev
echo $LOGEND
else
# What can we do here? The tag marks an object that is not
@@ -626,7 +638,7 @@ generate_delete_general_email()
echo " was $oldrev"
echo ""
echo $LOGBEGIN
- git show -s --pretty=oneline $oldrev
+ git diff-tree -s --always --encoding=UTF-8 --pretty=oneline $oldrev
echo $LOGEND
}
diff --git a/contrib/hooks/pre-auto-gc-battery b/contrib/hooks/pre-auto-gc-battery
index 1f914c94aa..7ba78c4dff 100644..100755
--- a/contrib/hooks/pre-auto-gc-battery
+++ b/contrib/hooks/pre-auto-gc-battery
@@ -13,12 +13,11 @@
# For example, if the hook is stored in
# /usr/share/git-core/contrib/hooks/pre-auto-gc-battery:
#
-# chmod a+x pre-auto-gc-battery
# cd /path/to/your/repository.git
# ln -sf /usr/share/git-core/contrib/hooks/pre-auto-gc-battery \
# hooks/pre-auto-gc
-if test -x /sbin/on_ac_power && /sbin/on_ac_power
+if test -x /sbin/on_ac_power && (/sbin/on_ac_power;test $? -ne 1)
then
exit 0
elif test "$(cat /sys/class/power_supply/AC/online 2>/dev/null)" = 1
@@ -34,7 +33,7 @@ elif grep -q "AC Power \+: 1" /proc/pmu/info 2>/dev/null
then
exit 0
elif test -x /usr/bin/pmset && /usr/bin/pmset -g batt |
- grep -q "Currently drawing from 'AC Power'"
+ grep -q "drawing from 'AC Power'"
then
exit 0
fi
diff --git a/contrib/hooks/setgitperms.perl b/contrib/hooks/setgitperms.perl
index a577ad095f..2770a1b1d2 100644..100755
--- a/contrib/hooks/setgitperms.perl
+++ b/contrib/hooks/setgitperms.perl
@@ -24,7 +24,7 @@ use File::Find;
use File::Basename;
my $usage =
-"Usage: setgitperms.perl [OPTION]... <--read|--write>
+"usage: setgitperms.perl [OPTION]... <--read|--write>
This program uses a file `.gitmeta` to store/restore permissions and uid/gid
info for all files/dirs tracked by git in the repository.
diff --git a/contrib/hooks/update-paranoid b/contrib/hooks/update-paranoid
index d18b317b2f..0092d67b8a 100644..100755
--- a/contrib/hooks/update-paranoid
+++ b/contrib/hooks/update-paranoid
@@ -49,7 +49,7 @@ opcode.
Repository sections are matched on the basename of the repository
(after removing the .git suffix).
-The opcode abbrevations are:
+The opcode abbreviations are:
C: create new ref
D: delete existing ref
diff --git a/contrib/long-running-filter/example.pl b/contrib/long-running-filter/example.pl
new file mode 100755
index 0000000000..a677569ddd
--- /dev/null
+++ b/contrib/long-running-filter/example.pl
@@ -0,0 +1,132 @@
+#!/usr/bin/perl
+#
+# Example implementation for the Git filter protocol version 2
+# See Documentation/gitattributes.txt, section "Filter Protocol"
+#
+# Please note, this pass-thru filter is a minimal skeleton. No proper
+# error handling was implemented.
+#
+
+use strict;
+use warnings;
+
+my $MAX_PACKET_CONTENT_SIZE = 65516;
+
+sub packet_bin_read {
+ my $buffer;
+ my $bytes_read = read STDIN, $buffer, 4;
+ if ( $bytes_read == 0 ) {
+
+ # EOF - Git stopped talking to us!
+ exit();
+ }
+ elsif ( $bytes_read != 4 ) {
+ die "invalid packet: '$buffer'";
+ }
+ my $pkt_size = hex($buffer);
+ if ( $pkt_size == 0 ) {
+ return ( 1, "" );
+ }
+ elsif ( $pkt_size > 4 ) {
+ my $content_size = $pkt_size - 4;
+ $bytes_read = read STDIN, $buffer, $content_size;
+ if ( $bytes_read != $content_size ) {
+ die "invalid packet ($content_size bytes expected; $bytes_read bytes read)";
+ }
+ return ( 0, $buffer );
+ }
+ else {
+ die "invalid packet size: $pkt_size";
+ }
+}
+
+sub packet_txt_read {
+ my ( $res, $buf ) = packet_bin_read();
+ unless ( $buf =~ s/\n$// ) {
+ die "A non-binary line MUST be terminated by an LF.";
+ }
+ return ( $res, $buf );
+}
+
+sub packet_bin_write {
+ my $buf = shift;
+ print STDOUT sprintf( "%04x", length($buf) + 4 );
+ print STDOUT $buf;
+ STDOUT->flush();
+}
+
+sub packet_txt_write {
+ packet_bin_write( $_[0] . "\n" );
+}
+
+sub packet_flush {
+ print STDOUT sprintf( "%04x", 0 );
+ STDOUT->flush();
+}
+
+( packet_txt_read() eq ( 0, "git-filter-client" ) ) || die "bad initialize";
+( packet_txt_read() eq ( 0, "version=2" ) ) || die "bad version";
+( packet_bin_read() eq ( 1, "" ) ) || die "bad version end";
+
+packet_txt_write("git-filter-server");
+packet_txt_write("version=2");
+packet_flush();
+
+( packet_txt_read() eq ( 0, "capability=clean" ) ) || die "bad capability";
+( packet_txt_read() eq ( 0, "capability=smudge" ) ) || die "bad capability";
+( packet_bin_read() eq ( 1, "" ) ) || die "bad capability end";
+
+packet_txt_write("capability=clean");
+packet_txt_write("capability=smudge");
+packet_flush();
+
+while (1) {
+ my ($command) = packet_txt_read() =~ /^command=(.+)$/;
+ my ($pathname) = packet_txt_read() =~ /^pathname=(.+)$/;
+
+ if ( $pathname eq "" ) {
+ die "bad pathname '$pathname'";
+ }
+
+ packet_bin_read();
+
+ my $input = "";
+ {
+ binmode(STDIN);
+ my $buffer;
+ my $done = 0;
+ while ( !$done ) {
+ ( $done, $buffer ) = packet_bin_read();
+ $input .= $buffer;
+ }
+ }
+
+ my $output;
+ if ( $command eq "clean" ) {
+ ### Perform clean here ###
+ $output = $input;
+ }
+ elsif ( $command eq "smudge" ) {
+ ### Perform smudge here ###
+ $output = $input;
+ }
+ else {
+ die "bad command '$command'";
+ }
+
+ packet_txt_write("status=success");
+ packet_flush();
+ while ( length($output) > 0 ) {
+ my $packet = substr( $output, 0, $MAX_PACKET_CONTENT_SIZE );
+ packet_bin_write($packet);
+ if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) {
+ $output = substr( $output, $MAX_PACKET_CONTENT_SIZE );
+ }
+ else {
+ $output = "";
+ }
+ }
+ packet_flush(); # flush content!
+ packet_flush(); # empty list, keep "status=success" unchanged!
+
+}
diff --git a/contrib/mw-to-git/.gitignore b/contrib/mw-to-git/.gitignore
new file mode 100644
index 0000000000..ae545b013d
--- /dev/null
+++ b/contrib/mw-to-git/.gitignore
@@ -0,0 +1,2 @@
+git-remote-mediawiki
+git-mw
diff --git a/contrib/mw-to-git/.perlcriticrc b/contrib/mw-to-git/.perlcriticrc
new file mode 100644
index 0000000000..b7333267ad
--- /dev/null
+++ b/contrib/mw-to-git/.perlcriticrc
@@ -0,0 +1,28 @@
+# These 3 rules demand to add the s, m and x flag to *every* regexp. This is
+# overkill and would be harmful for readability.
+[-RegularExpressions::RequireExtendedFormatting]
+[-RegularExpressions::RequireDotMatchAnything]
+[-RegularExpressions::RequireLineBoundaryMatching]
+
+# This rule says that builtin functions should not be called with parentheses
+# e.g.: (taken from CPAN's documentation)
+# open($handle, '>', $filename); #not ok
+# open $handle, '>', $filename; #ok
+# Applying such a rule would mean modifying a huge number of lines for a
+# question of style.
+[-CodeLayout::ProhibitParensWithBuiltins]
+
+# This rule states that each system call should have its return value checked
+# The problem is that it includes the print call. Checking every print call's
+# return value would be harmful to the code readability.
+# This configuration keeps all default function but print.
+[InputOutput::RequireCheckedSyscalls]
+functions = open say close
+
+# This rule demands to add a dependency for the Readonly module. This is not
+# wished.
+[-ValuesAndExpressions::ProhibitConstantPragma]
+
+# This rule is not really useful (rather a question of style) and produces many
+# warnings among the code.
+[-ValuesAndExpressions::ProhibitNoisyQuotes]
diff --git a/contrib/mw-to-git/Git/Mediawiki.pm b/contrib/mw-to-git/Git/Mediawiki.pm
new file mode 100644
index 0000000000..917d9e2d32
--- /dev/null
+++ b/contrib/mw-to-git/Git/Mediawiki.pm
@@ -0,0 +1,101 @@
+package Git::Mediawiki;
+
+use 5.008;
+use strict;
+use POSIX;
+use Git;
+
+BEGIN {
+
+our ($VERSION, @ISA, @EXPORT, @EXPORT_OK);
+
+# Totally unstable API.
+$VERSION = '0.01';
+
+require Exporter;
+
+@ISA = qw(Exporter);
+
+@EXPORT = ();
+
+# Methods which can be called as standalone functions as well:
+@EXPORT_OK = qw(clean_filename smudge_filename connect_maybe
+ EMPTY HTTP_CODE_OK HTTP_CODE_PAGE_NOT_FOUND);
+}
+
+# Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced
+use constant SLASH_REPLACEMENT => '%2F';
+
+# Used to test for empty strings
+use constant EMPTY => q{};
+
+# HTTP codes
+use constant HTTP_CODE_OK => 200;
+use constant HTTP_CODE_PAGE_NOT_FOUND => 404;
+
+sub clean_filename {
+ my $filename = shift;
+ $filename =~ s{@{[SLASH_REPLACEMENT]}}{/}g;
+ # [, ], |, {, and } are forbidden by MediaWiki, even URL-encoded.
+ # Do a variant of URL-encoding, i.e. looks like URL-encoding,
+ # but with _ added to prevent MediaWiki from thinking this is
+ # an actual special character.
+ $filename =~ s/[\[\]\{\}\|]/sprintf("_%%_%x", ord($&))/ge;
+ # If we use the uri escape before
+ # we should unescape here, before anything
+
+ return $filename;
+}
+
+sub smudge_filename {
+ my $filename = shift;
+ $filename =~ s{/}{@{[SLASH_REPLACEMENT]}}g;
+ $filename =~ s/ /_/g;
+ # Decode forbidden characters encoded in clean_filename
+ $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf('%c', hex($1))/ge;
+ return substr($filename, 0, NAME_MAX-length('.mw'));
+}
+
+sub connect_maybe {
+ my $wiki = shift;
+ if ($wiki) {
+ return $wiki;
+ }
+
+ my $remote_name = shift;
+ my $remote_url = shift;
+ my ($wiki_login, $wiki_password, $wiki_domain);
+
+ $wiki_login = Git::config("remote.${remote_name}.mwLogin");
+ $wiki_password = Git::config("remote.${remote_name}.mwPassword");
+ $wiki_domain = Git::config("remote.${remote_name}.mwDomain");
+
+ $wiki = MediaWiki::API->new;
+ $wiki->{config}->{api_url} = "${remote_url}/api.php";
+ if ($wiki_login) {
+ my %credential = (
+ 'url' => $remote_url,
+ 'username' => $wiki_login,
+ 'password' => $wiki_password
+ );
+ Git::credential(\%credential);
+ my $request = {lgname => $credential{username},
+ lgpassword => $credential{password},
+ lgdomain => $wiki_domain};
+ if ($wiki->login($request)) {
+ Git::credential(\%credential, 'approve');
+ print {*STDERR} qq(Logged in mediawiki user "$credential{username}".\n);
+ } else {
+ print {*STDERR} qq(Failed to log in mediawiki user "$credential{username}" on ${remote_url}\n);
+ print {*STDERR} ' (error ' .
+ $wiki->{error}->{code} . ': ' .
+ $wiki->{error}->{details} . ")\n";
+ Git::credential(\%credential, 'reject');
+ exit 1;
+ }
+ }
+
+ return $wiki;
+}
+
+1; # Famous last words
diff --git a/contrib/mw-to-git/Makefile b/contrib/mw-to-git/Makefile
index 3ed728b0ef..4e603512a3 100644
--- a/contrib/mw-to-git/Makefile
+++ b/contrib/mw-to-git/Makefile
@@ -1,47 +1,58 @@
#
-# Copyright (C) 2012
-# Charles Roussel <charles.roussel@ensimag.imag.fr>
-# Simon Cathebras <simon.cathebras@ensimag.imag.fr>
-# Julien Khayat <julien.khayat@ensimag.imag.fr>
-# Guillaume Sasdy <guillaume.sasdy@ensimag.imag.fr>
-# Simon Perrat <simon.perrat@ensimag.imag.fr>
+# Copyright (C) 2013
+# Matthieu Moy <Matthieu.Moy@imag.fr>
#
-## Build git-remote-mediawiki
+# To build and test:
+#
+# make
+# bin-wrapper/git mw preview Some_page.mw
+# bin-wrapper/git clone mediawiki::http://example.com/wiki/
+#
+# To install, run Git's toplevel 'make install' then run:
+#
+# make install
--include ../../config.mak.autogen
--include ../../config.mak
+GIT_MEDIAWIKI_PM=Git/Mediawiki.pm
+SCRIPT_PERL=git-remote-mediawiki.perl
+SCRIPT_PERL+=git-mw.perl
+GIT_ROOT_DIR=../..
+HERE=contrib/mw-to-git/
-ifndef PERL_PATH
- PERL_PATH = /usr/bin/perl
-endif
-ifndef gitexecdir
- gitexecdir = $(shell git --exec-path)
-endif
+INSTALL = install
-PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
-gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
-SCRIPT = git-remote-mediawiki
+SCRIPT_PERL_FULL=$(patsubst %,$(HERE)/%,$(SCRIPT_PERL))
+INSTLIBDIR=$(shell $(MAKE) -C $(GIT_ROOT_DIR)/ \
+ -s --no-print-directory prefix=$(prefix) \
+ perllibdir=$(perllibdir) perllibdir)
+DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+INSTLIBDIR_SQ = $(subst ','\'',$(INSTLIBDIR))
-.PHONY: install help doc test clean
+all: build
-help:
- @echo 'This is the help target of the Makefile. Current configuration:'
- @echo ' gitexecdir = $(gitexecdir_SQ)'
- @echo ' PERL_PATH = $(PERL_PATH_SQ)'
- @echo 'Run "$(MAKE) install" to install $(SCRIPT) in gitexecdir'
- @echo 'Run "$(MAKE) test" to run the testsuite'
+test: all
+ $(MAKE) -C t
-install:
- sed -e '1s|#!.*/perl|#!$(PERL_PATH_SQ)|' $(SCRIPT) \
- > '$(gitexecdir_SQ)/$(SCRIPT)'
- chmod +x '$(gitexecdir)/$(SCRIPT)'
+check: perlcritic test
-doc:
- @echo 'Sorry, "make doc" is not implemented yet for $(SCRIPT)'
+install_pm:
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(INSTLIBDIR_SQ)/Git'
+ $(INSTALL) -m 644 $(GIT_MEDIAWIKI_PM) \
+ '$(DESTDIR_SQ)$(INSTLIBDIR_SQ)/$(GIT_MEDIAWIKI_PM)'
-test:
- $(MAKE) -C t/ test
+build:
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ build-perl-script
+
+install: install_pm
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ install-perl-script
clean:
- $(RM) '$(gitexecdir)/$(SCRIPT)'
- $(MAKE) -C t/ clean
+ $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
+ clean-perl-script
+
+perlcritic:
+ perlcritic -5 $(SCRIPT_PERL)
+ -perlcritic -2 $(SCRIPT_PERL)
+
+.PHONY: all test check install_pm install clean perlcritic
diff --git a/contrib/mw-to-git/bin-wrapper/git b/contrib/mw-to-git/bin-wrapper/git
new file mode 100755
index 0000000000..6663ae57e8
--- /dev/null
+++ b/contrib/mw-to-git/bin-wrapper/git
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# git executable wrapper script for Git-Mediawiki to run tests without
+# installing all the scripts and perl packages.
+
+GIT_ROOT_DIR=../../..
+GIT_EXEC_PATH=$(cd "$(dirname "$0")" && cd ${GIT_ROOT_DIR} && pwd)
+
+GITPERLLIB="$GIT_EXEC_PATH"'/contrib/mw-to-git'"${GITPERLLIB:+:$GITPERLLIB}"
+PATH="$GIT_EXEC_PATH"'/contrib/mw-to-git:'"$PATH"
+
+export GITPERLLIB PATH
+
+exec "${GIT_EXEC_PATH}/bin-wrappers/git" "$@"
diff --git a/contrib/mw-to-git/git-mw.perl b/contrib/mw-to-git/git-mw.perl
new file mode 100755
index 0000000000..eb52a53d32
--- /dev/null
+++ b/contrib/mw-to-git/git-mw.perl
@@ -0,0 +1,368 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2013
+# Benoit Person <benoit.person@ensimag.imag.fr>
+# Celestin Matte <celestin.matte@ensimag.imag.fr>
+# License: GPL v2 or later
+
+# Set of tools for git repo with a mediawiki remote.
+# Documentation & bugtracker: https://github.com/Git-Mediawiki/Git-Mediawiki
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+use URI::URL qw(url);
+use LWP::UserAgent;
+use HTML::TreeBuilder;
+
+use Git;
+use MediaWiki::API;
+use Git::Mediawiki qw(clean_filename connect_maybe
+ EMPTY HTTP_CODE_PAGE_NOT_FOUND);
+
+# By default, use UTF-8 to communicate with Git and the user
+binmode STDERR, ':encoding(UTF-8)';
+binmode STDOUT, ':encoding(UTF-8)';
+
+# Global parameters
+my $verbose = 0;
+sub v_print {
+ if ($verbose) {
+ return print {*STDERR} @_;
+ }
+ return;
+}
+
+# Preview parameters
+my $file_name = EMPTY;
+my $remote_name = EMPTY;
+my $preview_file_name = EMPTY;
+my $autoload = 0;
+sub file {
+ $file_name = shift;
+ return $file_name;
+}
+
+my %commands = (
+ 'help' =>
+ [\&help, {}, \&help],
+ 'preview' =>
+ [\&preview, {
+ '<>' => \&file,
+ 'output|o=s' => \$preview_file_name,
+ 'remote|r=s' => \$remote_name,
+ 'autoload|a' => \$autoload
+ }, \&preview_help]
+);
+
+# Search for sub-command
+my $cmd = $commands{'help'};
+for (0..@ARGV-1) {
+ if (defined $commands{$ARGV[$_]}) {
+ $cmd = $commands{$ARGV[$_]};
+ splice @ARGV, $_, 1;
+ last;
+ }
+};
+GetOptions( %{$cmd->[1]},
+ 'help|h' => \&{$cmd->[2]},
+ 'verbose|v' => \$verbose);
+
+# Launch command
+&{$cmd->[0]};
+
+############################# Preview Functions ################################
+
+sub preview_help {
+ print {*STDOUT} <<'END';
+USAGE: git mw preview [--remote|-r <remote name>] [--autoload|-a]
+ [--output|-o <output filename>] [--verbose|-v]
+ <blob> | <filename>
+
+DESCRIPTION:
+Preview is an utiliy to preview local content of a mediawiki repo as if it was
+pushed on the remote.
+
+For that, preview searches for the remote name of the current branch's
+upstream if --remote is not set. If that remote is not found or if it
+is not a mediawiki, it lists all mediawiki remotes configured and asks
+you to replay your command with the --remote option set properly.
+
+Then, it searches for a file named 'filename'. If it's not found in
+the current dir, it will assume it's a blob.
+
+The content retrieved in the file (or in the blob) will then be parsed
+by the remote mediawiki and combined with a template retrieved from
+the mediawiki.
+
+Finally, preview will save the HTML result in a file. and autoload it
+in your default web browser if the option --autoload is present.
+
+OPTIONS:
+ -r <remote name>, --remote <remote name>
+ If the remote is a mediawiki, the template and the parse engine
+ used for the preview will be those of that remote.
+ If not, a list of valid remotes will be shown.
+
+ -a, --autoload
+ Try to load the HTML output in a new tab (or new window) of your
+ default web browser.
+
+ -o <output filename>, --output <output filename>
+ Change the HTML output filename. Default filename is based on the
+ input filename with its extension replaced by '.html'.
+
+ -v, --verbose
+ Show more information on what's going on under the hood.
+END
+ exit;
+}
+
+sub preview {
+ my $wiki;
+ my ($remote_url, $wiki_page_name);
+ my ($new_content, $template);
+ my $file_content;
+
+ if ($file_name eq EMPTY) {
+ die "Missing file argument, see `git mw help`\n";
+ }
+
+ v_print("### Selecting remote\n");
+ if ($remote_name eq EMPTY) {
+ $remote_name = find_upstream_remote_name();
+ if ($remote_name) {
+ $remote_url = mediawiki_remote_url_maybe($remote_name);
+ }
+
+ if (! $remote_url) {
+ my @valid_remotes = find_mediawiki_remotes();
+
+ if ($#valid_remotes == 0) {
+ print {*STDERR} "No mediawiki remote in this repo. \n";
+ exit 1;
+ } else {
+ my $remotes_list = join("\n\t", @valid_remotes);
+ print {*STDERR} <<"MESSAGE";
+There are multiple mediawiki remotes, which of:
+ ${remotes_list}
+do you want ? Use the -r option to specify the remote.
+MESSAGE
+ }
+
+ exit 1;
+ }
+ } else {
+ if (!is_valid_remote($remote_name)) {
+ die "${remote_name} is not a remote\n";
+ }
+
+ $remote_url = mediawiki_remote_url_maybe($remote_name);
+ if (! $remote_url) {
+ die "${remote_name} is not a mediawiki remote\n";
+ }
+ }
+ v_print("selected remote:\n\tname: ${remote_name}\n\turl: ${remote_url}\n");
+
+ $wiki = connect_maybe($wiki, $remote_name, $remote_url);
+
+ # Read file content
+ if (! -e $file_name) {
+ $file_content = git_cmd_try {
+ Git::command('cat-file', 'blob', $file_name); }
+ "%s failed w/ code %d";
+
+ if ($file_name =~ /(.+):(.+)/) {
+ $file_name = $2;
+ }
+ } else {
+ open my $read_fh, "<", $file_name
+ or die "could not open ${file_name}: $!\n";
+ $file_content = do { local $/ = undef; <$read_fh> };
+ close $read_fh
+ or die "unable to close: $!\n";
+ }
+
+ v_print("### Retrieving template\n");
+ ($wiki_page_name = clean_filename($file_name)) =~ s/\.[^.]+$//;
+ $template = get_template($remote_url, $wiki_page_name);
+
+ v_print("### Parsing local content\n");
+ $new_content = $wiki->api({
+ action => 'parse',
+ text => $file_content,
+ title => $wiki_page_name
+ }, {
+ skip_encoding => 1
+ }) or die "No response from remote mediawiki\n";
+ $new_content = $new_content->{'parse'}->{'text'}->{'*'};
+
+ v_print("### Merging contents\n");
+ if ($preview_file_name eq EMPTY) {
+ ($preview_file_name = $file_name) =~ s/\.[^.]+$/.html/;
+ }
+ open(my $save_fh, '>:encoding(UTF-8)', $preview_file_name)
+ or die "Could not open: $!\n";
+ print {$save_fh} merge_contents($template, $new_content, $remote_url);
+ close($save_fh)
+ or die "Could not close: $!\n";
+
+ v_print("### Results\n");
+ if ($autoload) {
+ v_print("Launching browser w/ file: ${preview_file_name}");
+ system('git', 'web--browse', $preview_file_name);
+ } else {
+ print {*STDERR} "Preview file saved as: ${preview_file_name}\n";
+ }
+
+ exit;
+}
+
+# uses global scope variable: $remote_name
+sub merge_contents {
+ my $template = shift;
+ my $content = shift;
+ my $remote_url = shift;
+ my ($content_tree, $html_tree, $mw_content_text);
+ my $template_content_id = 'bodyContent';
+
+ $html_tree = HTML::TreeBuilder->new;
+ $html_tree->parse($template);
+
+ $content_tree = HTML::TreeBuilder->new;
+ $content_tree->parse($content);
+
+ $template_content_id = Git::config("remote.${remote_name}.mwIDcontent")
+ || $template_content_id;
+ v_print("Using '${template_content_id}' as the content ID\n");
+
+ $mw_content_text = $html_tree->look_down('id', $template_content_id);
+ if (!defined $mw_content_text) {
+ print {*STDERR} <<"CONFIG";
+Could not combine the new content with the template. You might want to
+configure `mediawiki.IDContent` in your config:
+ git config --add remote.${remote_name}.mwIDcontent <id>
+and re-run the command afterward.
+CONFIG
+ exit 1;
+ }
+ $mw_content_text->delete_content();
+ $mw_content_text->push_content($content_tree);
+
+ make_links_absolute($html_tree, $remote_url);
+
+ return $html_tree->as_HTML;
+}
+
+sub make_links_absolute {
+ my $html_tree = shift;
+ my $remote_url = shift;
+ for (@{ $html_tree->extract_links() }) {
+ my ($link, $element, $attr) = @{ $_ };
+ my $url = url($link)->canonical;
+ if ($url !~ /#/) {
+ $element->attr($attr, URI->new_abs($url, $remote_url));
+ }
+ }
+ return $html_tree;
+}
+
+sub is_valid_remote {
+ my $remote = shift;
+ my @remotes = git_cmd_try {
+ Git::command('remote') }
+ "%s failed w/ code %d";
+ my $found_remote = 0;
+ foreach my $remote (@remotes) {
+ if ($remote eq $remote) {
+ $found_remote = 1;
+ last;
+ }
+ }
+ return $found_remote;
+}
+
+sub find_mediawiki_remotes {
+ my @remotes = git_cmd_try {
+ Git::command('remote'); }
+ "%s failed w/ code %d";
+ my $remote_url;
+ my @valid_remotes = ();
+ foreach my $remote (@remotes) {
+ $remote_url = mediawiki_remote_url_maybe($remote);
+ if ($remote_url) {
+ push(@valid_remotes, $remote);
+ }
+ }
+ return @valid_remotes;
+}
+
+sub find_upstream_remote_name {
+ my $current_branch = git_cmd_try {
+ Git::command_oneline('symbolic-ref', '--short', 'HEAD') }
+ "%s failed w/ code %d";
+ return Git::config("branch.${current_branch}.remote");
+}
+
+sub mediawiki_remote_url_maybe {
+ my $remote = shift;
+
+ # Find remote url
+ my $remote_url = Git::config("remote.${remote}.url");
+ if ($remote_url =~ s/mediawiki::(.*)/$1/) {
+ return url($remote_url)->canonical;
+ }
+
+ return;
+}
+
+sub get_template {
+ my $url = shift;
+ my $page_name = shift;
+ my ($req, $res, $code, $url_after);
+
+ $req = LWP::UserAgent->new;
+ if ($verbose) {
+ $req->show_progress(1);
+ }
+
+ $res = $req->get("${url}/index.php?title=${page_name}");
+ if (!$res->is_success) {
+ $code = $res->code;
+ $url_after = $res->request()->uri(); # resolve all redirections
+ if ($code == HTTP_CODE_PAGE_NOT_FOUND) {
+ if ($verbose) {
+ print {*STDERR} <<"WARNING";
+Warning: Failed to retrieve '$page_name'. Create it on the mediawiki if you want
+all the links to work properly.
+Trying to use the mediawiki homepage as a fallback template ...
+WARNING
+ }
+
+ # LWP automatically redirects GET request
+ $res = $req->get("${url}/index.php");
+ if (!$res->is_success) {
+ $url_after = $res->request()->uri(); # resolve all redirections
+ die "Failed to get homepage @ ${url_after} w/ code ${code}\n";
+ }
+ } else {
+ die "Failed to get '${page_name}' @ ${url_after} w/ code ${code}\n";
+ }
+ }
+
+ return $res->decoded_content;
+}
+
+############################## Help Functions ##################################
+
+sub help {
+ print {*STDOUT} <<'END';
+usage: git mw <command> <args>
+
+git mw commands are:
+ help Display help information about git mw
+ preview Parse and render local file into HTML
+END
+ exit;
+}
diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki.perl
index 094129de09..a5624413dc 100755
--- a/contrib/mw-to-git/git-remote-mediawiki
+++ b/contrib/mw-to-git/git-remote-mediawiki.perl
@@ -9,26 +9,24 @@
# License: GPL v2 or later
# Gateway between Git and MediaWiki.
-# Documentation & bugtracker: https://github.com/moy/Git-Mediawiki/
+# Documentation & bugtracker: https://github.com/Git-Mediawiki/Git-Mediawiki
use strict;
use MediaWiki::API;
+use Git;
+use Git::Mediawiki qw(clean_filename smudge_filename connect_maybe
+ EMPTY HTTP_CODE_OK);
use DateTime::Format::ISO8601;
+use warnings;
# By default, use UTF-8 to communicate with Git and the user
-binmode STDERR, ":utf8";
-binmode STDOUT, ":utf8";
+binmode STDERR, ':encoding(UTF-8)';
+binmode STDOUT, ':encoding(UTF-8)';
use URI::Escape;
-use IPC::Open2;
-
-use warnings;
-
-# Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced
-use constant SLASH_REPLACEMENT => "%2F";
# It's not always possible to delete pages (may require some
-# priviledges). Deleted pages are replaced with this content.
+# privileges). Deleted pages are replaced with this content.
use constant DELETED_CONTENT => "[[Category:Deleted]]\n";
# It's not possible to create empty pages. New empty files in Git are
@@ -36,45 +34,62 @@ use constant DELETED_CONTENT => "[[Category:Deleted]]\n";
use constant EMPTY_CONTENT => "<!-- empty page -->\n";
# used to reflect file creation or deletion in diff.
-use constant NULL_SHA1 => "0000000000000000000000000000000000000000";
+use constant NULL_SHA1 => '0000000000000000000000000000000000000000';
# Used on Git's side to reflect empty edit messages on the wiki
use constant EMPTY_MESSAGE => '*Empty MediaWiki Message*';
+# Number of pages taken into account at once in submodule get_mw_page_list
+use constant SLICE_SIZE => 50;
+
+# Number of linked mediafile to get at once in get_linked_mediafiles
+# The query is split in small batches because of the MW API limit of
+# the number of links to be returned (500 links max).
+use constant BATCH_SIZE => 10;
+
+if (@ARGV != 2) {
+ exit_error_usage();
+}
+
my $remotename = $ARGV[0];
my $url = $ARGV[1];
# Accept both space-separated and multiple keys in config file.
# Spaces should be written as _ anyway because we'll use chomp.
-my @tracked_pages = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".pages"));
+my @tracked_pages = split(/[ \n]/, run_git_quoted(["config", "--get-all", "remote.${remotename}.pages"]));
chomp(@tracked_pages);
# Just like @tracked_pages, but for MediaWiki categories.
-my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".categories"));
+my @tracked_categories = split(/[ \n]/, run_git_quoted(["config", "--get-all", "remote.${remotename}.categories"]));
chomp(@tracked_categories);
+# Just like @tracked_categories, but for MediaWiki namespaces.
+my @tracked_namespaces = split(/[ \n]/, run_git_quoted(["config", "--get-all", "remote.${remotename}.namespaces"]));
+for (@tracked_namespaces) { s/_/ /g; }
+chomp(@tracked_namespaces);
+
# Import media files on pull
-my $import_media = run_git("config --get --bool remote.". $remotename .".mediaimport");
+my $import_media = run_git_quoted(["config", "--get", "--bool", "remote.${remotename}.mediaimport"]);
chomp($import_media);
-$import_media = ($import_media eq "true");
+$import_media = ($import_media eq 'true');
# Export media files on push
-my $export_media = run_git("config --get --bool remote.". $remotename .".mediaexport");
+my $export_media = run_git_quoted(["config", "--get", "--bool", "remote.${remotename}.mediaexport"]);
chomp($export_media);
-$export_media = !($export_media eq "false");
+$export_media = !($export_media eq 'false');
-my $wiki_login = run_git("config --get remote.". $remotename .".mwLogin");
-# Note: mwPassword is discourraged. Use the credential system instead.
-my $wiki_passwd = run_git("config --get remote.". $remotename .".mwPassword");
-my $wiki_domain = run_git("config --get remote.". $remotename .".mwDomain");
+my $wiki_login = run_git_quoted(["config", "--get", "remote.${remotename}.mwLogin"]);
+# Note: mwPassword is discouraged. Use the credential system instead.
+my $wiki_passwd = run_git_quoted(["config", "--get", "remote.${remotename}.mwPassword"]);
+my $wiki_domain = run_git_quoted(["config", "--get", "remote.${remotename}.mwDomain"]);
chomp($wiki_login);
chomp($wiki_passwd);
chomp($wiki_domain);
# Import only last revisions (both for clone and fetch)
-my $shallow_import = run_git("config --get --bool remote.". $remotename .".shallow");
+my $shallow_import = run_git_quoted(["config", "--get", "--bool", "remote.${remotename}.shallow"]);
chomp($shallow_import);
-$shallow_import = ($shallow_import eq "true");
+$shallow_import = ($shallow_import eq 'true');
# Fetch (clone and pull) by revisions instead of by pages. This behavior
# is more efficient when we have a wiki with lots of pages and we fetch
@@ -82,15 +97,18 @@ $shallow_import = ($shallow_import eq "true");
# Possible values:
# - by_rev: perform one query per new revision on the remote wiki
# - by_page: query each tracked page for new revision
-my $fetch_strategy = run_git("config --get remote.$remotename.fetchStrategy");
-unless ($fetch_strategy) {
- $fetch_strategy = run_git("config --get mediawiki.fetchStrategy");
+my $fetch_strategy = run_git_quoted(["config", "--get", "remote.${remotename}.fetchStrategy"]);
+if (!$fetch_strategy) {
+ $fetch_strategy = run_git_quoted(["config", "--get", "mediawiki.fetchStrategy"]);
}
chomp($fetch_strategy);
-unless ($fetch_strategy) {
- $fetch_strategy = "by_page";
+if (!$fetch_strategy) {
+ $fetch_strategy = 'by_page';
}
+# Remember the timestamp corresponding to a revision id.
+my %basetimestamps;
+
# Dumb push: don't update notes and mediawiki ref to reflect the last push.
#
# Configurable with mediawiki.dumbPush, or per-remote with
@@ -105,48 +123,25 @@ unless ($fetch_strategy) {
# will get the history with information lost). If the import is
# deterministic, this means everybody gets the same sha1 for each
# MediaWiki revision.
-my $dumb_push = run_git("config --get --bool remote.$remotename.dumbPush");
-unless ($dumb_push) {
- $dumb_push = run_git("config --get --bool mediawiki.dumbPush");
+my $dumb_push = run_git_quoted(["config", "--get", "--bool", "remote.${remotename}.dumbPush"]);
+if (!$dumb_push) {
+ $dumb_push = run_git_quoted(["config", "--get", "--bool", "mediawiki.dumbPush"]);
}
chomp($dumb_push);
-$dumb_push = ($dumb_push eq "true");
+$dumb_push = ($dumb_push eq 'true');
my $wiki_name = $url;
-$wiki_name =~ s/[^\/]*:\/\///;
+$wiki_name =~ s{[^/]*://}{};
# If URL is like http://user:password@example.com/, we clearly don't
# want the password in $wiki_name. While we're there, also remove user
# and '@' sign, to avoid author like MWUser@HTTPUser@host.com
$wiki_name =~ s/^.*@//;
# Commands parser
-my $entry;
-my @cmd;
while (<STDIN>) {
chomp;
- @cmd = split(/ /);
- if (defined($cmd[0])) {
- # Line not blank
- if ($cmd[0] eq "capabilities") {
- die("Too many arguments for capabilities") unless (!defined($cmd[1]));
- mw_capabilities();
- } elsif ($cmd[0] eq "list") {
- die("Too many arguments for list") unless (!defined($cmd[2]));
- mw_list($cmd[1]);
- } elsif ($cmd[0] eq "import") {
- die("Invalid arguments for import") unless ($cmd[1] ne "" && !defined($cmd[2]));
- mw_import($cmd[1]);
- } elsif ($cmd[0] eq "option") {
- die("Too many arguments for option") unless ($cmd[1] ne "" && $cmd[2] ne "" && !defined($cmd[3]));
- mw_option($cmd[1],$cmd[2]);
- } elsif ($cmd[0] eq "push") {
- mw_push($cmd[1]);
- } else {
- print STDERR "Unknown command. Aborting...\n";
- last;
- }
- } else {
- # blank line: we should terminate
+
+ if (!parse_command($_)) {
last;
}
@@ -156,107 +151,91 @@ while (<STDIN>) {
########################## Functions ##############################
-## credential API management (generic functions)
-
-sub credential_read {
- my %credential;
- my $reader = shift;
- my $op = shift;
- while (<$reader>) {
- my ($key, $value) = /([^=]*)=(.*)/;
- if (not defined $key) {
- die "ERROR receiving response from git credential $op:\n$_\n";
- }
- $credential{$key} = $value;
- }
- return %credential;
+## error handling
+sub exit_error_usage {
+ die "ERROR: git-remote-mediawiki module was not called with a correct number of\n" .
+ "parameters\n" .
+ "You may obtain this error because you attempted to run the git-remote-mediawiki\n" .
+ "module directly.\n" .
+ "This module can be used the following way:\n" .
+ "\tgit clone mediawiki://<address of a mediawiki>\n" .
+ "Then, use git commit, push and pull as with every normal git repository.\n";
}
-sub credential_write {
- my $credential = shift;
- my $writer = shift;
- # url overwrites other fields, so it must come first
- print $writer "url=$credential->{url}\n" if exists $credential->{url};
- while (my ($key, $value) = each(%$credential) ) {
- if (length $value && $key ne 'url') {
- print $writer "$key=$value\n";
- }
+sub parse_command {
+ my ($line) = @_;
+ my @cmd = split(/ /, $line);
+ if (!defined $cmd[0]) {
+ return 0;
}
-}
-
-sub credential_run {
- my $op = shift;
- my $credential = shift;
- my $pid = open2(my $reader, my $writer, "git credential $op");
- credential_write($credential, $writer);
- print $writer "\n";
- close($writer);
-
- if ($op eq "fill") {
- %$credential = credential_read($reader, $op);
+ if ($cmd[0] eq 'capabilities') {
+ die("Too many arguments for capabilities\n")
+ if (defined($cmd[1]));
+ mw_capabilities();
+ } elsif ($cmd[0] eq 'list') {
+ die("Too many arguments for list\n") if (defined($cmd[2]));
+ mw_list($cmd[1]);
+ } elsif ($cmd[0] eq 'import') {
+ die("Invalid argument for import\n")
+ if ($cmd[1] eq EMPTY);
+ die("Too many arguments for import\n")
+ if (defined($cmd[2]));
+ mw_import($cmd[1]);
+ } elsif ($cmd[0] eq 'option') {
+ die("Invalid arguments for option\n")
+ if ($cmd[1] eq EMPTY || $cmd[2] eq EMPTY);
+ die("Too many arguments for option\n")
+ if (defined($cmd[3]));
+ mw_option($cmd[1],$cmd[2]);
+ } elsif ($cmd[0] eq 'push') {
+ mw_push($cmd[1]);
} else {
- if (<$reader>) {
- die "ERROR while running git credential $op:\n$_";
- }
- }
- close($reader);
- waitpid($pid, 0);
- my $child_exit_status = $? >> 8;
- if ($child_exit_status != 0) {
- die "'git credential $op' failed with code $child_exit_status.";
+ print {*STDERR} "Unknown command. Aborting...\n";
+ return 0;
}
+ return 1;
}
# MediaWiki API instance, created lazily.
my $mediawiki;
-sub mw_connect_maybe {
- if ($mediawiki) {
- return;
- }
- $mediawiki = MediaWiki::API->new;
- $mediawiki->{config}->{api_url} = "$url/api.php";
- if ($wiki_login) {
- my %credential = (url => $url);
- $credential{username} = $wiki_login;
- $credential{password} = $wiki_passwd;
- credential_run("fill", \%credential);
- my $request = {lgname => $credential{username},
- lgpassword => $credential{password},
- lgdomain => $wiki_domain};
- if ($mediawiki->login($request)) {
- credential_run("approve", \%credential);
- print STDERR "Logged in mediawiki user \"$credential{username}\".\n";
- } else {
- print STDERR "Failed to log in mediawiki user \"$credential{username}\" on $url\n";
- print STDERR " (error " .
- $mediawiki->{error}->{code} . ': ' .
- $mediawiki->{error}->{details} . ")\n";
- credential_run("reject", \%credential);
- exit 1;
- }
+sub fatal_mw_error {
+ my $action = shift;
+ print STDERR "fatal: could not $action.\n";
+ print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+ if ($url =~ /^https/) {
+ print STDERR "fatal: make sure '$url/api.php' is a valid page\n";
+ print STDERR "fatal: and the SSL certificate is correct.\n";
+ } else {
+ print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
}
+ print STDERR "fatal: (error " .
+ $mediawiki->{error}->{code} . ': ' .
+ $mediawiki->{error}->{details} . ")\n";
+ exit 1;
}
## Functions for listing pages on the remote wiki
sub get_mw_tracked_pages {
my $pages = shift;
get_mw_page_list(\@tracked_pages, $pages);
+ return;
}
sub get_mw_page_list {
my $page_list = shift;
my $pages = shift;
- my @some_pages = @$page_list;
+ my @some_pages = @{$page_list};
while (@some_pages) {
- my $last = 50;
- if ($#some_pages < $last) {
- $last = $#some_pages;
+ my $last_page = SLICE_SIZE;
+ if ($#some_pages < $last_page) {
+ $last_page = $#some_pages;
}
- my @slice = @some_pages[0..$last];
+ my @slice = @some_pages[0..$last_page];
get_mw_first_pages(\@slice, $pages);
- @some_pages = @some_pages[51..$#some_pages];
+ @some_pages = @some_pages[(SLICE_SIZE + 1)..$#some_pages];
}
+ return;
}
sub get_mw_tracked_categories {
@@ -266,7 +245,7 @@ sub get_mw_tracked_categories {
# Mediawiki requires the Category
# prefix, but let's not force the user
# to specify it.
- $category = "Category:" . $category;
+ $category = "Category:${category}";
}
my $mw_pages = $mediawiki->list( {
action => 'query',
@@ -274,11 +253,38 @@ sub get_mw_tracked_categories {
cmtitle => $category,
cmlimit => 'max' } )
|| die $mediawiki->{error}->{code} . ': '
- . $mediawiki->{error}->{details};
+ . $mediawiki->{error}->{details} . "\n";
foreach my $page (@{$mw_pages}) {
$pages->{$page->{title}} = $page;
}
}
+ return;
+}
+
+sub get_mw_tracked_namespaces {
+ my $pages = shift;
+ foreach my $local_namespace (sort @tracked_namespaces) {
+ my $namespace_id;
+ if ($local_namespace eq "(Main)") {
+ $namespace_id = 0;
+ } else {
+ $namespace_id = get_mw_namespace_id($local_namespace);
+ }
+ # virtual namespaces don't support allpages
+ next if !defined($namespace_id) || $namespace_id < 0;
+ my $mw_pages = $mediawiki->list( {
+ action => 'query',
+ list => 'allpages',
+ apnamespace => $namespace_id,
+ aplimit => 'max' } )
+ || die $mediawiki->{error}->{code} . ': '
+ . $mediawiki->{error}->{details} . "\n";
+ print {*STDERR} "$#{$mw_pages} found in namespace $local_namespace ($namespace_id)\n";
+ foreach my $page (@{$mw_pages}) {
+ $pages->{$page->{title}} = $page;
+ }
+ }
+ return;
}
sub get_mw_all_pages {
@@ -290,14 +296,12 @@ sub get_mw_all_pages {
aplimit => 'max'
});
if (!defined($mw_pages)) {
- print STDERR "fatal: could not get the list of wiki pages.\n";
- print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
- print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
- exit 1;
+ fatal_mw_error("get the list of wiki pages");
}
foreach my $page (@{$mw_pages}) {
$pages->{$page->{title}} = $page;
}
+ return;
}
# queries the wiki for a set of pages. Meant to be used within a loop
@@ -316,25 +320,23 @@ sub get_mw_first_pages {
titles => $titles,
});
if (!defined($mw_pages)) {
- print STDERR "fatal: could not query the list of wiki pages.\n";
- print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
- print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
- exit 1;
+ fatal_mw_error("query the list of wiki pages");
}
while (my ($id, $page) = each(%{$mw_pages->{query}->{pages}})) {
if ($id < 0) {
- print STDERR "Warning: page $page->{title} not found on wiki\n";
+ print {*STDERR} "Warning: page $page->{title} not found on wiki\n";
} else {
$pages->{$page->{title}} = $page;
}
}
+ return;
}
# Get the list of pages to be fetched according to configuration.
sub get_mw_pages {
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
- print STDERR "Listing pages on remote wiki...\n";
+ print {*STDERR} "Listing pages on remote wiki...\n";
my %pages; # hash on page titles to avoid duplicates
my $user_defined;
@@ -348,33 +350,50 @@ sub get_mw_pages {
$user_defined = 1;
get_mw_tracked_categories(\%pages);
}
+ if (@tracked_namespaces) {
+ $user_defined = 1;
+ get_mw_tracked_namespaces(\%pages);
+ }
if (!$user_defined) {
get_mw_all_pages(\%pages);
}
if ($import_media) {
- print STDERR "Getting media files for selected pages...\n";
+ print {*STDERR} "Getting media files for selected pages...\n";
if ($user_defined) {
get_linked_mediafiles(\%pages);
} else {
get_all_mediafiles(\%pages);
}
}
- print STDERR (scalar keys %pages) . " pages found.\n";
+ print {*STDERR} (scalar keys %pages) . " pages found.\n";
return %pages;
}
-# usage: $out = run_git("command args");
-# $out = run_git("command args", "raw"); # don't interpret output as UTF-8.
-sub run_git {
+# usage: $out = run_git_quoted(["command", "args", ...]);
+# $out = run_git_quoted(["command", "args", ...], "raw"); # don't interpret output as UTF-8.
+# $out = run_git_quoted_nostderr(["command", "args", ...]); # discard stderr
+# $out = run_git_quoted_nostderr(["command", "args", ...], "raw"); # ditto but raw instead of UTF-8 as above
+sub _run_git {
my $args = shift;
- my $encoding = (shift || "encoding(UTF-8)");
- open(my $git, "-|:$encoding", "git " . $args);
- my $res = do { local $/; <$git> };
+ my $encoding = (shift || 'encoding(UTF-8)');
+ open(my $git, "-|:${encoding}", @$args)
+ or die "Unable to fork: $!\n";
+ my $res = do {
+ local $/ = undef;
+ <$git>
+ };
close($git);
return $res;
}
+sub run_git_quoted {
+ _run_git(["git", @{$_[0]}], $_[1]);
+}
+
+sub run_git_quoted_nostderr {
+ _run_git(['sh', '-c', 'git "$@" 2>/dev/null', '--', @{$_[0]}], $_[1]);
+}
sub get_all_mediafiles {
my $pages = shift;
@@ -384,27 +403,26 @@ sub get_all_mediafiles {
my $mw_pages = $mediawiki->list({
action => 'query',
list => 'allpages',
- apnamespace => get_mw_namespace_id("File"),
+ apnamespace => get_mw_namespace_id('File'),
aplimit => 'max'
});
if (!defined($mw_pages)) {
- print STDERR "fatal: could not get the list of pages for media files.\n";
- print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
- print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+ print {*STDERR} "fatal: could not get the list of pages for media files.\n";
+ print {*STDERR} "fatal: '$url' does not appear to be a mediawiki\n";
+ print {*STDERR} "fatal: make sure '$url/api.php' is a valid page.\n";
exit 1;
}
foreach my $page (@{$mw_pages}) {
$pages->{$page->{title}} = $page;
}
+ return;
}
sub get_linked_mediafiles {
my $pages = shift;
- my @titles = map $_->{title}, values(%{$pages});
+ my @titles = map { $_->{title} } values(%{$pages});
- # The query is split in small batches because of the MW API limit of
- # the number of links to be returned (500 links max).
- my $batch = 10;
+ my $batch = BATCH_SIZE;
while (@titles) {
if ($#titles < $batch) {
$batch = $#titles;
@@ -420,7 +438,7 @@ sub get_linked_mediafiles {
action => 'query',
prop => 'links|images',
titles => $mw_titles,
- plnamespace => get_mw_namespace_id("File"),
+ plnamespace => get_mw_namespace_id('File'),
pllimit => 'max'
};
my $result = $mediawiki->api($query);
@@ -428,11 +446,13 @@ sub get_linked_mediafiles {
while (my ($id, $page) = each(%{$result->{query}->{pages}})) {
my @media_titles;
if (defined($page->{links})) {
- my @link_titles = map $_->{title}, @{$page->{links}};
+ my @link_titles
+ = map { $_->{title} } @{$page->{links}};
push(@media_titles, @link_titles);
}
if (defined($page->{images})) {
- my @image_titles = map $_->{title}, @{$page->{images}};
+ my @image_titles
+ = map { $_->{title} } @{$page->{images}};
push(@media_titles, @image_titles);
}
if (@media_titles) {
@@ -442,6 +462,7 @@ sub get_linked_mediafiles {
@titles = @titles[($batch+1)..$#titles];
}
+ return;
}
sub get_mw_mediafile_for_page_revision {
@@ -455,7 +476,7 @@ sub get_mw_mediafile_for_page_revision {
my $query = {
action => 'query',
prop => 'imageinfo',
- titles => "File:" . $filename,
+ titles => "File:${filename}",
iistart => $timestamp,
iiend => $timestamp,
iiprop => 'timestamp|archivename|url',
@@ -473,53 +494,56 @@ sub get_mw_mediafile_for_page_revision {
$mediafile{timestamp} = $fileinfo->{timestamp};
# Mediawiki::API's download function doesn't support https URLs
# and can't download old versions of files.
- print STDERR "\tDownloading file $mediafile{title}, version $mediafile{timestamp}\n";
+ print {*STDERR} "\tDownloading file $mediafile{title}, version $mediafile{timestamp}\n";
$mediafile{content} = download_mw_mediafile($fileinfo->{url});
}
return %mediafile;
}
sub download_mw_mediafile {
- my $url = shift;
-
- my $response = $mediawiki->{ua}->get($url);
- if ($response->code == 200) {
- return $response->decoded_content;
+ my $download_url = shift;
+
+ my $response = $mediawiki->{ua}->get($download_url);
+ if ($response->code == HTTP_CODE_OK) {
+ # It is tempting to return
+ # $response->decoded_content({charset => "none"}), but
+ # when doing so, utf8::downgrade($content) fails with
+ # "Wide character in subroutine entry".
+ $response->decode();
+ return $response->content();
} else {
- print STDERR "Error downloading mediafile from :\n";
- print STDERR "URL: $url\n";
- print STDERR "Server response: " . $response->code . " " . $response->message . "\n";
+ print {*STDERR} "Error downloading mediafile from :\n";
+ print {*STDERR} "URL: ${download_url}\n";
+ print {*STDERR} 'Server response: ' . $response->code . q{ } . $response->message . "\n";
exit 1;
}
}
sub get_last_local_revision {
- # Get note regarding last mediawiki revision
- my $note = run_git("notes --ref=$remotename/mediawiki show refs/mediawiki/$remotename/master 2>/dev/null");
+ # Get note regarding last mediawiki revision.
+ my $note = run_git_quoted_nostderr(["notes", "--ref=${remotename}/mediawiki",
+ "show", "refs/mediawiki/${remotename}/master"]);
my @note_info = split(/ /, $note);
my $lastrevision_number;
- if (!(defined($note_info[0]) && $note_info[0] eq "mediawiki_revision:")) {
- print STDERR "No previous mediawiki revision found";
+ if (!(defined($note_info[0]) && $note_info[0] eq 'mediawiki_revision:')) {
+ print {*STDERR} 'No previous mediawiki revision found';
$lastrevision_number = 0;
} else {
# Notes are formatted : mediawiki_revision: #number
$lastrevision_number = $note_info[1];
chomp($lastrevision_number);
- print STDERR "Last local mediawiki revision found is $lastrevision_number";
+ print {*STDERR} "Last local mediawiki revision found is ${lastrevision_number}";
}
return $lastrevision_number;
}
-# Remember the timestamp corresponding to a revision id.
-my %basetimestamps;
-
# Get the last remote revision without taking in account which pages are
# tracked or not. This function makes a single request to the wiki thus
# avoid a loop onto all tracked pages. This is useful for the fetch-by-rev
# option.
sub get_last_global_remote_rev {
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
my $query = {
action => 'query',
@@ -535,14 +559,14 @@ sub get_last_global_remote_rev {
# Get the last remote revision concerning the tracked pages and the tracked
# categories.
sub get_last_remote_revision {
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
my %pages_hash = get_mw_pages();
my @pages = values(%pages_hash);
my $max_rev_num = 0;
- print STDERR "Getting last revision id on tracked pages...\n";
+ print {*STDERR} "Getting last revision id on tracked pages...\n";
foreach my $page (@pages) {
my $id = $page->{pageid};
@@ -563,7 +587,7 @@ sub get_last_remote_revision {
$max_rev_num = ($lastrev->{revid} > $max_rev_num ? $lastrev->{revid} : $max_rev_num);
}
- print STDERR "Last remote revision found is $max_rev_num.\n";
+ print {*STDERR} "Last remote revision found is $max_rev_num.\n";
return $max_rev_num;
}
@@ -574,7 +598,7 @@ sub mediawiki_clean {
# Mediawiki does not allow blank space at the end of a page and ends with a single \n.
# This function right trims a string and adds a \n at the end to follow this rule
$string =~ s/\s+$//;
- if ($string eq "" && $page_created) {
+ if ($string eq EMPTY && $page_created) {
# Creating empty pages is forbidden.
$string = EMPTY_CONTENT;
}
@@ -585,38 +609,16 @@ sub mediawiki_clean {
sub mediawiki_smudge {
my $string = shift;
if ($string eq EMPTY_CONTENT) {
- $string = "";
+ $string = EMPTY;
}
# This \n is important. This is due to mediawiki's way to handle end of files.
- return $string."\n";
-}
-
-sub mediawiki_clean_filename {
- my $filename = shift;
- $filename =~ s/@{[SLASH_REPLACEMENT]}/\//g;
- # [, ], |, {, and } are forbidden by MediaWiki, even URL-encoded.
- # Do a variant of URL-encoding, i.e. looks like URL-encoding,
- # but with _ added to prevent MediaWiki from thinking this is
- # an actual special character.
- $filename =~ s/[\[\]\{\}\|]/sprintf("_%%_%x", ord($&))/ge;
- # If we use the uri escape before
- # we should unescape here, before anything
-
- return $filename;
-}
-
-sub mediawiki_smudge_filename {
- my $filename = shift;
- $filename =~ s/\//@{[SLASH_REPLACEMENT]}/g;
- $filename =~ s/ /_/g;
- # Decode forbidden characters encoded in mediawiki_clean_filename
- $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf("%c", hex($1))/ge;
- return $filename;
+ return "${string}\n";
}
sub literal_data {
my ($content) = @_;
- print STDOUT "data ", bytes::length($content), "\n", $content;
+ print {*STDOUT} 'data ', bytes::length($content), "\n", $content;
+ return;
}
sub literal_data_raw {
@@ -624,33 +626,40 @@ sub literal_data_raw {
my ($content) = @_;
# Avoid confusion between size in bytes and in characters
utf8::downgrade($content);
- binmode STDOUT, ":raw";
- print STDOUT "data ", bytes::length($content), "\n", $content;
- binmode STDOUT, ":utf8";
+ binmode STDOUT, ':raw';
+ print {*STDOUT} 'data ', bytes::length($content), "\n", $content;
+ binmode STDOUT, ':encoding(UTF-8)';
+ return;
}
sub mw_capabilities {
# Revisions are imported to the private namespace
# refs/mediawiki/$remotename/ by the helper and fetched into
# refs/remotes/$remotename later by fetch.
- print STDOUT "refspec refs/heads/*:refs/mediawiki/$remotename/*\n";
- print STDOUT "import\n";
- print STDOUT "list\n";
- print STDOUT "push\n";
- print STDOUT "\n";
+ print {*STDOUT} "refspec refs/heads/*:refs/mediawiki/${remotename}/*\n";
+ print {*STDOUT} "import\n";
+ print {*STDOUT} "list\n";
+ print {*STDOUT} "push\n";
+ if ($dumb_push) {
+ print {*STDOUT} "no-private-update\n";
+ }
+ print {*STDOUT} "\n";
+ return;
}
sub mw_list {
# MediaWiki do not have branches, we consider one branch arbitrarily
# called master, and HEAD pointing to it.
- print STDOUT "? refs/heads/master\n";
- print STDOUT "\@refs/heads/master HEAD\n";
- print STDOUT "\n";
+ print {*STDOUT} "? refs/heads/master\n";
+ print {*STDOUT} "\@refs/heads/master HEAD\n";
+ print {*STDOUT} "\n";
+ return;
}
sub mw_option {
- print STDERR "remote-helper command 'option $_[0]' not yet implemented\n";
- print STDOUT "unsupported\n";
+ print {*STDERR} "remote-helper command 'option $_[0]' not yet implemented\n";
+ print {*STDOUT} "unsupported\n";
+ return;
}
sub fetch_mw_revisions_for_page {
@@ -666,6 +675,9 @@ sub fetch_mw_revisions_for_page {
rvstartid => $fetch_from,
rvlimit => 500,
pageids => $id,
+
+ # Let MediaWiki know that we support the latest API.
+ continue => '',
};
my $revnum = 0;
@@ -681,15 +693,22 @@ sub fetch_mw_revisions_for_page {
push(@page_revs, $page_rev_ids);
$revnum++;
}
- last unless $result->{'query-continue'};
- $query->{rvstartid} = $result->{'query-continue'}->{revisions}->{rvstartid};
+
+ if ($result->{'query-continue'}) { # For legacy APIs
+ $query->{rvstartid} = $result->{'query-continue'}->{revisions}->{rvstartid};
+ } elsif ($result->{continue}) { # For newer APIs
+ $query->{rvstartid} = $result->{continue}->{rvcontinue};
+ $query->{continue} = $result->{continue}->{continue};
+ } else {
+ last;
+ }
}
if ($shallow_import && @page_revs) {
- print STDERR " Found 1 revision (shallow import).\n";
+ print {*STDERR} " Found 1 revision (shallow import).\n";
@page_revs = sort {$b->{revid} <=> $a->{revid}} (@page_revs);
return $page_revs[0];
}
- print STDERR " Found ", $revnum, " revision(s).\n";
+ print {*STDERR} " Found ${revnum} revision(s).\n";
return @page_revs;
}
@@ -701,8 +720,7 @@ sub fetch_mw_revisions {
my $n = 1;
foreach my $page (@pages) {
my $id = $page->{pageid};
-
- print STDERR "page $n/", scalar(@pages), ": ". $page->{title} ."\n";
+ print {*STDERR} "page ${n}/", scalar(@pages), ': ', $page->{title}, "\n";
$n++;
my @page_revs = fetch_mw_revisions_for_page($page, $id, $fetch_from);
@revisions = (@page_revs, @revisions);
@@ -716,7 +734,7 @@ sub fe_escape_path {
$path =~ s/\\/\\\\/g;
$path =~ s/"/\\"/g;
$path =~ s/\n/\\n/g;
- return '"' . $path . '"';
+ return qq("${path}");
}
sub import_file_revision {
@@ -736,42 +754,43 @@ sub import_file_revision {
my $author = $commit{author};
my $date = $commit{date};
- print STDOUT "commit refs/mediawiki/$remotename/master\n";
- print STDOUT "mark :$n\n";
- print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n";
+ print {*STDOUT} "commit refs/mediawiki/${remotename}/master\n";
+ print {*STDOUT} "mark :${n}\n";
+ print {*STDOUT} "committer ${author} <${author}\@${wiki_name}> " . $date->epoch . " +0000\n";
literal_data($comment);
# If it's not a clone, we need to know where to start from
if (!$full_import && $n == 1) {
- print STDOUT "from refs/mediawiki/$remotename/master^0\n";
+ print {*STDOUT} "from refs/mediawiki/${remotename}/master^0\n";
}
if ($content ne DELETED_CONTENT) {
- print STDOUT "M 644 inline " .
- fe_escape_path($title . ".mw") . "\n";
+ print {*STDOUT} 'M 644 inline ' .
+ fe_escape_path("${title}.mw") . "\n";
literal_data($content);
if (%mediafile) {
- print STDOUT "M 644 inline "
+ print {*STDOUT} 'M 644 inline '
. fe_escape_path($mediafile{title}) . "\n";
literal_data_raw($mediafile{content});
}
- print STDOUT "\n\n";
+ print {*STDOUT} "\n\n";
} else {
- print STDOUT "D " . fe_escape_path($title . ".mw") . "\n";
+ print {*STDOUT} 'D ' . fe_escape_path("${title}.mw") . "\n";
}
# mediawiki revision number in the git note
if ($full_import && $n == 1) {
- print STDOUT "reset refs/notes/$remotename/mediawiki\n";
+ print {*STDOUT} "reset refs/notes/${remotename}/mediawiki\n";
}
- print STDOUT "commit refs/notes/$remotename/mediawiki\n";
- print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n";
- literal_data("Note added by git-mediawiki during import");
+ print {*STDOUT} "commit refs/notes/${remotename}/mediawiki\n";
+ print {*STDOUT} "committer ${author} <${author}\@${wiki_name}> " . $date->epoch . " +0000\n";
+ literal_data('Note added by git-mediawiki during import');
if (!$full_import && $n == 1) {
- print STDOUT "from refs/notes/$remotename/mediawiki^0\n";
+ print {*STDOUT} "from refs/notes/${remotename}/mediawiki^0\n";
}
- print STDOUT "N inline :$n\n";
- literal_data("mediawiki_revision: " . $commit{mw_revision});
- print STDOUT "\n\n";
+ print {*STDOUT} "N inline :${n}\n";
+ literal_data("mediawiki_revision: $commit{mw_revision}");
+ print {*STDOUT} "\n\n";
+ return;
}
# parse a sequence of
@@ -784,23 +803,28 @@ sub get_more_refs {
my @refs;
while (1) {
my $line = <STDIN>;
- if ($line =~ m/^$cmd (.*)$/) {
+ if ($line =~ /^$cmd (.*)$/) {
push(@refs, $1);
} elsif ($line eq "\n") {
return @refs;
} else {
- die("Invalid command in a '$cmd' batch: ". $_);
+ die("Invalid command in a '$cmd' batch: $_\n");
}
}
+ return;
}
sub mw_import {
# multiple import commands can follow each other.
- my @refs = (shift, get_more_refs("import"));
+ my @refs = (shift, get_more_refs('import'));
+ my $processedRefs;
foreach my $ref (@refs) {
+ next if $processedRefs->{$ref}; # skip duplicates: "import refs/heads/master" being issued twice; TODO: why?
+ $processedRefs->{$ref} = 1;
mw_import_ref($ref);
}
- print STDOUT "done\n";
+ print {*STDOUT} "done\n";
+ return;
}
sub mw_import_ref {
@@ -810,40 +834,41 @@ sub mw_import_ref {
# Since HEAD is a symbolic ref to master (by convention,
# followed by the output of the command "list" that we gave),
# we don't need to do anything in this case.
- if ($ref eq "HEAD") {
+ if ($ref eq 'HEAD') {
return;
}
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
- print STDERR "Searching revisions...\n";
+ print {*STDERR} "Searching revisions...\n";
my $last_local = get_last_local_revision();
my $fetch_from = $last_local + 1;
if ($fetch_from == 1) {
- print STDERR ", fetching from beginning.\n";
+ print {*STDERR} ", fetching from beginning.\n";
} else {
- print STDERR ", fetching from here.\n";
+ print {*STDERR} ", fetching from here.\n";
}
my $n = 0;
- if ($fetch_strategy eq "by_rev") {
- print STDERR "Fetching & writing export data by revs...\n";
+ if ($fetch_strategy eq 'by_rev') {
+ print {*STDERR} "Fetching & writing export data by revs...\n";
$n = mw_import_ref_by_revs($fetch_from);
- } elsif ($fetch_strategy eq "by_page") {
- print STDERR "Fetching & writing export data by pages...\n";
+ } elsif ($fetch_strategy eq 'by_page') {
+ print {*STDERR} "Fetching & writing export data by pages...\n";
$n = mw_import_ref_by_pages($fetch_from);
} else {
- print STDERR "fatal: invalid fetch strategy \"$fetch_strategy\".\n";
- print STDERR "Check your configuration variables remote.$remotename.fetchStrategy and mediawiki.fetchStrategy\n";
+ print {*STDERR} qq(fatal: invalid fetch strategy "${fetch_strategy}".\n);
+ print {*STDERR} "Check your configuration variables remote.${remotename}.fetchStrategy and mediawiki.fetchStrategy\n";
exit 1;
}
if ($fetch_from == 1 && $n == 0) {
- print STDERR "You appear to have cloned an empty MediaWiki.\n";
+ print {*STDERR} "You appear to have cloned an empty MediaWiki.\n";
# Something has to be done remote-helper side. If nothing is done, an error is
- # thrown saying that HEAD is refering to unknown object 0000000000000000000
+ # thrown saying that HEAD is referring to unknown object 0000000000000000000
# and the clone fails.
}
+ return;
}
sub mw_import_ref_by_pages {
@@ -855,7 +880,7 @@ sub mw_import_ref_by_pages {
my ($n, @revisions) = fetch_mw_revisions(\@pages, $fetch_from);
@revisions = sort {$a->{revid} <=> $b->{revid}} @revisions;
- my @revision_ids = map $_->{revid}, @revisions;
+ my @revision_ids = map { $_->{revid} } @revisions;
return mw_import_revids($fetch_from, \@revision_ids, \%pages_hash);
}
@@ -880,9 +905,9 @@ sub mw_import_revids {
my $n = 0;
my $n_actual = 0;
- my $last_timestamp = 0; # Placeholer in case $rev->timestamp is undefined
+ my $last_timestamp = 0; # Placeholder in case $rev->timestamp is undefined
- foreach my $pagerevid (@$revision_ids) {
+ foreach my $pagerevid (@{$revision_ids}) {
# Count page even if we skip it, since we display
# $n/$total and $total includes skipped pages.
$n++;
@@ -898,7 +923,7 @@ sub mw_import_revids {
my $result = $mediawiki->api($query);
if (!$result) {
- die "Failed to retrieve modified page for revision $pagerevid";
+ die "Failed to retrieve modified page for revision $pagerevid\n";
}
if (defined($result->{query}->{badrevids}->{$pagerevid})) {
@@ -907,7 +932,7 @@ sub mw_import_revids {
}
if (!defined($result->{query}->{pages})) {
- die "Invalid revision $pagerevid.";
+ die "Invalid revision ${pagerevid}.\n";
}
my @result_pages = values(%{$result->{query}->{pages}});
@@ -917,8 +942,8 @@ sub mw_import_revids {
my $page_title = $result_page->{title};
if (!exists($pages->{$page_title})) {
- print STDERR "$n/", scalar(@$revision_ids),
- ": Skipping revision #$rev->{revid} of $page_title\n";
+ print {*STDERR} "${n}/", scalar(@{$revision_ids}),
+ ": Skipping revision #$rev->{revid} of ${page_title}\n";
next;
}
@@ -927,7 +952,7 @@ sub mw_import_revids {
my %commit;
$commit{author} = $rev->{user} || 'Anonymous';
$commit{comment} = $rev->{comment} || EMPTY_MESSAGE;
- $commit{title} = mediawiki_smudge_filename($page_title);
+ $commit{title} = smudge_filename($page_title);
$commit{mw_revision} = $rev->{revid};
$commit{content} = mediawiki_smudge($rev->{'*'});
@@ -943,14 +968,14 @@ sub mw_import_revids {
my %mediafile;
if ($namespace) {
my $id = get_mw_namespace_id($namespace);
- if ($id && $id == get_mw_namespace_id("File")) {
+ if ($id && $id == get_mw_namespace_id('File')) {
%mediafile = get_mw_mediafile_for_page_revision($filename, $rev->{timestamp});
}
}
# If this is a revision of the media page for new version
# of a file do one common commit for both file and media page.
# Else do commit only for that page.
- print STDERR "$n/", scalar(@$revision_ids), ": Revision #$rev->{revid} of $commit{title}\n";
+ print {*STDERR} "${n}/", scalar(@{$revision_ids}), ": Revision #$rev->{revid} of $commit{title}\n";
import_file_revision(\%commit, ($fetch_from == 1), $n_actual, \%mediafile);
}
@@ -958,17 +983,17 @@ sub mw_import_revids {
}
sub error_non_fast_forward {
- my $advice = run_git("config --bool advice.pushNonFastForward");
+ my $advice = run_git_quoted(["config", "--bool", "advice.pushNonFastForward"]);
chomp($advice);
- if ($advice ne "false") {
+ if ($advice ne 'false') {
# Native git-push would show this after the summary.
# We can't ask it to display it cleanly, so print it
# ourselves before.
- print STDERR "To prevent you from losing history, non-fast-forward updates were rejected\n";
- print STDERR "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n";
- print STDERR "'Note about fast-forwards' section of 'git push --help' for details.\n";
+ print {*STDERR} "To prevent you from losing history, non-fast-forward updates were rejected\n";
+ print {*STDERR} "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n";
+ print {*STDERR} "'Note about fast-forwards' section of 'git push --help' for details.\n";
}
- print STDOUT "error $_[0] \"non-fast-forward\"\n";
+ print {*STDOUT} qq(error $_[0] "non-fast-forward"\n);
return 0;
}
@@ -979,34 +1004,34 @@ sub mw_upload_file {
my $file_deleted = shift;
my $summary = shift;
my $newrevid;
- my $path = "File:" . $complete_file_name;
+ my $path = "File:${complete_file_name}";
my %hashFiles = get_allowed_file_extensions();
if (!exists($hashFiles{$extension})) {
- print STDERR "$complete_file_name is not a permitted file on this wiki.\n";
- print STDERR "Check the configuration of file uploads in your mediawiki.\n";
+ print {*STDERR} "${complete_file_name} is not a permitted file on this wiki.\n";
+ print {*STDERR} "Check the configuration of file uploads in your mediawiki.\n";
return $newrevid;
}
- # Deleting and uploading a file requires a priviledged user
+ # Deleting and uploading a file requires a privileged user
if ($file_deleted) {
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
my $query = {
action => 'delete',
title => $path,
reason => $summary
};
if (!$mediawiki->edit($query)) {
- print STDERR "Failed to delete file on remote wiki\n";
- print STDERR "Check your permissions on the remote site. Error code:\n";
- print STDERR $mediawiki->{error}->{code} . ':' . $mediawiki->{error}->{details};
+ print {*STDERR} "Failed to delete file on remote wiki\n";
+ print {*STDERR} "Check your permissions on the remote site. Error code:\n";
+ print {*STDERR} $mediawiki->{error}->{code} . ':' . $mediawiki->{error}->{details};
exit 1;
}
} else {
# Don't let perl try to interpret file content as UTF-8 => use "raw"
- my $content = run_git("cat-file blob $new_sha1", "raw");
- if ($content ne "") {
- mw_connect_maybe();
+ my $content = run_git_quoted(["cat-file", "blob", $new_sha1], 'raw');
+ if ($content ne EMPTY) {
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
$mediawiki->{config}->{upload_url} =
- "$url/index.php/Special:Upload";
+ "${url}/index.php/Special:Upload";
$mediawiki->edit({
action => 'upload',
filename => $complete_file_name,
@@ -1018,12 +1043,12 @@ sub mw_upload_file {
}, {
skip_encoding => 1
} ) || die $mediawiki->{error}->{code} . ':'
- . $mediawiki->{error}->{details};
+ . $mediawiki->{error}->{details} . "\n";
my $last_file_page = $mediawiki->get_page({title => $path});
$newrevid = $last_file_page->{revid};
- print STDERR "Pushed file: $new_sha1 - $complete_file_name.\n";
+ print {*STDERR} "Pushed file: ${new_sha1} - ${complete_file_name}.\n";
} else {
- print STDERR "Empty file $complete_file_name not pushed.\n";
+ print {*STDERR} "Empty file ${complete_file_name} not pushed.\n";
}
}
return $newrevid;
@@ -1045,37 +1070,37 @@ sub mw_push_file {
my $newrevid;
if ($summary eq EMPTY_MESSAGE) {
- $summary = '';
+ $summary = EMPTY;
}
my $new_sha1 = $diff_info_split[3];
my $old_sha1 = $diff_info_split[2];
my $page_created = ($old_sha1 eq NULL_SHA1);
my $page_deleted = ($new_sha1 eq NULL_SHA1);
- $complete_file_name = mediawiki_clean_filename($complete_file_name);
+ $complete_file_name = clean_filename($complete_file_name);
my ($title, $extension) = $complete_file_name =~ /^(.*)\.([^\.]*)$/;
if (!defined($extension)) {
- $extension = "";
+ $extension = EMPTY;
}
- if ($extension eq "mw") {
+ if ($extension eq 'mw') {
my $ns = get_mw_namespace_id_for_page($complete_file_name);
- if ($ns && $ns == get_mw_namespace_id("File") && (!$export_media)) {
- print STDERR "Ignoring media file related page: $complete_file_name\n";
- return ($oldrevid, "ok");
+ if ($ns && $ns == get_mw_namespace_id('File') && (!$export_media)) {
+ print {*STDERR} "Ignoring media file related page: ${complete_file_name}\n";
+ return ($oldrevid, 'ok');
}
my $file_content;
if ($page_deleted) {
# Deleting a page usually requires
- # special priviledges. A common
+ # special privileges. A common
# convention is to replace the page
# with this content instead:
$file_content = DELETED_CONTENT;
} else {
- $file_content = run_git("cat-file blob $new_sha1");
+ $file_content = run_git_quoted(["cat-file", "blob", $new_sha1]);
}
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
my $result = $mediawiki->edit( {
action => 'edit',
@@ -1089,49 +1114,49 @@ sub mw_push_file {
if (!$result) {
if ($mediawiki->{error}->{code} == 3) {
# edit conflicts, considered as non-fast-forward
- print STDERR 'Warning: Error ' .
+ print {*STDERR} 'Warning: Error ' .
$mediawiki->{error}->{code} .
- ' from mediwiki: ' . $mediawiki->{error}->{details} .
+ ' from mediawiki: ' . $mediawiki->{error}->{details} .
".\n";
- return ($oldrevid, "non-fast-forward");
+ return ($oldrevid, 'non-fast-forward');
} else {
# Other errors. Shouldn't happen => just die()
die 'Fatal: Error ' .
$mediawiki->{error}->{code} .
- ' from mediwiki: ' . $mediawiki->{error}->{details};
+ ' from mediawiki: ' . $mediawiki->{error}->{details} . "\n";
}
}
$newrevid = $result->{edit}->{newrevid};
- print STDERR "Pushed file: $new_sha1 - $title\n";
+ print {*STDERR} "Pushed file: ${new_sha1} - ${title}\n";
} elsif ($export_media) {
$newrevid = mw_upload_file($complete_file_name, $new_sha1,
$extension, $page_deleted,
$summary);
} else {
- print STDERR "Ignoring media file $title\n";
+ print {*STDERR} "Ignoring media file ${title}\n";
}
$newrevid = ($newrevid or $oldrevid);
- return ($newrevid, "ok");
+ return ($newrevid, 'ok');
}
sub mw_push {
# multiple push statements can follow each other
- my @refsspecs = (shift, get_more_refs("push"));
+ my @refsspecs = (shift, get_more_refs('push'));
my $pushed;
for my $refspec (@refsspecs) {
my ($force, $local, $remote) = $refspec =~ /^(\+)?([^:]*):([^:]*)$/
- or die("Invalid refspec for push. Expected <src>:<dst> or +<src>:<dst>");
+ or die("Invalid refspec for push. Expected <src>:<dst> or +<src>:<dst>\n");
if ($force) {
- print STDERR "Warning: forced push not allowed on a MediaWiki.\n";
+ print {*STDERR} "Warning: forced push not allowed on a MediaWiki.\n";
}
- if ($local eq "") {
- print STDERR "Cannot delete remote branch on a MediaWiki\n";
- print STDOUT "error $remote cannot delete\n";
+ if ($local eq EMPTY) {
+ print {*STDERR} "Cannot delete remote branch on a MediaWiki\n";
+ print {*STDOUT} "error ${remote} cannot delete\n";
next;
}
- if ($remote ne "refs/heads/master") {
- print STDERR "Only push to the branch 'master' is supported on a MediaWiki\n";
- print STDOUT "error $remote only master allowed\n";
+ if ($remote ne 'refs/heads/master') {
+ print {*STDERR} "Only push to the branch 'master' is supported on a MediaWiki\n";
+ print {*STDOUT} "error ${remote} only master allowed\n";
next;
}
if (mw_push_revision($local, $remote)) {
@@ -1140,30 +1165,32 @@ sub mw_push {
}
# Notify Git that the push is done
- print STDOUT "\n";
+ print {*STDOUT} "\n";
if ($pushed && $dumb_push) {
- print STDERR "Just pushed some revisions to MediaWiki.\n";
- print STDERR "The pushed revisions now have to be re-imported, and your current branch\n";
- print STDERR "needs to be updated with these re-imported commits. You can do this with\n";
- print STDERR "\n";
- print STDERR " git pull --rebase\n";
- print STDERR "\n";
+ print {*STDERR} "Just pushed some revisions to MediaWiki.\n";
+ print {*STDERR} "The pushed revisions now have to be re-imported, and your current branch\n";
+ print {*STDERR} "needs to be updated with these re-imported commits. You can do this with\n";
+ print {*STDERR} "\n";
+ print {*STDERR} " git pull --rebase\n";
+ print {*STDERR} "\n";
}
+ return;
}
sub mw_push_revision {
my $local = shift;
my $remote = shift; # actually, this has to be "refs/heads/master" at this point.
my $last_local_revid = get_last_local_revision();
- print STDERR ".\n"; # Finish sentence started by get_last_local_revision()
+ print {*STDERR} ".\n"; # Finish sentence started by get_last_local_revision()
my $last_remote_revid = get_last_remote_revision();
my $mw_revision = $last_remote_revid;
# Get sha1 of commit pointed by local HEAD
- my $HEAD_sha1 = run_git("rev-parse $local 2>/dev/null"); chomp($HEAD_sha1);
+ my $HEAD_sha1 = run_git_quoted_nostderr(["rev-parse", $local]);
+ chomp($HEAD_sha1);
# Get sha1 of commit pointed by remotes/$remotename/master
- my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/$remotename/master 2>/dev/null");
+ my $remoteorigin_sha1 = run_git_quoted_nostderr(["rev-parse", "refs/remotes/${remotename}/master"]);
chomp($remoteorigin_sha1);
if ($last_local_revid > 0 &&
@@ -1182,22 +1209,22 @@ sub mw_push_revision {
if ($last_local_revid > 0) {
my $parsed_sha1 = $remoteorigin_sha1;
# Find a path from last MediaWiki commit to pushed commit
- print STDERR "Computing path from local to remote ...\n";
- my @local_ancestry = split(/\n/, run_git("rev-list --boundary --parents $local ^$parsed_sha1"));
+ print {*STDERR} "Computing path from local to remote ...\n";
+ my @local_ancestry = split(/\n/, run_git_quoted(["rev-list", "--boundary", "--parents", $local, "^${parsed_sha1}"]));
my %local_ancestry;
foreach my $line (@local_ancestry) {
- if (my ($child, $parents) = $line =~ m/^-?([a-f0-9]+) ([a-f0-9 ]+)/) {
- foreach my $parent (split(' ', $parents)) {
+ if (my ($child, $parents) = $line =~ /^-?([a-f0-9]+) ([a-f0-9 ]+)/) {
+ foreach my $parent (split(/ /, $parents)) {
$local_ancestry{$parent} = $child;
}
- } elsif (!$line =~ m/^([a-f0-9]+)/) {
- die "Unexpected output from git rev-list: $line";
+ } elsif (!$line =~ /^([a-f0-9]+)/) {
+ die "Unexpected output from git rev-list: ${line}\n";
}
}
while ($parsed_sha1 ne $HEAD_sha1) {
my $child = $local_ancestry{$parsed_sha1};
if (!$child) {
- printf STDERR "Cannot find a path in history from remote commit to last commit\n";
+ print {*STDERR} "Cannot find a path in history from remote commit to last commit\n";
return error_non_fast_forward($remote);
}
push(@commit_pairs, [$parsed_sha1, $child]);
@@ -1206,12 +1233,12 @@ sub mw_push_revision {
} else {
# No remote mediawiki revision. Export the whole
# history (linearized with --first-parent)
- print STDERR "Warning: no common ancestor, pushing complete history\n";
- my $history = run_git("rev-list --first-parent --children $local");
- my @history = split('\n', $history);
+ print {*STDERR} "Warning: no common ancestor, pushing complete history\n";
+ my $history = run_git_quoted(["rev-list", "--first-parent", "--children", $local]);
+ my @history = split(/\n/, $history);
@history = @history[1..$#history];
foreach my $line (reverse @history) {
- my @commit_info_split = split(/ |\n/, $line);
+ my @commit_info_split = split(/[ \n]/, $line);
push(@commit_pairs, \@commit_info_split);
}
}
@@ -1219,12 +1246,12 @@ sub mw_push_revision {
foreach my $commit_info_split (@commit_pairs) {
my $sha1_child = @{$commit_info_split}[0];
my $sha1_commit = @{$commit_info_split}[1];
- my $diff_infos = run_git("diff-tree -r --raw -z $sha1_child $sha1_commit");
+ my $diff_infos = run_git_quoted(["diff-tree", "-r", "--raw", "-z", $sha1_child, $sha1_commit]);
# TODO: we could detect rename, and encode them with a #redirect on the wiki.
# TODO: for now, it's just a delete+add
my @diff_info_list = split(/\0/, $diff_infos);
# Keep the subject line of the commit message as mediawiki comment for the revision
- my $commit_msg = run_git("log --no-walk --format=\"%s\" $sha1_commit");
+ my $commit_msg = run_git_quoted(["log", "--no-walk", '--format="%s"', $sha1_commit]);
chomp($commit_msg);
# Push every blob
while (@diff_info_list) {
@@ -1236,7 +1263,7 @@ sub mw_push_revision {
my $info = shift(@diff_info_list);
my $file = shift(@diff_info_list);
($mw_revision, $status) = mw_push_file($info, $file, $commit_msg, $mw_revision);
- if ($status eq "non-fast-forward") {
+ if ($status eq 'non-fast-forward') {
# we may already have sent part of the
# commit to MediaWiki, but it's too
# late to cancel it. Stop the push in
@@ -1244,22 +1271,24 @@ sub mw_push_revision {
# accurate error message.
return error_non_fast_forward($remote);
}
- if ($status ne "ok") {
- die("Unknown error from mw_push_file()");
+ if ($status ne 'ok') {
+ die("Unknown error from mw_push_file()\n");
}
}
- unless ($dumb_push) {
- run_git("notes --ref=$remotename/mediawiki add -f -m \"mediawiki_revision: $mw_revision\" $sha1_commit");
- run_git("update-ref -m \"Git-MediaWiki push\" refs/mediawiki/$remotename/master $sha1_commit $sha1_child");
+ if (!$dumb_push) {
+ run_git_quoted(["notes", "--ref=${remotename}/mediawiki",
+ "add", "-f", "-m",
+ "mediawiki_revision: ${mw_revision}",
+ $sha1_commit]);
}
}
- print STDOUT "ok $remote\n";
+ print {*STDOUT} "ok ${remote}\n";
return 1;
}
sub get_allowed_file_extensions {
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
my $query = {
action => 'query',
@@ -1267,8 +1296,8 @@ sub get_allowed_file_extensions {
siprop => 'fileextensions'
};
my $result = $mediawiki->api($query);
- my @file_extensions= map $_->{ext},@{$result->{query}->{fileextensions}};
- my %hashFile = map {$_ => 1}@file_extensions;
+ my @file_extensions = map { $_->{ext}} @{$result->{query}->{fileextensions}};
+ my %hashFile = map { $_ => 1 } @file_extensions;
return %hashFile;
}
@@ -1283,15 +1312,15 @@ my %cached_mw_namespace_id;
# Return MediaWiki id for a canonical namespace name.
# Ex.: "File", "Project".
sub get_mw_namespace_id {
- mw_connect_maybe();
+ $mediawiki = connect_maybe($mediawiki, $remotename, $url);
my $name = shift;
if (!exists $namespace_id{$name}) {
# Look at configuration file, if the record for that namespace is
# already cached. Namespaces are stored in form:
# "Name_of_namespace:Id_namespace", ex.: "File:6".
- my @temp = split(/[\n]/, run_git("config --get-all remote."
- . $remotename .".namespaceCache"));
+ my @temp = split(/\n/,
+ run_git_quoted(["config", "--get-all", "remote.${remotename}.namespaceCache"]));
chomp(@temp);
foreach my $ns (@temp) {
my ($n, $id) = split(/:/, $ns);
@@ -1305,7 +1334,7 @@ sub get_mw_namespace_id {
}
if (!exists $namespace_id{$name}) {
- print STDERR "Namespace $name not found in cache, querying the wiki ...\n";
+ print {*STDERR} "Namespace ${name} not found in cache, querying the wiki ...\n";
# NS not found => get namespace id from MW and store it in
# configuration file.
my $query = {
@@ -1329,8 +1358,9 @@ sub get_mw_namespace_id {
my $ns = $namespace_id{$name};
my $id;
- unless (defined $ns) {
- print STDERR "No such namespace $name on MediaWiki.\n";
+ if (!defined $ns) {
+ my @namespaces = map { s/ /_/g; $_; } sort keys %namespace_id;
+ print {*STDERR} "No such namespace ${name} on MediaWiki, known namespaces: @namespaces\n";
$ns = {is_namespace => 0};
$namespace_id{$name} = $ns;
}
@@ -1342,17 +1372,17 @@ sub get_mw_namespace_id {
# Store "notANameSpace" as special value for inexisting namespaces
my $store_id = ($id || 'notANameSpace');
- # Store explicitely requested namespaces on disk
+ # Store explicitly requested namespaces on disk
if (!exists $cached_mw_namespace_id{$name}) {
- run_git("config --add remote.". $remotename
- .".namespaceCache \"". $name .":". $store_id ."\"");
+ run_git_quoted(["config", "--add", "remote.${remotename}.namespaceCache", "${name}:${store_id}"]);
$cached_mw_namespace_id{$name} = 1;
}
return $id;
}
sub get_mw_namespace_id_for_page {
- if (my ($namespace) = $_[0] =~ /^([^:]*):/) {
+ my $namespace = shift;
+ if ($namespace =~ /^([^:]*):/) {
return get_mw_namespace_id($namespace);
} else {
return;
diff --git a/contrib/mw-to-git/git-remote-mediawiki.txt b/contrib/mw-to-git/git-remote-mediawiki.txt
index 4d211f5b81..5da825f61e 100644
--- a/contrib/mw-to-git/git-remote-mediawiki.txt
+++ b/contrib/mw-to-git/git-remote-mediawiki.txt
@@ -4,4 +4,4 @@ objects from mediawiki just as one would do with a classic git
repository thanks to remote-helpers.
For more information, visit the wiki at
-https://github.com/Bibzball/Git-Mediawiki/wiki
+https://github.com/Git-Mediawiki/Git-Mediawiki
diff --git a/contrib/mw-to-git/t/.gitignore b/contrib/mw-to-git/t/.gitignore
index a7a40b4964..2b8dc30c6d 100644
--- a/contrib/mw-to-git/t/.gitignore
+++ b/contrib/mw-to-git/t/.gitignore
@@ -1,4 +1,4 @@
WEB/
-wiki/
+mediawiki/
trash directory.t*/
test-results/
diff --git a/contrib/mw-to-git/t/README b/contrib/mw-to-git/t/README
index 96e97390cf..72c4889db7 100644
--- a/contrib/mw-to-git/t/README
+++ b/contrib/mw-to-git/t/README
@@ -14,18 +14,18 @@ install the following packages (Debian/Ubuntu names, may need to be
adapted for another distribution):
* lighttpd
-* php5
-* php5-cgi
-* php5-cli
-* php5-curl
-* php5-sqlite
+* php
+* php-cgi
+* php-cli
+* php-curl
+* php-sqlite
Principles and Technical Choices
--------------------------------
The test environment makes it easy to install and manipulate one or
several MediaWiki instances. To allow developers to run the testsuite
-easily, the environment does not require root priviledge (except to
+easily, the environment does not require root privilege (except to
install the required packages if needed). It starts a webserver
instance on the user's account (using lighttpd greatly helps for
that), and does not need a separate database daemon (thanks to the use
@@ -81,7 +81,7 @@ parameters, please refer to the `test-gitmw-lib.sh` and
** `test_check_wiki_precond`:
Check if the tests must be skipped or not. Please use this function
-at the beggining of each new test file.
+at the beginning of each new test file.
** `wiki_getpage`:
Fetch a given page from the wiki and puts its content in the
@@ -113,7 +113,7 @@ Tests if a given page exists on the wiki.
** `wiki_reset`:
Reset the wiki, i.e. flush the database. Use this function at the
-begining of each new test, except if the test re-uses the same wiki
+beginning of each new test, except if the test re-uses the same wiki
(and history) as the previous test.
How to write a new test
@@ -121,4 +121,4 @@ How to write a new test
Please, follow the standards given by git. See git/t/README.
New file should be named as t936[0-9]-*.sh.
-Be sure to reset your wiki regulary with the function `wiki_reset`.
+Be sure to reset your wiki regularly with the function `wiki_reset`.
diff --git a/contrib/mw-to-git/t/install-wiki.sh b/contrib/mw-to-git/t/install-wiki.sh
index c6d6fa3aef..c215213c4b 100755
--- a/contrib/mw-to-git/t/install-wiki.sh
+++ b/contrib/mw-to-git/t/install-wiki.sh
@@ -15,11 +15,13 @@ fi
. "$WIKI_TEST_DIR"/test-gitmw-lib.sh
usage () {
- echo "Usage: "
+ echo "usage: "
echo " ./install-wiki.sh <install | delete | --help>"
echo " install | -i : Install a wiki on your computer."
echo " delete | -d : Delete the wiki and all its pages and "
echo " content."
+ echo " start | -s : Start the previously configured lighttpd daemon"
+ echo " stop : Stop lighttpd daemon."
}
@@ -33,6 +35,14 @@ case "$1" in
wiki_delete
exit 0
;;
+ "start" | "-s")
+ start_lighttpd
+ exit
+ ;;
+ "stop")
+ stop_lighttpd
+ exit
+ ;;
"--help" | "-h")
usage
exit 0
diff --git a/contrib/mw-to-git/t/install-wiki/.gitignore b/contrib/mw-to-git/t/install-wiki/.gitignore
deleted file mode 100644
index b5a2a4408c..0000000000
--- a/contrib/mw-to-git/t/install-wiki/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-wikidb.sqlite
diff --git a/contrib/mw-to-git/t/install-wiki/LocalSettings.php b/contrib/mw-to-git/t/install-wiki/LocalSettings.php
deleted file mode 100644
index 29f125116b..0000000000
--- a/contrib/mw-to-git/t/install-wiki/LocalSettings.php
+++ /dev/null
@@ -1,129 +0,0 @@
-<?php
-# This file was automatically generated by the MediaWiki 1.19.0
-# installer. If you make manual changes, please keep track in case you
-# need to recreate them later.
-#
-# See includes/DefaultSettings.php for all configurable settings
-# and their default values, but don't forget to make changes in _this_
-# file, not there.
-#
-# Further documentation for configuration settings may be found at:
-# http://www.mediawiki.org/wiki/Manual:Configuration_settings
-
-# Protect against web entry
-if ( !defined( 'MEDIAWIKI' ) ) {
- exit;
-}
-
-## Uncomment this to disable output compression
-# $wgDisableOutputCompression = true;
-
-$wgSitename = "Git-MediaWiki-Test";
-$wgMetaNamespace = "Git-MediaWiki-Test";
-
-## The URL base path to the directory containing the wiki;
-## defaults for all runtime URL paths are based off of this.
-## For more information on customizing the URLs please see:
-## http://www.mediawiki.org/wiki/Manual:Short_URL
-$wgScriptPath = "@WG_SCRIPT_PATH@";
-$wgScriptExtension = ".php";
-
-## The protocol and server name to use in fully-qualified URLs
-$wgServer = "@WG_SERVER@";
-
-## The relative URL path to the skins directory
-$wgStylePath = "$wgScriptPath/skins";
-
-## The relative URL path to the logo. Make sure you change this from the default,
-## or else you'll overwrite your logo when you upgrade!
-$wgLogo = "$wgStylePath/common/images/wiki.png";
-
-## UPO means: this is also a user preference option
-
-$wgEnableEmail = true;
-$wgEnableUserEmail = true; # UPO
-
-$wgEmergencyContact = "apache@localhost";
-$wgPasswordSender = "apache@localhost";
-
-$wgEnotifUserTalk = false; # UPO
-$wgEnotifWatchlist = false; # UPO
-$wgEmailAuthentication = true;
-
-## Database settings
-$wgDBtype = "sqlite";
-$wgDBserver = "";
-$wgDBname = "@WG_SQLITE_DATAFILE@";
-$wgDBuser = "";
-$wgDBpassword = "";
-
-# SQLite-specific settings
-$wgSQLiteDataDir = "@WG_SQLITE_DATADIR@";
-
-
-## Shared memory settings
-$wgMainCacheType = CACHE_NONE;
-$wgMemCachedServers = array();
-
-## To enable image uploads, make sure the 'images' directory
-## is writable, then set this to true:
-$wgEnableUploads = true;
-$wgUseImageMagick = true;
-$wgImageMagickConvertCommand ="@CONVERT@";
-$wgFileExtensions[] = 'txt';
-
-# InstantCommons allows wiki to use images from http://commons.wikimedia.org
-$wgUseInstantCommons = false;
-
-## If you use ImageMagick (or any other shell command) on a
-## Linux server, this will need to be set to the name of an
-## available UTF-8 locale
-$wgShellLocale = "en_US.utf8";
-
-## If you want to use image uploads under safe mode,
-## create the directories images/archive, images/thumb and
-## images/temp, and make them all writable. Then uncomment
-## this, if it's not already uncommented:
-#$wgHashedUploadDirectory = false;
-
-## Set $wgCacheDirectory to a writable directory on the web server
-## to make your wiki go slightly faster. The directory should not
-## be publically accessible from the web.
-#$wgCacheDirectory = "$IP/cache";
-
-# Site language code, should be one of the list in ./languages/Names.php
-$wgLanguageCode = "en";
-
-$wgSecretKey = "1c912bfe3519fb70f5dc523ecc698111cd43d81a11c585b3eefb28f29c2699b7";
-#$wgSecretKey = "@SECRETKEY@";
-
-
-# Site upgrade key. Must be set to a string (default provided) to turn on the
-# web installer while LocalSettings.php is in place
-$wgUpgradeKey = "ddae7dc87cd0a645";
-
-## Default skin: you can change the default skin. Use the internal symbolic
-## names, ie 'standard', 'nostalgia', 'cologneblue', 'monobook', 'vector':
-$wgDefaultSkin = "vector";
-
-## For attaching licensing metadata to pages, and displaying an
-## appropriate copyright notice / icon. GNU Free Documentation
-## License and Creative Commons licenses are supported so far.
-$wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
-$wgRightsUrl = "";
-$wgRightsText = "";
-$wgRightsIcon = "";
-
-# Path to the GNU diff3 utility. Used for conflict resolution.
-$wgDiff3 = "/usr/bin/diff3";
-
-# Query string length limit for ResourceLoader. You should only set this if
-# your web server has a query string length limit (then set it to that limit),
-# or if you have suhosin.get.max_value_length set in php.ini (then set it to
-# that value)
-$wgResourceLoaderMaxQueryLength = -1;
-
-
-
-# End of automatically generated settings.
-# Add more configuration options below.
diff --git a/contrib/mw-to-git/t/install-wiki/db_install.php b/contrib/mw-to-git/t/install-wiki/db_install.php
deleted file mode 100644
index 0f3f4e018a..0000000000
--- a/contrib/mw-to-git/t/install-wiki/db_install.php
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-/**
- * This script generates a SQLite database for a MediaWiki version 1.19.0
- * You must specify the login of the admin (argument 1) and its
- * password (argument 2) and the folder where the database file
- * is located (absolute path in argument 3).
- * It is used by the script install-wiki.sh in order to make easy the
- * installation of a MediaWiki.
- *
- * In order to generate a SQLite database file, MediaWiki ask the user
- * to submit some forms in its web browser. This script simulates this
- * behavior though the functions <get> and <submit>
- *
- */
-$argc = $_SERVER['argc'];
-$argv = $_SERVER['argv'];
-
-$login = $argv[2];
-$pass = $argv[3];
-$tmp = $argv[4];
-$port = $argv[5];
-
-$url = 'http://localhost:'.$port.'/wiki/mw-config/index.php';
-$db_dir = urlencode($tmp);
-$tmp_cookie = tempnam($tmp, "COOKIE_");
-/*
- * Fetchs a page with cURL.
- */
-function get($page_name = "") {
- $curl = curl_init();
- $page_name_add = "";
- if ($page_name != "") {
- $page_name_add = '?page='.$page_name;
- }
- $url = $GLOBALS['url'].$page_name_add;
- $tmp_cookie = $GLOBALS['tmp_cookie'];
- curl_setopt($curl, CURLOPT_COOKIEJAR, $tmp_cookie);
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($curl, CURLOPT_COOKIEFILE, $tmp_cookie);
- curl_setopt($curl, CURLOPT_HEADER, true);
- curl_setopt($curl, CURLOPT_URL, $url);
-
- $page = curl_exec($curl);
- if (!$page) {
- die("Could not get page: $url\n");
- }
- curl_close($curl);
- return $page;
-}
-
-/*
- * Submits a form with cURL.
- */
-function submit($page_name, $option = "") {
- $curl = curl_init();
- $datapost = 'submit-continue=Continue+%E2%86%92';
- if ($option != "") {
- $datapost = $option.'&'.$datapost;
- }
- $url = $GLOBALS['url'].'?page='.$page_name;
- $tmp_cookie = $GLOBALS['tmp_cookie'];
- curl_setopt($curl, CURLOPT_URL, $url);
- curl_setopt($curl, CURLOPT_POST, true);
- curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($curl, CURLOPT_POSTFIELDS, $datapost);
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($curl, CURLOPT_COOKIEJAR, $tmp_cookie);
- curl_setopt($curl, CURLOPT_COOKIEFILE, $tmp_cookie);
-
- $page = curl_exec($curl);
- if (!$page) {
- die("Could not get page: $url\n");
- }
- curl_close($curl);
- return "$page";
-}
-
-/*
- * Here starts this script: simulates the behavior of the user
- * submitting forms to generates the database file.
- * Note this simulation was made for the MediaWiki version 1.19.0,
- * we can't assume it works with other versions.
- *
- */
-
-$page = get();
-if (!preg_match('/input type="hidden" value="([0-9]+)" name="LanguageRequestTime"/',
- $page, $matches)) {
- echo "Unexpected content for page downloaded:\n";
- echo "$page";
- die;
-};
-$timestamp = $matches[1];
-$language = "LanguageRequestTime=$timestamp&uselang=en&ContLang=en";
-$page = submit('Language', $language);
-
-submit('Welcome');
-
-$db_config = 'DBType=sqlite';
-$db_config = $db_config.'&sqlite_wgSQLiteDataDir='.$db_dir;
-$db_config = $db_config.'&sqlite_wgDBname='.$argv[1];
-submit('DBConnect', $db_config);
-
-$wiki_config = 'config_wgSitename=TEST';
-$wiki_config = $wiki_config.'&config__NamespaceType=site-name';
-$wiki_config = $wiki_config.'&config_wgMetaNamespace=MyWiki';
-$wiki_config = $wiki_config.'&config__AdminName='.$login;
-
-$wiki_config = $wiki_config.'&config__AdminPassword='.$pass;
-$wiki_config = $wiki_config.'&config__AdminPassword2='.$pass;
-
-$wiki_config = $wiki_config.'&wiki__configEmail=email%40email.org';
-$wiki_config = $wiki_config.'&config__SkipOptional=skip';
-submit('Name', $wiki_config);
-submit('Install');
-submit('Install');
-
-unlink($tmp_cookie);
-?>
diff --git a/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh b/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh
index 811a90c9ae..f08890d9e7 100755
--- a/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh
+++ b/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh
@@ -28,7 +28,7 @@ test_expect_success 'Git clone creates the expected git log with one file' '
git log --format=%s HEAD^..HEAD >log.tmp
) &&
echo "this must be the same" >msg.tmp &&
- diff -b mw_dir_1/log.tmp msg.tmp
+ test_cmp msg.tmp mw_dir_1/log.tmp
'
@@ -50,8 +50,8 @@ test_expect_success 'Git clone creates the expected git log with multiple files'
echo "this must be the same" >>msgDaddy.tmp &&
echo "identical too" >msgDj.tmp &&
echo "identical" >>msgDj.tmp &&
- diff -b mw_dir_2/logDaddy.tmp msgDaddy.tmp &&
- diff -b mw_dir_2/logDj.tmp msgDj.tmp
+ test_cmp msgDaddy.tmp mw_dir_2/logDaddy.tmp &&
+ test_cmp msgDj.tmp mw_dir_2/logDj.tmp
'
@@ -86,7 +86,7 @@ test_expect_success 'Git clone works with page added' '
test_expect_success 'Git clone works with an edited page ' '
wiki_reset &&
wiki_editpage foo "this page will be edited" \
- false -s "first edition of page foo"&&
+ false -s "first edition of page foo" &&
wiki_editpage foo "this page has been edited and must be on the clone " true &&
git clone mediawiki::'"$WIKI_URL"' mw_dir_6 &&
test_path_is_file mw_dir_6/Foo.mw &&
@@ -135,7 +135,7 @@ test_expect_success 'Git clone works with one specific page cloned ' '
cd mw_dir_8 &&
echo "this log must stay" >msg.tmp &&
git log --format=%s >log.tmp &&
- diff -b msg.tmp log.tmp
+ test_cmp msg.tmp log.tmp
) &&
wiki_check_content mw_dir_8/Namnam.mw Namnam
'
@@ -143,7 +143,7 @@ test_expect_success 'Git clone works with one specific page cloned ' '
test_expect_success 'Git clone works with multiple specific page cloned ' '
wiki_reset &&
wiki_editpage foo "I will be there" false &&
- wiki_editpage bar "I will not disapear" false &&
+ wiki_editpage bar "I will not disappear" false &&
wiki_editpage namnam "I be erased" false &&
wiki_editpage nyancat "nyan nyan nyan you will not erase me" false &&
wiki_delete_page namnam &&
@@ -191,10 +191,10 @@ test_expect_success 'Git clone works with the shallow option' '
test_path_is_file mw_dir_11/Main_Page.mw &&
(
cd mw_dir_11 &&
- test `git log --oneline Nyan.mw | wc -l` -eq 1 &&
- test `git log --oneline Foo.mw | wc -l` -eq 1 &&
- test `git log --oneline Bar.mw | wc -l` -eq 1 &&
- test `git log --oneline Main_Page.mw | wc -l ` -eq 1
+ test $(git log --oneline Nyan.mw | wc -l) -eq 1 &&
+ test $(git log --oneline Foo.mw | wc -l) -eq 1 &&
+ test $(git log --oneline Bar.mw | wc -l) -eq 1 &&
+ test $(git log --oneline Main_Page.mw | wc -l ) -eq 1
) &&
wiki_check_content mw_dir_11/Nyan.mw Nyan &&
wiki_check_content mw_dir_11/Foo.mw Foo &&
@@ -218,9 +218,9 @@ test_expect_success 'Git clone works with the shallow option with a delete page'
test_path_is_file mw_dir_12/Main_Page.mw &&
(
cd mw_dir_12 &&
- test `git log --oneline Nyan.mw | wc -l` -eq 1 &&
- test `git log --oneline Bar.mw | wc -l` -eq 1 &&
- test `git log --oneline Main_Page.mw | wc -l ` -eq 1
+ test $(git log --oneline Nyan.mw | wc -l) -eq 1 &&
+ test $(git log --oneline Bar.mw | wc -l) -eq 1 &&
+ test $(git log --oneline Main_Page.mw | wc -l ) -eq 1
) &&
wiki_check_content mw_dir_12/Nyan.mw Nyan &&
wiki_check_content mw_dir_12/Bar.mw Bar &&
@@ -247,7 +247,7 @@ test_expect_success 'Test of resistance to modification of category on wiki for
wiki_editpage Notconsidered "this page will not appear on local" false &&
wiki_editpage Othercategory "this page will not appear on local" false -c=Cattwo &&
wiki_editpage Tobeedited "this page have been modified" true -c=Catone &&
- wiki_delete_page Tobedeleted
+ wiki_delete_page Tobedeleted &&
git clone -c remote.origin.categories="Catone" \
mediawiki::'"$WIKI_URL"' mw_dir_14 &&
wiki_getallpage ref_page_14 Catone &&
diff --git a/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh b/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh
index b6405ce262..526d92850f 100755
--- a/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh
+++ b/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh
@@ -70,8 +70,8 @@ test_expect_success 'The shallow option works with accents' '
test_path_is_file mw_dir_4/Main_Page.mw &&
(
cd mw_dir_4 &&
- test `git log --oneline Néoà.mw | wc -l` -eq 1 &&
- test `git log --oneline Main_Page.mw | wc -l ` -eq 1
+ test $(git log --oneline Néoà.mw | wc -l) -eq 1 &&
+ test $(git log --oneline Main_Page.mw | wc -l ) -eq 1
) &&
wiki_check_content mw_dir_4/Néoà.mw Néoà &&
wiki_check_content mw_dir_4/Main_Page.mw Main_Page
@@ -139,7 +139,7 @@ test_expect_success 'character $ in file name (git -> mw) ' '
'
-test_expect_failure 'capital at the begining of file names' '
+test_expect_failure 'capital at the beginning of file names' '
wiki_reset &&
git clone mediawiki::'"$WIKI_URL"' mw_dir_10 &&
(
@@ -156,7 +156,7 @@ test_expect_failure 'capital at the begining of file names' '
'
-test_expect_failure 'special character at the begining of file name from mw to git' '
+test_expect_failure 'special character at the beginning of file name from mw to git' '
wiki_reset &&
git clone mediawiki::'"$WIKI_URL"' mw_dir_11 &&
wiki_editpage {char_1 "expect to be renamed {char_1" false &&
@@ -189,7 +189,7 @@ test_expect_success 'Push page with title containing ":" other than namespace se
wiki_page_exist NotANameSpace:Page
'
-test_expect_success 'test of correct formating for file name from mw to git' '
+test_expect_success 'test of correct formatting for file name from mw to git' '
wiki_reset &&
git clone mediawiki::'"$WIKI_URL"' mw_dir_12 &&
wiki_editpage char_%_7b_1 "expect to be renamed char{_1" false &&
@@ -207,7 +207,7 @@ test_expect_success 'test of correct formating for file name from mw to git' '
'
-test_expect_failure 'test of correct formating for file name begining with special character' '
+test_expect_failure 'test of correct formatting for file name beginning with special character' '
wiki_reset &&
git clone mediawiki::'"$WIKI_URL"' mw_dir_13 &&
(
@@ -215,7 +215,7 @@ test_expect_failure 'test of correct formating for file name begining with speci
echo "my new file {char_1" >\{char_1.mw &&
echo "my new file [char_2" >\[char_2.mw &&
git add . &&
- git commit -am "commiting some exotic file name..." &&
+ git commit -am "committing some exotic file name..." &&
git push &&
git pull
) &&
@@ -226,7 +226,7 @@ test_expect_failure 'test of correct formating for file name begining with speci
'
-test_expect_success 'test of correct formating for file name from git to mw' '
+test_expect_success 'test of correct formatting for file name from git to mw' '
wiki_reset &&
git clone mediawiki::'"$WIKI_URL"' mw_dir_14 &&
(
@@ -234,7 +234,7 @@ test_expect_success 'test of correct formating for file name from git to mw' '
echo "my new file char{_1" >Char\{_1.mw &&
echo "my new file char[_2" >Char\[_2.mw &&
git add . &&
- git commit -m "commiting some exotic file name..." &&
+ git commit -m "committing some exotic file name..." &&
git push
) &&
wiki_getallpage ref_page_14 &&
@@ -287,7 +287,7 @@ test_expect_success 'git push with \' '
git add \\ko\\o.mw &&
git commit -m " \\ko\\o added" &&
git push
- )&&
+ ) &&
wiki_page_exist \\ko\\o &&
wiki_check_content mw_dir_18/\\ko\\o.mw \\ko\\o
@@ -311,7 +311,7 @@ test_expect_success 'git push with \ in format control' '
git add \\fo\\o.mw &&
git commit -m " \\fo\\o added" &&
git push
- )&&
+ ) &&
wiki_page_exist \\fo\\o &&
wiki_check_content mw_dir_20/\\fo\\o.mw \\fo\\o
diff --git a/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh b/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh
index 5a0373935f..6187ec67fa 100755
--- a/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh
+++ b/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh
@@ -27,12 +27,12 @@ test_git_reimport () {
# Don't bother with permissions, be administrator by default
test_expect_success 'setup config' '
- git config --global remote.origin.mwLogin WikiAdmin &&
- git config --global remote.origin.mwPassword AdminPass &&
+ git config --global remote.origin.mwLogin "$WIKI_ADMIN" &&
+ git config --global remote.origin.mwPassword "$WIKI_PASSW" &&
test_might_fail git config --global --unset remote.origin.mediaImport
'
-test_expect_success 'git push can upload media (File:) files' '
+test_expect_failure 'git push can upload media (File:) files' '
wiki_reset &&
git clone mediawiki::'"$WIKI_URL"' mw_dir &&
(
@@ -48,16 +48,36 @@ test_expect_success 'git push can upload media (File:) files' '
)
'
-test_expect_success 'git clone works on previously created wiki with media files' '
+test_expect_failure 'git clone works on previously created wiki with media files' '
test_when_finished "rm -rf mw_dir mw_dir_clone" &&
git clone -c remote.origin.mediaimport=true \
mediawiki::'"$WIKI_URL"' mw_dir_clone &&
test_cmp mw_dir_clone/Foo.txt mw_dir/Foo.txt &&
(cd mw_dir_clone && git checkout HEAD^) &&
(cd mw_dir && git checkout HEAD^) &&
+ test_path_is_file mw_dir_clone/Foo.txt &&
test_cmp mw_dir_clone/Foo.txt mw_dir/Foo.txt
'
+test_expect_success 'git push can upload media (File:) files containing valid UTF-8' '
+ wiki_reset &&
+ git clone mediawiki::'"$WIKI_URL"' mw_dir &&
+ (
+ cd mw_dir &&
+ "$PERL_PATH" -e "print STDOUT \"UTF-8 content: éèàéê€.\";" >Bar.txt &&
+ git add Bar.txt &&
+ git commit -m "add a text file with UTF-8 content" &&
+ git push
+ )
+'
+
+test_expect_success 'git clone works on previously created wiki with media files containing valid UTF-8' '
+ test_when_finished "rm -rf mw_dir mw_dir_clone" &&
+ git clone -c remote.origin.mediaimport=true \
+ mediawiki::'"$WIKI_URL"' mw_dir_clone &&
+ test_cmp mw_dir_clone/Bar.txt mw_dir/Bar.txt
+'
+
test_expect_success 'git push & pull work with locally renamed media files' '
wiki_reset &&
git clone mediawiki::'"$WIKI_URL"' mw_dir &&
diff --git a/contrib/mw-to-git/t/t9365-continuing-queries.sh b/contrib/mw-to-git/t/t9365-continuing-queries.sh
new file mode 100755
index 0000000000..016454749f
--- /dev/null
+++ b/contrib/mw-to-git/t/t9365-continuing-queries.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description='Test the Git Mediawiki remote helper: queries w/ more than 500 results'
+
+. ./test-gitmw-lib.sh
+. $TEST_DIRECTORY/test-lib.sh
+
+test_check_precond
+
+test_expect_success 'creating page w/ >500 revisions' '
+ wiki_reset &&
+ for i in $(test_seq 501)
+ do
+ echo "creating revision $i" &&
+ wiki_editpage foo "revision $i<br/>" true
+ done
+'
+
+test_expect_success 'cloning page w/ >500 revisions' '
+ git clone mediawiki::'"$WIKI_URL"' mw_dir
+'
+
+test_done
diff --git a/contrib/mw-to-git/t/test-gitmw-lib.sh b/contrib/mw-to-git/t/test-gitmw-lib.sh
index 3b2cfacf51..64e46c1671 100755
--- a/contrib/mw-to-git/t/test-gitmw-lib.sh
+++ b/contrib/mw-to-git/t/test-gitmw-lib.sh
@@ -13,7 +13,8 @@
. ./test.config
-WIKI_URL=http://"$SERVER_ADDR:$PORT/$WIKI_DIR_NAME"
+WIKI_BASE_URL=http://$SERVER_ADDR:$PORT
+WIKI_URL=$WIKI_BASE_URL/$WIKI_DIR_NAME
CURR_DIR=$(pwd)
TEST_OUTPUT_DIRECTORY=$(pwd)
TEST_DIRECTORY="$CURR_DIR"/../../../t
@@ -62,14 +63,10 @@ test_check_precond () {
test_done
fi
- if [ ! -f "$GIT_BUILD_DIR"/git-remote-mediawiki ];
- then
- echo "No remote mediawiki for git found. Copying it in git"
- echo "cp $GIT_BUILD_DIR/contrib/mw-to-git/git-remote-mediawiki $GIT_BUILD_DIR/"
- ln -s "$GIT_BUILD_DIR"/contrib/mw-to-git/git-remote-mediawiki "$GIT_BUILD_DIR"
- fi
+ GIT_EXEC_PATH=$(cd "$(dirname "$0")" && cd "../.." && pwd)
+ PATH="$GIT_EXEC_PATH"'/bin-wrapper:'"$PATH"
- if [ ! -d "$WIKI_DIR_INST/$WIKI_DIR_NAME" ];
+ if ! test -d "$WIKI_DIR_INST/$WIKI_DIR_NAME"
then
skip_all='skipping gateway git-mw tests, no mediawiki found'
test_done
@@ -94,8 +91,8 @@ test_diff_directories () {
#
# Check that <dir> contains exactly <N> files
test_contains_N_files () {
- if test `ls -- "$1" | wc -l` -ne "$2"; then
- echo "directory $1 sould contain $2 files"
+ if test $(ls -- "$1" | wc -l) -ne "$2"; then
+ echo "directory $1 should contain $2 files"
echo "it contains these files:"
ls "$1"
false
@@ -283,7 +280,7 @@ start_lighttpd () {
"$LIGHTTPD_DIR"/lighttpd -f "$WEB"/lighttpd.conf
if test $? -ne 0 ; then
- echo "Could not execute http deamon lighttpd"
+ echo "Could not execute http daemon lighttpd"
exit 1
fi
}
@@ -293,30 +290,61 @@ start_lighttpd () {
# Kill daemon lighttpd and removes files and folders associated.
stop_lighttpd () {
test -f "$WEB_TMP/pid" && kill $(cat "$WEB_TMP/pid")
- rm -rf "$WEB"
}
-# Create the SQLite database of the MediaWiki. If the database file already
-# exists, it will be deleted.
-# This script should be runned from the directory where $FILES_FOLDER is
-# located.
-create_db () {
- rm -f "$TMP/$DB_FILE"
-
- echo "Generating the SQLite database file. It can take some time ..."
- # Run the php script to generate the SQLite database file
- # with cURL calls.
- php "$FILES_FOLDER/$DB_INSTALL_SCRIPT" $(basename "$DB_FILE" .sqlite) \
- "$WIKI_ADMIN" "$WIKI_PASSW" "$TMP" "$PORT"
-
- if [ ! -f "$TMP/$DB_FILE" ] ; then
- error "Can't create database file $TMP/$DB_FILE. Try to run ./install-wiki.sh delete first."
+wiki_delete_db () {
+ rm -rf \
+ "$FILES_FOLDER_DB"/* || error "Couldn't delete $FILES_FOLDER_DB/"
+}
+
+wiki_delete_db_backup () {
+ rm -rf \
+ "$FILES_FOLDER_POST_INSTALL_DB"/* || error "Couldn't delete $FILES_FOLDER_POST_INSTALL_DB/"
+}
+
+# Install MediaWiki using its install.php script. If the database file
+# already exists, it will be deleted.
+install_mediawiki () {
+
+ localsettings="$WIKI_DIR_INST/$WIKI_DIR_NAME/LocalSettings.php"
+ if test -f "$localsettings"
+ then
+ error "We already installed the wiki, since $localsettings exists" \
+ "perhaps you wanted to run 'delete' first?"
fi
- # Copy the generated database file into the directory the
- # user indicated.
- cp "$TMP/$DB_FILE" "$FILES_FOLDER" ||
- error "Unable to copy $TMP/$DB_FILE to $FILES_FOLDER"
+ wiki_delete_db
+ wiki_delete_db_backup
+ mkdir \
+ "$FILES_FOLDER_DB/" \
+ "$FILES_FOLDER_POST_INSTALL_DB/"
+
+ install_script="$WIKI_DIR_INST/$WIKI_DIR_NAME/maintenance/install.php"
+ echo "Installing MediaWiki using $install_script. This may take some time ..."
+
+ php "$WIKI_DIR_INST/$WIKI_DIR_NAME/maintenance/install.php" \
+ --server $WIKI_BASE_URL \
+ --scriptpath /wiki \
+ --lang en \
+ --dbtype sqlite \
+ --dbpath $PWD/$FILES_FOLDER_DB/ \
+ --pass "$WIKI_PASSW" \
+ Git-MediaWiki-Test \
+ "$WIKI_ADMIN" ||
+ error "Couldn't run $install_script, see errors above. Try to run ./install-wiki.sh delete first."
+ cat <<-'EOF' >>$localsettings
+# Custom settings added by test-gitmw-lib.sh
+#
+# Uploading text files is needed for
+# t9363-mw-to-git-export-import.sh
+$wgEnableUploads = true;
+$wgFileExtensions[] = 'txt';
+EOF
+
+ # Copy the initially generated database file into our backup
+ # folder
+ cp -R "$FILES_FOLDER_DB/"* "$FILES_FOLDER_POST_INSTALL_DB/" ||
+ error "Unable to copy $FILES_FOLDER_DB/* to $FILES_FOLDER_POST_INSTALL_DB/*"
}
# Install a wiki in your web server directory.
@@ -325,79 +353,47 @@ wiki_install () {
start_lighttpd
fi
- SERVER_ADDR=$SERVER_ADDR:$PORT
# In this part, we change directory to $TMP in order to download,
# unpack and copy the files of MediaWiki
(
mkdir -p "$WIKI_DIR_INST/$WIKI_DIR_NAME"
- if [ ! -d "$WIKI_DIR_INST/$WIKI_DIR_NAME" ] ; then
+ if ! test -d "$WIKI_DIR_INST/$WIKI_DIR_NAME"
+ then
error "Folder $WIKI_DIR_INST/$WIKI_DIR_NAME doesn't exist.
Please create it and launch the script again."
fi
- # Fetch MediaWiki's archive if not already present in the TMP directory
- cd "$TMP"
- if [ ! -f "$MW_VERSION.tar.gz" ] ; then
- echo "Downloading $MW_VERSION sources ..."
- wget "http://download.wikimedia.org/mediawiki/1.19/mediawiki-1.19.0.tar.gz" ||
+ # Fetch MediaWiki's archive if not already present in the
+ # download directory
+ mkdir -p "$FILES_FOLDER_DOWNLOAD"
+ MW_FILENAME="mediawiki-$MW_VERSION_MAJOR.$MW_VERSION_MINOR.tar.gz"
+ cd "$FILES_FOLDER_DOWNLOAD"
+ if ! test -f $MW_FILENAME
+ then
+ echo "Downloading $MW_VERSION_MAJOR.$MW_VERSION_MINOR sources ..."
+ wget "http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/$MW_FILENAME" ||
error "Unable to download "\
- "http://download.wikimedia.org/mediawiki/1.19/"\
- "mediawiki-1.19.0.tar.gz. "\
+ "http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/"\
+ "$MW_FILENAME. "\
"Please fix your connection and launch the script again."
- echo "$MW_VERSION.tar.gz downloaded in `pwd`. "\
- "You can delete it later if you want."
+ echo "$MW_FILENAME downloaded in $(pwd)/;" \
+ "you can delete it later if you want."
else
- echo "Reusing existing $MW_VERSION.tar.gz downloaded in `pwd`."
+ echo "Reusing existing $MW_FILENAME downloaded in $(pwd)/"
fi
- archive_abs_path=$(pwd)/"$MW_VERSION.tar.gz"
+ archive_abs_path=$(pwd)/$MW_FILENAME
cd "$WIKI_DIR_INST/$WIKI_DIR_NAME/" ||
error "can't cd to $WIKI_DIR_INST/$WIKI_DIR_NAME/"
tar xzf "$archive_abs_path" --strip-components=1 ||
error "Unable to extract WikiMedia's files from $archive_abs_path to "\
"$WIKI_DIR_INST/$WIKI_DIR_NAME"
) || exit 1
+ echo Extracted in "$WIKI_DIR_INST/$WIKI_DIR_NAME"
- create_db
-
- # Copy the generic LocalSettings.php in the web server's directory
- # And modify parameters according to the ones set at the top
- # of this script.
- # Note that LocalSettings.php is never modified.
- if [ ! -f "$FILES_FOLDER/LocalSettings.php" ] ; then
- error "Can't find $FILES_FOLDER/LocalSettings.php " \
- "in the current folder. "\
- "Please run the script inside its folder."
- fi
- cp "$FILES_FOLDER/LocalSettings.php" \
- "$FILES_FOLDER/LocalSettings-tmp.php" ||
- error "Unable to copy $FILES_FOLDER/LocalSettings.php " \
- "to $FILES_FOLDER/LocalSettings-tmp.php"
-
- # Parse and set the LocalSettings file of the user according to the
- # CONFIGURATION VARIABLES section at the beginning of this script
- file_swap="$FILES_FOLDER/LocalSettings-swap.php"
- sed "s,@WG_SCRIPT_PATH@,/$WIKI_DIR_NAME," \
- "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
- mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
- sed "s,@WG_SERVER@,http://$SERVER_ADDR," \
- "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
- mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
- sed "s,@WG_SQLITE_DATADIR@,$TMP," \
- "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
- mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
- sed "s,@WG_SQLITE_DATAFILE@,$( basename $DB_FILE .sqlite)," \
- "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
- mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
-
- mv "$FILES_FOLDER/LocalSettings-tmp.php" \
- "$WIKI_DIR_INST/$WIKI_DIR_NAME/LocalSettings.php" ||
- error "Unable to move $FILES_FOLDER/LocalSettings-tmp.php" \
- "in $WIKI_DIR_INST/$WIKI_DIR_NAME"
- echo "File $FILES_FOLDER/LocalSettings.php is set in" \
- " $WIKI_DIR_INST/$WIKI_DIR_NAME"
+ install_mediawiki
echo "Your wiki has been installed. You can check it at
- http://$SERVER_ADDR/$WIKI_DIR_NAME"
+ $WIKI_URL"
}
# Reset the database of the wiki and the password of the admin
@@ -405,12 +401,18 @@ wiki_install () {
# Warning: This function must be called only in a subdirectory of t/ directory
wiki_reset () {
# Copy initial database of the wiki
- if [ ! -f "../$FILES_FOLDER/$DB_FILE" ] ; then
- error "Can't find ../$FILES_FOLDER/$DB_FILE in the current folder."
+ if ! test -d "../$FILES_FOLDER_DB"
+ then
+ error "No wiki database at ../$FILES_FOLDER_DB, not installed yet?"
+ fi
+ if ! test -d "../$FILES_FOLDER_POST_INSTALL_DB"
+ then
+ error "No wiki backup database at ../$FILES_FOLDER_POST_INSTALL_DB, failed installation?"
fi
- cp "../$FILES_FOLDER/$DB_FILE" "$TMP" ||
- error "Can't copy ../$FILES_FOLDER/$DB_FILE in $TMP"
- echo "File $FILES_FOLDER/$DB_FILE is set in $TMP"
+ wiki_delete_db
+ cp -R "../$FILES_FOLDER_POST_INSTALL_DB/"* "../$FILES_FOLDER_DB/" ||
+ error "Can't copy ../$FILES_FOLDER_POST_INSTALL_DB/* to ../$FILES_FOLDER_DB/*"
+ echo "File $FILES_FOLDER_DB/* has been reset"
}
# Delete the wiki created in the web server's directory and all its content
@@ -418,18 +420,13 @@ wiki_reset () {
wiki_delete () {
if test $LIGHTTPD = "true"; then
stop_lighttpd
+ rm -fr "$WEB"
else
# Delete the wiki's directory.
rm -rf "$WIKI_DIR_INST/$WIKI_DIR_NAME" ||
error "Wiki's directory $WIKI_DIR_INST/" \
"$WIKI_DIR_NAME could not be deleted"
- # Delete the wiki's SQLite database.
- rm -f "$TMP/$DB_FILE" ||
- error "Database $TMP/$DB_FILE could not be deleted."
fi
-
- # Delete the wiki's SQLite database
- rm -f "$TMP/$DB_FILE" || error "Database $TMP/$DB_FILE could not be deleted."
- rm -f "$FILES_FOLDER/$DB_FILE"
- rm -rf "$TMP/$MW_VERSION"
+ wiki_delete_db
+ wiki_delete_db_backup
}
diff --git a/contrib/mw-to-git/t/test-gitmw.pl b/contrib/mw-to-git/t/test-gitmw.pl
index 0ff76259fa..c5d687f078 100755
--- a/contrib/mw-to-git/t/test-gitmw.pl
+++ b/contrib/mw-to-git/t/test-gitmw.pl
@@ -24,9 +24,7 @@
use MediaWiki::API;
use Getopt::Long;
-use encoding 'utf8';
use DateTime::Format::ISO8601;
-use open ':encoding(utf8)';
use constant SLASH_REPLACEMENT => "%2F";
#Parsing of the config file
@@ -87,7 +85,7 @@ sub wiki_getpage {
# Replace spaces by underscore in the page name
$pagename =~ s/ /_/g;
$pagename =~ s/\//%2F/g;
- open(my $file, ">$destdir/$pagename.mw");
+ open(my $file, ">:encoding(UTF-8)", "$destdir/$pagename.mw");
print $file "$content";
close ($file);
@@ -172,7 +170,7 @@ sub wiki_getallpagename {
cmlimit => 500 },
)
|| die $mw->{error}->{code}.": ".$mw->{error}->{details};
- open(my $file, ">all.txt");
+ open(my $file, ">:encoding(UTF-8)", "all.txt");
foreach my $page (@{$mw_pages}) {
print $file "$page->{title}\n";
}
@@ -185,7 +183,7 @@ sub wiki_getallpagename {
aplimit => 500,
})
|| die $mw->{error}->{code}.": ".$mw->{error}->{details};
- open(my $file, ">all.txt");
+ open(my $file, ">:encoding(UTF-8)", "all.txt");
foreach my $page (@{$mw_pages}) {
print $file "$page->{title}\n";
}
@@ -214,12 +212,12 @@ my $fct_to_call = shift;
wiki_login($wiki_admin, $wiki_admin_pass);
-my %functions_to_call = qw(
- upload_file wiki_upload_file
- get_page wiki_getpage
- delete_page wiki_delete_page
- edit_page wiki_editpage
- getallpagename wiki_getallpagename
+my %functions_to_call = (
+ upload_file => \&wiki_upload_file,
+ get_page => \&wiki_getpage,
+ delete_page => \&wiki_delete_page,
+ edit_page => \&wiki_editpage,
+ getallpagename => \&wiki_getallpagename,
);
die "$0 ERROR: wrong argument" unless exists $functions_to_call{$fct_to_call};
-&{$functions_to_call{$fct_to_call}}(@ARGV);
+$functions_to_call{$fct_to_call}->(map { utf8::decode($_); $_ } @ARGV);
diff --git a/contrib/mw-to-git/t/test.config b/contrib/mw-to-git/t/test.config
index 958b37b4a7..ed10b3e4a4 100644
--- a/contrib/mw-to-git/t/test.config
+++ b/contrib/mw-to-git/t/test.config
@@ -3,16 +3,12 @@ WIKI_DIR_NAME=wiki
# Login and password of the wiki's admin
WIKI_ADMIN=WikiAdmin
-WIKI_PASSW=AdminPass
+WIKI_PASSW=AdminPass1
# Address of the web server
SERVER_ADDR=localhost
-# SQLite database of the wiki, named DB_FILE, is located in TMP
-TMP=/tmp
-DB_FILE=wikidb.sqlite
-
-# If LIGHTTPD is not set to true, the script will use the defaut
+# If LIGHTTPD is not set to true, the script will use the default
# web server running in WIKI_DIR_INST.
WIKI_DIR_INST=/var/www
@@ -28,8 +24,17 @@ WEB=WEB
WEB_TMP=$WEB/tmp
WEB_WWW=$WEB/www
+# Where our configuration for the wiki is located
+FILES_FOLDER=mediawiki
+FILES_FOLDER_DOWNLOAD=$FILES_FOLDER/download
+FILES_FOLDER_DB=$FILES_FOLDER/db
+FILES_FOLDER_POST_INSTALL_DB=$FILES_FOLDER/post-install-db
+
# The variables below are used by the script to install a wiki.
# You should not modify these unless you are modifying the script itself.
-MW_VERSION=mediawiki-1.19.0
-FILES_FOLDER=install-wiki
-DB_INSTALL_SCRIPT=db_install.php
+# tested versions: 1.19.X -> 1.21.1 -> 1.34.2
+#
+# See https://www.mediawiki.org/wiki/Download for what the latest
+# version is.
+MW_VERSION_MAJOR=1.34
+MW_VERSION_MINOR=2
diff --git a/contrib/p4import/README b/contrib/p4import/README
deleted file mode 100644
index b9892b6793..0000000000
--- a/contrib/p4import/README
+++ /dev/null
@@ -1 +0,0 @@
-Please see contrib/fast-import/git-p4 for a better Perforce importer.
diff --git a/contrib/p4import/git-p4import.py b/contrib/p4import/git-p4import.py
deleted file mode 100644
index b6e534b65b..0000000000
--- a/contrib/p4import/git-p4import.py
+++ /dev/null
@@ -1,360 +0,0 @@
-#!/usr/bin/env python
-#
-# This tool is copyright (c) 2006, Sean Estabrooks.
-# It is released under the Gnu Public License, version 2.
-#
-# Import Perforce branches into Git repositories.
-# Checking out the files is done by calling the standard p4
-# client which you must have properly configured yourself
-#
-
-import marshal
-import os
-import sys
-import time
-import getopt
-
-from signal import signal, \
- SIGPIPE, SIGINT, SIG_DFL, \
- default_int_handler
-
-signal(SIGPIPE, SIG_DFL)
-s = signal(SIGINT, SIG_DFL)
-if s != default_int_handler:
- signal(SIGINT, s)
-
-def die(msg, *args):
- for a in args:
- msg = "%s %s" % (msg, a)
- print "git-p4import fatal error:", msg
- sys.exit(1)
-
-def usage():
- print "USAGE: git-p4import [-q|-v] [--authors=<file>] [-t <timezone>] [//p4repo/path <branch>]"
- sys.exit(1)
-
-verbosity = 1
-logfile = "/dev/null"
-ignore_warnings = False
-stitch = 0
-tagall = True
-
-def report(level, msg, *args):
- global verbosity
- global logfile
- for a in args:
- msg = "%s %s" % (msg, a)
- fd = open(logfile, "a")
- fd.writelines(msg)
- fd.close()
- if level <= verbosity:
- print msg
-
-class p4_command:
- def __init__(self, _repopath):
- try:
- global logfile
- self.userlist = {}
- if _repopath[-1] == '/':
- self.repopath = _repopath[:-1]
- else:
- self.repopath = _repopath
- if self.repopath[-4:] != "/...":
- self.repopath= "%s/..." % self.repopath
- f=os.popen('p4 -V 2>>%s'%logfile, 'rb')
- a = f.readlines()
- if f.close():
- raise
- except:
- die("Could not find the \"p4\" command")
-
- def p4(self, cmd, *args):
- global logfile
- cmd = "%s %s" % (cmd, ' '.join(args))
- report(2, "P4:", cmd)
- f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb')
- list = []
- while 1:
- try:
- list.append(marshal.load(f))
- except EOFError:
- break
- self.ret = f.close()
- return list
-
- def sync(self, id, force=False, trick=False, test=False):
- if force:
- ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0]
- elif trick:
- ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0]
- elif test:
- ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0]
- else:
- ret = self.p4("sync %s@%s"%(self.repopath, id))[0]
- if ret['code'] == "error":
- data = ret['data'].upper()
- if data.find('VIEW') > 0:
- die("Perforce reports %s is not in client view"% self.repopath)
- elif data.find('UP-TO-DATE') < 0:
- die("Could not sync files from perforce", self.repopath)
-
- def changes(self, since=0):
- try:
- list = []
- for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)):
- list.append(rec['change'])
- list.reverse()
- return list
- except:
- return []
-
- def authors(self, filename):
- f=open(filename)
- for l in f.readlines():
- self.userlist[l[:l.find('=')].rstrip()] = \
- (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')])
- f.close()
- for f,e in self.userlist.items():
- report(2, f, ":", e[0], " <", e[1], ">")
-
- def _get_user(self, id):
- if not self.userlist.has_key(id):
- try:
- user = self.p4("users", id)[0]
- self.userlist[id] = (user['FullName'], user['Email'])
- except:
- self.userlist[id] = (id, "")
- return self.userlist[id]
-
- def _format_date(self, ticks):
- symbol='+'
- name = time.tzname[0]
- offset = time.timezone
- if ticks[8]:
- name = time.tzname[1]
- offset = time.altzone
- if offset < 0:
- offset *= -1
- symbol = '-'
- localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name)
- tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks)
- return "%s %s" % (tickso, localo)
-
- def where(self):
- try:
- return self.p4("where %s" % self.repopath)[-1]['path']
- except:
- return ""
-
- def describe(self, num):
- desc = self.p4("describe -s", num)[0]
- self.msg = desc['desc']
- self.author, self.email = self._get_user(desc['user'])
- self.date = self._format_date(time.localtime(long(desc['time'])))
- return self
-
-class git_command:
- def __init__(self):
- try:
- self.version = self.git("--version")[0][12:].rstrip()
- except:
- die("Could not find the \"git\" command")
- try:
- self.gitdir = self.get_single("rev-parse --git-dir")
- report(2, "gdir:", self.gitdir)
- except:
- die("Not a git repository... did you forget to \"git init\" ?")
- try:
- self.cdup = self.get_single("rev-parse --show-cdup")
- if self.cdup != "":
- os.chdir(self.cdup)
- self.topdir = os.getcwd()
- report(2, "topdir:", self.topdir)
- except:
- die("Could not find top git directory")
-
- def git(self, cmd):
- global logfile
- report(2, "GIT:", cmd)
- f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb')
- r=f.readlines()
- self.ret = f.close()
- return r
-
- def get_single(self, cmd):
- return self.git(cmd)[0].rstrip()
-
- def current_branch(self):
- try:
- testit = self.git("rev-parse --verify HEAD")[0]
- return self.git("symbolic-ref HEAD")[0][11:].rstrip()
- except:
- return None
-
- def get_config(self, variable):
- try:
- return self.git("config --get %s" % variable)[0].rstrip()
- except:
- return None
-
- def set_config(self, variable, value):
- try:
- self.git("config %s %s"%(variable, value) )
- except:
- die("Could not set %s to " % variable, value)
-
- def make_tag(self, name, head):
- self.git("tag -f %s %s"%(name,head))
-
- def top_change(self, branch):
- try:
- a=self.get_single("name-rev --tags refs/heads/%s" % branch)
- loc = a.find(' tags/') + 6
- if a[loc:loc+3] != "p4/":
- raise
- return int(a[loc+3:][:-2])
- except:
- return 0
-
- def update_index(self):
- self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
-
- def checkout(self, branch):
- self.git("checkout %s" % branch)
-
- def repoint_head(self, branch):
- self.git("symbolic-ref HEAD refs/heads/%s" % branch)
-
- def remove_files(self):
- self.git("ls-files | xargs rm")
-
- def clean_directories(self):
- self.git("clean -d")
-
- def fresh_branch(self, branch):
- report(1, "Creating new branch", branch)
- self.git("ls-files | xargs rm")
- os.remove(".git/index")
- self.repoint_head(branch)
- self.git("clean -d")
-
- def basedir(self):
- return self.topdir
-
- def commit(self, author, email, date, msg, id):
- self.update_index()
- fd=open(".msg", "w")
- fd.writelines(msg)
- fd.close()
- try:
- current = self.get_single("rev-parse --verify HEAD")
- head = "-p HEAD"
- except:
- current = ""
- head = ""
- tree = self.get_single("write-tree")
- for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]:
- os.environ['GIT_AUTHOR_%s'%r] = l
- os.environ['GIT_COMMITTER_%s'%r] = l
- commit = self.get_single("commit-tree %s %s < .msg" % (tree,head))
- os.remove(".msg")
- self.make_tag("p4/%s"%id, commit)
- self.git("update-ref HEAD %s %s" % (commit, current) )
-
-try:
- opts, args = getopt.getopt(sys.argv[1:], "qhvt:",
- ["authors=","help","stitch=","timezone=","log=","ignore","notags"])
-except getopt.GetoptError:
- usage()
-
-for o, a in opts:
- if o == "-q":
- verbosity = 0
- if o == "-v":
- verbosity += 1
- if o in ("--log"):
- logfile = a
- if o in ("--notags"):
- tagall = False
- if o in ("-h", "--help"):
- usage()
- if o in ("--ignore"):
- ignore_warnings = True
-
-git = git_command()
-branch=git.current_branch()
-
-for o, a in opts:
- if o in ("-t", "--timezone"):
- git.set_config("perforce.timezone", a)
- if o in ("--stitch"):
- git.set_config("perforce.%s.path" % branch, a)
- stitch = 1
-
-if len(args) == 2:
- branch = args[1]
- git.checkout(branch)
- if branch == git.current_branch():
- die("Branch %s already exists!" % branch)
- report(1, "Setting perforce to ", args[0])
- git.set_config("perforce.%s.path" % branch, args[0])
-elif len(args) != 0:
- die("You must specify the perforce //depot/path and git branch")
-
-p4path = git.get_config("perforce.%s.path" % branch)
-if p4path == None:
- die("Do not know Perforce //depot/path for git branch", branch)
-
-p4 = p4_command(p4path)
-
-for o, a in opts:
- if o in ("-a", "--authors"):
- p4.authors(a)
-
-localdir = git.basedir()
-if p4.where()[:len(localdir)] != localdir:
- report(1, "**WARNING** Appears p4 client is misconfigured")
- report(1, " for sync from %s to %s" % (p4.repopath, localdir))
- if ignore_warnings != True:
- die("Reconfigure or use \"--ignore\" on command line")
-
-if stitch == 0:
- top = git.top_change(branch)
-else:
- top = 0
-changes = p4.changes(top)
-count = len(changes)
-if count == 0:
- report(1, "Already up to date...")
- sys.exit(0)
-
-ptz = git.get_config("perforce.timezone")
-if ptz:
- report(1, "Setting timezone to", ptz)
- os.environ['TZ'] = ptz
- time.tzset()
-
-if stitch == 1:
- git.remove_files()
- git.clean_directories()
- p4.sync(changes[0], force=True)
-elif top == 0 and branch != git.current_branch():
- p4.sync(changes[0], test=True)
- report(1, "Creating new initial commit");
- git.fresh_branch(branch)
- p4.sync(changes[0], force=True)
-else:
- p4.sync(changes[0], trick=True)
-
-report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch))
-for id in changes:
- report(1, "Importing changeset", id)
- change = p4.describe(id)
- p4.sync(id)
- if tagall :
- git.commit(change.author, change.email, change.date, change.msg, id)
- else:
- git.commit(change.author, change.email, change.date, change.msg, "import")
- if stitch == 1:
- git.clean_directories()
- stitch = 0
diff --git a/contrib/p4import/git-p4import.txt b/contrib/p4import/git-p4import.txt
deleted file mode 100644
index 9967587fe6..0000000000
--- a/contrib/p4import/git-p4import.txt
+++ /dev/null
@@ -1,167 +0,0 @@
-git-p4import(1)
-===============
-
-NAME
-----
-git-p4import - Import a Perforce repository into git
-
-
-SYNOPSIS
---------
-[verse]
-`git-p4import` [-q|-v] [--notags] [--authors <file>] [-t <timezone>]
- <//p4repo/path> <branch>
-`git-p4import` --stitch <//p4repo/path>
-`git-p4import`
-
-
-DESCRIPTION
------------
-Import a Perforce repository into an existing git repository. When
-a <//p4repo/path> and <branch> are specified a new branch with the
-given name will be created and the initial import will begin.
-
-Once the initial import is complete you can do an incremental import
-of new commits from the Perforce repository. You do this by checking
-out the appropriate git branch and then running `git-p4import` without
-any options.
-
-The standard p4 client is used to communicate with the Perforce
-repository; it must be configured correctly in order for `git-p4import`
-to operate (see below).
-
-
-OPTIONS
--------
--q::
- Do not display any progress information.
-
--v::
- Give extra progress information.
-
-\--authors::
- Specify an authors file containing a mapping of Perforce user
- ids to full names and email addresses (see Notes below).
-
-\--notags::
- Do not create a tag for each imported commit.
-
-\--stitch::
- Import the contents of the given perforce branch into the
- currently checked out git branch.
-
-\--log::
- Store debugging information in the specified file.
-
--t::
- Specify that the remote repository is in the specified timezone.
- Timezone must be in the format "US/Pacific" or "Europe/London"
- etc. You only need to specify this once, it will be saved in
- the git config file for the repository.
-
-<//p4repo/path>::
- The Perforce path that will be imported into the specified branch.
-
-<branch>::
- The new branch that will be created to hold the Perforce imports.
-
-
-P4 Client
----------
-You must make the `p4` client command available in your $PATH and
-configure it to communicate with the target Perforce repository.
-Typically this means you must set the "$P4PORT" and "$P4CLIENT"
-environment variables.
-
-You must also configure a `p4` client "view" which maps the Perforce
-branch into the top level of your git repository, for example:
-
-------------
-Client: myhost
-
-Root: /home/sean/import
-
-Options: noallwrite clobber nocompress unlocked modtime rmdir
-
-View:
- //public/jam/... //myhost/jam/...
-------------
-
-With the above `p4` client setup, you could import the "jam"
-perforce branch into a branch named "jammy", like so:
-
-------------
-$ mkdir -p /home/sean/import/jam
-$ cd /home/sean/import/jam
-$ git init
-$ git p4import //public/jam jammy
-------------
-
-
-Multiple Branches
------------------
-Note that by creating multiple "views" you can use `git-p4import`
-to import additional branches into the same git repository.
-However, the `p4` client has a limitation in that it silently
-ignores all but the last "view" that maps into the same local
-directory. So the following will *not* work:
-
-------------
-View:
- //public/jam/... //myhost/jam/...
- //public/other/... //myhost/jam/...
- //public/guest/... //myhost/jam/...
-------------
-
-If you want more than one Perforce branch to be imported into the
-same directory you must employ a workaround. A simple option is
-to adjust your `p4` client before each import to only include a
-single view.
-
-Another option is to create multiple symlinks locally which all
-point to the same directory in your git repository and then use
-one per "view" instead of listing the actual directory.
-
-
-Tags
-----
-A git tag of the form p4/xx is created for every change imported from
-the Perforce repository where xx is the Perforce changeset number.
-Therefore after the import you can use git to access any commit by its
-Perforce number, e.g. git show p4/327.
-
-The tag associated with the HEAD commit is also how `git-p4import`
-determines if there are new changes to incrementally import from the
-Perforce repository.
-
-If you import from a repository with many thousands of changes
-you will have an equal number of p4/xxxx git tags. Git tags can
-be expensive in terms of disk space and repository operations.
-If you don't need to perform further incremental imports, you
-may delete the tags.
-
-
-Notes
------
-You can interrupt the import (e.g. ctrl-c) at any time and restart it
-without worry.
-
-Author information is automatically determined by querying the
-Perforce "users" table using the id associated with each change.
-However, if you want to manually supply these mappings you can do
-so with the "--authors" option. It accepts a file containing a list
-of mappings with each line containing one mapping in the format:
-
-------------
- perforce_id = Full Name <email@address.com>
-------------
-
-
-Author
-------
-Written by Sean Estabrooks <seanlkml@sympatico.ca>
-
-
-GIT
----
-Part of the gitlink:git[7] suite
diff --git a/contrib/patches/docbook-xsl-manpages-charmap.patch b/contrib/patches/docbook-xsl-manpages-charmap.patch
deleted file mode 100644
index f2b08b4f4a..0000000000
--- a/contrib/patches/docbook-xsl-manpages-charmap.patch
+++ /dev/null
@@ -1,21 +0,0 @@
-From: Ismail Dönmez <ismail@pardus.org.tr>
-
-Trying to build the documentation with docbook-xsl 1.73 may result in
-the following error. This patch fixes it.
-
-$ xmlto -m callouts.xsl man git-add.xml
-runtime error: file
-file:///usr/share/sgml/docbook/xsl-stylesheets-1.73.0/manpages/other.xsl line
-129 element call-template
-The called template 'read-character-map' was not found.
-
---- docbook-xsl-1.73.0/manpages/docbook.xsl.manpages-charmap 2007-07-23 16:24:23.000000000 +0100
-+++ docbook-xsl-1.73.0/manpages/docbook.xsl 2007-07-23 16:25:16.000000000 +0100
-@@ -37,6 +37,7 @@
- <xsl:include href="lists.xsl"/>
- <xsl:include href="endnotes.xsl"/>
- <xsl:include href="table.xsl"/>
-+ <xsl:include href="../common/charmap.xsl"/>
-
- <!-- * we rename the following just to avoid using params with "man" -->
- <!-- * prefixes in the table.xsl stylesheet (because that stylesheet -->
diff --git a/contrib/persistent-https/Makefile b/contrib/persistent-https/Makefile
index 92baa3beee..52b84ba3d4 100644
--- a/contrib/persistent-https/Makefile
+++ b/contrib/persistent-https/Makefile
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-BUILD_LABEL=$(shell date +"%s")
+BUILD_LABEL=$(shell cut -d" " -f3 ../../GIT-VERSION-FILE)
TAR_OUT=$(shell go env GOOS)_$(shell go env GOARCH).tar.gz
all: git-remote-persistent-https git-remote-persistent-https--proxy \
@@ -25,8 +25,10 @@ git-remote-persistent-http: git-remote-persistent-https
ln -f -s git-remote-persistent-https git-remote-persistent-http
git-remote-persistent-https:
+ case $$(go version) in \
+ "go version go"1.[0-5].*) EQ=" " ;; *) EQ="=" ;; esac && \
go build -o git-remote-persistent-https \
- -ldflags "-X main._BUILD_EMBED_LABEL $(BUILD_LABEL)"
+ -ldflags "-X main._BUILD_EMBED_LABEL$${EQ}$(BUILD_LABEL)"
clean:
rm -f git-remote-persistent-http* *.tar.gz
diff --git a/contrib/persistent-https/README b/contrib/persistent-https/README
index f784dd2e66..7c4cd8d257 100644
--- a/contrib/persistent-https/README
+++ b/contrib/persistent-https/README
@@ -35,6 +35,16 @@ to use persistent-https:
[url "persistent-http"]
insteadof = http
+You may also want to allow the use of the persistent-https helper for
+submodule URLs (since any https URLs pointing to submodules will be
+rewritten, and Git's out-of-the-box defaults forbid submodules from
+using unknown remote helpers):
+
+[protocol "persistent-https"]
+ allow = always
+[protocol "persistent-http"]
+ allow = always
+
#####################################################################
# BUILDING FROM SOURCE
diff --git a/contrib/remote-helpers/Makefile b/contrib/remote-helpers/Makefile
deleted file mode 100644
index 9a76575f78..0000000000
--- a/contrib/remote-helpers/Makefile
+++ /dev/null
@@ -1,13 +0,0 @@
-TESTS := $(wildcard test*.sh)
-
-export T := $(addprefix $(CURDIR)/,$(TESTS))
-export MAKE := $(MAKE) -e
-export PATH := $(CURDIR):$(PATH)
-
-test:
- $(MAKE) -C ../../t $@
-
-$(TESTS):
- $(MAKE) -C ../../t $(CURDIR)/$@
-
-.PHONY: $(TESTS)
diff --git a/contrib/remote-helpers/README b/contrib/remote-helpers/README
new file mode 100644
index 0000000000..ac72332517
--- /dev/null
+++ b/contrib/remote-helpers/README
@@ -0,0 +1,15 @@
+The remote-helper bridges to access data stored in Mercurial and
+Bazaar are maintained outside the git.git tree in the repositories
+of their primary author:
+
+ https://github.com/felipec/git-remote-hg (for Mercurial)
+ https://github.com/felipec/git-remote-bzr (for Bazaar)
+
+You can pick a directory on your $PATH and download them from these
+repositories, e.g.:
+
+ $ wget -O $HOME/bin/git-remote-hg \
+ https://raw.github.com/felipec/git-remote-hg/master/git-remote-hg
+ $ wget -O $HOME/bin/git-remote-bzr \
+ https://raw.github.com/felipec/git-remote-bzr/master/git-remote-bzr
+ $ chmod +x $HOME/bin/git-remote-hg $HOME/bin/git-remote-bzr
diff --git a/contrib/remote-helpers/git-remote-bzr b/contrib/remote-helpers/git-remote-bzr
new file mode 100755
index 0000000000..1c3d87f861
--- /dev/null
+++ b/contrib/remote-helpers/git-remote-bzr
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+cat >&2 <<'EOT'
+WARNING: git-remote-bzr is now maintained independently.
+WARNING: For more information visit https://github.com/felipec/git-remote-bzr
+WARNING:
+WARNING: You can pick a directory on your $PATH and download it, e.g.:
+WARNING: $ wget -O $HOME/bin/git-remote-bzr \
+WARNING: https://raw.github.com/felipec/git-remote-bzr/master/git-remote-bzr
+WARNING: $ chmod +x $HOME/bin/git-remote-bzr
+EOT
diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg
index c7006000a6..8e9188364c 100755
--- a/contrib/remote-helpers/git-remote-hg
+++ b/contrib/remote-helpers/git-remote-hg
@@ -1,794 +1,11 @@
-#!/usr/bin/env python
-#
-# Copyright (c) 2012 Felipe Contreras
-#
-
-# Inspired by Rocco Rutte's hg-fast-export
-
-# Just copy to your ~/bin, or anywhere in your $PATH.
-# Then you can clone with:
-# git clone hg::/path/to/mercurial/repo/
-
-from mercurial import hg, ui, bookmarks, context, util, encoding
-
-import re
-import sys
-import os
-import json
-import shutil
-import subprocess
-import urllib
-
-#
-# If you want to switch to hg-git compatibility mode:
-# git config --global remote-hg.hg-git-compat true
-#
-# git:
-# Sensible defaults for git.
-# hg bookmarks are exported as git branches, hg branches are prefixed
-# with 'branches/', HEAD is a special case.
-#
-# hg:
-# Emulate hg-git.
-# Only hg bookmarks are exported as git branches.
-# Commits are modified to preserve hg information and allow bidirectionality.
-#
-
-NAME_RE = re.compile('^([^<>]+)')
-AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
-AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
-RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
-
-def die(msg, *args):
- sys.stderr.write('ERROR: %s\n' % (msg % args))
- sys.exit(1)
-
-def warn(msg, *args):
- sys.stderr.write('WARNING: %s\n' % (msg % args))
-
-def gitmode(flags):
- return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
-
-def gittz(tz):
- return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
-
-def hgmode(mode):
- m = { '0100755': 'x', '0120000': 'l' }
- return m.get(mode, '')
-
-def get_config(config):
- cmd = ['git', 'config', '--get', config]
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
- output, _ = process.communicate()
- return output
-
-class Marks:
-
- def __init__(self, path):
- self.path = path
- self.tips = {}
- self.marks = {}
- self.rev_marks = {}
- self.last_mark = 0
-
- self.load()
-
- def load(self):
- if not os.path.exists(self.path):
- return
-
- tmp = json.load(open(self.path))
-
- self.tips = tmp['tips']
- self.marks = tmp['marks']
- self.last_mark = tmp['last-mark']
-
- for rev, mark in self.marks.iteritems():
- self.rev_marks[mark] = int(rev)
-
- def dict(self):
- return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
-
- def store(self):
- json.dump(self.dict(), open(self.path, 'w'))
-
- def __str__(self):
- return str(self.dict())
-
- def from_rev(self, rev):
- return self.marks[str(rev)]
-
- def to_rev(self, mark):
- return self.rev_marks[mark]
-
- def get_mark(self, rev):
- self.last_mark += 1
- self.marks[str(rev)] = self.last_mark
- return self.last_mark
-
- def new_mark(self, rev, mark):
- self.marks[str(rev)] = mark
- self.rev_marks[mark] = rev
- self.last_mark = mark
-
- def is_marked(self, rev):
- return self.marks.has_key(str(rev))
-
- def get_tip(self, branch):
- return self.tips.get(branch, 0)
-
- def set_tip(self, branch, tip):
- self.tips[branch] = tip
-
-class Parser:
-
- def __init__(self, repo):
- self.repo = repo
- self.line = self.get_line()
-
- def get_line(self):
- return sys.stdin.readline().strip()
-
- def __getitem__(self, i):
- return self.line.split()[i]
-
- def check(self, word):
- return self.line.startswith(word)
-
- def each_block(self, separator):
- while self.line != separator:
- yield self.line
- self.line = self.get_line()
-
- def __iter__(self):
- return self.each_block('')
-
- def next(self):
- self.line = self.get_line()
- if self.line == 'done':
- self.line = None
-
- def get_mark(self):
- i = self.line.index(':') + 1
- return int(self.line[i:])
-
- def get_data(self):
- if not self.check('data'):
- return None
- i = self.line.index(' ') + 1
- size = int(self.line[i:])
- return sys.stdin.read(size)
-
- def get_author(self):
- global bad_mail
-
- ex = None
- m = RAW_AUTHOR_RE.match(self.line)
- if not m:
- return None
- _, name, email, date, tz = m.groups()
- if name and 'ext:' in name:
- m = re.match('^(.+?) ext:\((.+)\)$', name)
- if m:
- name = m.group(1)
- ex = urllib.unquote(m.group(2))
-
- if email != bad_mail:
- if name:
- user = '%s <%s>' % (name, email)
- else:
- user = '<%s>' % (email)
- else:
- user = name
-
- if ex:
- user += ex
-
- tz = int(tz)
- tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
- return (user, int(date), -tz)
-
-def export_file(fc):
- d = fc.data()
- print "M %s inline %s" % (gitmode(fc.flags()), fc.path())
- print "data %d" % len(d)
- print d
-
-def get_filechanges(repo, ctx, parent):
- modified = set()
- added = set()
- removed = set()
-
- cur = ctx.manifest()
- prev = repo[parent].manifest().copy()
-
- for fn in cur:
- if fn in prev:
- if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
- modified.add(fn)
- del prev[fn]
- else:
- added.add(fn)
- removed |= set(prev.keys())
-
- return added | modified, removed
-
-def fixup_user_git(user):
- name = mail = None
- user = user.replace('"', '')
- m = AUTHOR_RE.match(user)
- if m:
- name = m.group(1)
- mail = m.group(2).strip()
- else:
- m = NAME_RE.match(user)
- if m:
- name = m.group(1).strip()
- return (name, mail)
-
-def fixup_user_hg(user):
- def sanitize(name):
- # stole this from hg-git
- return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
-
- m = AUTHOR_HG_RE.match(user)
- if m:
- name = sanitize(m.group(1))
- mail = sanitize(m.group(2))
- ex = m.group(3)
- if ex:
- name += ' ext:(' + urllib.quote(ex) + ')'
- else:
- name = sanitize(user)
- if '@' in user:
- mail = name
- else:
- mail = None
-
- return (name, mail)
-
-def fixup_user(user):
- global mode, bad_mail
-
- if mode == 'git':
- name, mail = fixup_user_git(user)
- else:
- name, mail = fixup_user_hg(user)
-
- if not name:
- name = bad_name
- if not mail:
- mail = bad_mail
-
- return '%s <%s>' % (name, mail)
-
-def get_repo(url, alias):
- global dirname, peer
-
- myui = ui.ui()
- myui.setconfig('ui', 'interactive', 'off')
-
- if hg.islocal(url):
- repo = hg.repository(myui, url)
- else:
- local_path = os.path.join(dirname, 'clone')
- if not os.path.exists(local_path):
- peer, dstpeer = hg.clone(myui, {}, url, local_path, update=False, pull=True)
- repo = dstpeer.local()
- else:
- repo = hg.repository(myui, local_path)
- peer = hg.peer(myui, {}, url)
- repo.pull(peer, heads=None, force=True)
-
- return repo
-
-def rev_to_mark(rev):
- global marks
- return marks.from_rev(rev)
-
-def mark_to_rev(mark):
- global marks
- return marks.to_rev(mark)
-
-def export_ref(repo, name, kind, head):
- global prefix, marks, mode
-
- ename = '%s/%s' % (kind, name)
- tip = marks.get_tip(ename)
-
- # mercurial takes too much time checking this
- if tip and tip == head.rev():
- # nothing to do
- return
- revs = xrange(tip, head.rev() + 1)
- count = 0
-
- revs = [rev for rev in revs if not marks.is_marked(rev)]
-
- for rev in revs:
-
- c = repo[rev]
- (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
- rev_branch = extra['branch']
-
- author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
- if 'committer' in extra:
- user, time, tz = extra['committer'].rsplit(' ', 2)
- committer = "%s %s %s" % (user, time, gittz(int(tz)))
- else:
- committer = author
-
- parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
-
- if len(parents) == 0:
- modified = c.manifest().keys()
- removed = []
- else:
- modified, removed = get_filechanges(repo, c, parents[0])
-
- if mode == 'hg':
- extra_msg = ''
-
- if rev_branch != 'default':
- extra_msg += 'branch : %s\n' % rev_branch
-
- renames = []
- for f in c.files():
- if f not in c.manifest():
- continue
- rename = c.filectx(f).renamed()
- if rename:
- renames.append((rename[0], f))
-
- for e in renames:
- extra_msg += "rename : %s => %s\n" % e
-
- for key, value in extra.iteritems():
- if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
- continue
- else:
- extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
-
- desc += '\n'
- if extra_msg:
- desc += '\n--HG--\n' + extra_msg
-
- if len(parents) == 0 and rev:
- print 'reset %s/%s' % (prefix, ename)
-
- print "commit %s/%s" % (prefix, ename)
- print "mark :%d" % (marks.get_mark(rev))
- print "author %s" % (author)
- print "committer %s" % (committer)
- print "data %d" % (len(desc))
- print desc
-
- if len(parents) > 0:
- print "from :%s" % (rev_to_mark(parents[0]))
- if len(parents) > 1:
- print "merge :%s" % (rev_to_mark(parents[1]))
-
- for f in modified:
- export_file(c.filectx(f))
- for f in removed:
- print "D %s" % (f)
- print
-
- count += 1
- if (count % 100 == 0):
- print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
- print "#############################################################"
-
- # make sure the ref is updated
- print "reset %s/%s" % (prefix, ename)
- print "from :%u" % rev_to_mark(rev)
- print
-
- marks.set_tip(ename, rev)
-
-def export_tag(repo, tag):
- export_ref(repo, tag, 'tags', repo[tag])
-
-def export_bookmark(repo, bmark):
- head = bmarks[bmark]
- export_ref(repo, bmark, 'bookmarks', head)
-
-def export_branch(repo, branch):
- tip = get_branch_tip(repo, branch)
- head = repo[tip]
- export_ref(repo, branch, 'branches', head)
-
-def export_head(repo):
- global g_head
- export_ref(repo, g_head[0], 'bookmarks', g_head[1])
-
-def do_capabilities(parser):
- global prefix, dirname
-
- print "import"
- print "export"
- print "refspec refs/heads/branches/*:%s/branches/*" % prefix
- print "refspec refs/heads/*:%s/bookmarks/*" % prefix
- print "refspec refs/tags/*:%s/tags/*" % prefix
-
- path = os.path.join(dirname, 'marks-git')
-
- if os.path.exists(path):
- print "*import-marks %s" % path
- print "*export-marks %s" % path
-
- print
-
-def get_branch_tip(repo, branch):
- global branches
-
- heads = branches.get(branch, None)
- if not heads:
- return None
-
- # verify there's only one head
- if (len(heads) > 1):
- warn("Branch '%s' has more than one head, consider merging" % branch)
- # older versions of mercurial don't have this
- if hasattr(repo, "branchtip"):
- return repo.branchtip(branch)
-
- return heads[0]
-
-def list_head(repo, cur):
- global g_head, bmarks
-
- head = bookmarks.readcurrent(repo)
- if head:
- node = repo[head]
- else:
- # fake bookmark from current branch
- head = cur
- node = repo['.']
- if not node:
- node = repo['tip']
- if not node:
- return
- if head == 'default':
- head = 'master'
- bmarks[head] = node
-
- print "@refs/heads/%s HEAD" % head
- g_head = (head, node)
-
-def do_list(parser):
- global branches, bmarks, mode, track_branches
-
- repo = parser.repo
- for bmark, node in bookmarks.listbookmarks(repo).iteritems():
- bmarks[bmark] = repo[node]
-
- cur = repo.dirstate.branch()
-
- list_head(repo, cur)
-
- if track_branches:
- for branch in repo.branchmap():
- heads = repo.branchheads(branch)
- if len(heads):
- branches[branch] = heads
-
- for branch in branches:
- print "? refs/heads/branches/%s" % branch
-
- for bmark in bmarks:
- print "? refs/heads/%s" % bmark
-
- for tag, node in repo.tagslist():
- if tag == 'tip':
- continue
- print "? refs/tags/%s" % tag
-
- print
-
-def do_import(parser):
- repo = parser.repo
-
- path = os.path.join(dirname, 'marks-git')
-
- print "feature done"
- if os.path.exists(path):
- print "feature import-marks=%s" % path
- print "feature export-marks=%s" % path
- sys.stdout.flush()
-
- tmp = encoding.encoding
- encoding.encoding = 'utf-8'
-
- # lets get all the import lines
- while parser.check('import'):
- ref = parser[1]
-
- if (ref == 'HEAD'):
- export_head(repo)
- elif ref.startswith('refs/heads/branches/'):
- branch = ref[len('refs/heads/branches/'):]
- export_branch(repo, branch)
- elif ref.startswith('refs/heads/'):
- bmark = ref[len('refs/heads/'):]
- export_bookmark(repo, bmark)
- elif ref.startswith('refs/tags/'):
- tag = ref[len('refs/tags/'):]
- export_tag(repo, tag)
-
- parser.next()
-
- encoding.encoding = tmp
-
- print 'done'
-
-def parse_blob(parser):
- global blob_marks
-
- parser.next()
- mark = parser.get_mark()
- parser.next()
- data = parser.get_data()
- blob_marks[mark] = data
- parser.next()
- return
-
-def get_merge_files(repo, p1, p2, files):
- for e in repo[p1].files():
- if e not in files:
- if e not in repo[p1].manifest():
- continue
- f = { 'ctx' : repo[p1][e] }
- files[e] = f
-
-def parse_commit(parser):
- global marks, blob_marks, bmarks, parsed_refs
- global mode
-
- from_mark = merge_mark = None
-
- ref = parser[1]
- parser.next()
-
- commit_mark = parser.get_mark()
- parser.next()
- author = parser.get_author()
- parser.next()
- committer = parser.get_author()
- parser.next()
- data = parser.get_data()
- parser.next()
- if parser.check('from'):
- from_mark = parser.get_mark()
- parser.next()
- if parser.check('merge'):
- merge_mark = parser.get_mark()
- parser.next()
- if parser.check('merge'):
- die('octopus merges are not supported yet')
-
- files = {}
-
- for line in parser:
- if parser.check('M'):
- t, m, mark_ref, path = line.split(' ', 3)
- mark = int(mark_ref[1:])
- f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
- elif parser.check('D'):
- t, path = line.split(' ')
- f = { 'deleted' : True }
- else:
- die('Unknown file command: %s' % line)
- files[path] = f
-
- def getfilectx(repo, memctx, f):
- of = files[f]
- if 'deleted' in of:
- raise IOError
- if 'ctx' in of:
- return of['ctx']
- is_exec = of['mode'] == 'x'
- is_link = of['mode'] == 'l'
- rename = of.get('rename', None)
- return context.memfilectx(f, of['data'],
- is_link, is_exec, rename)
-
- repo = parser.repo
-
- user, date, tz = author
- extra = {}
-
- if committer != author:
- extra['committer'] = "%s %u %u" % committer
-
- if from_mark:
- p1 = repo.changelog.node(mark_to_rev(from_mark))
- else:
- p1 = '\0' * 20
-
- if merge_mark:
- p2 = repo.changelog.node(mark_to_rev(merge_mark))
- else:
- p2 = '\0' * 20
-
- #
- # If files changed from any of the parents, hg wants to know, but in git if
- # nothing changed from the first parent, nothing changed.
- #
- if merge_mark:
- get_merge_files(repo, p1, p2, files)
-
- if mode == 'hg':
- i = data.find('\n--HG--\n')
- if i >= 0:
- tmp = data[i + len('\n--HG--\n'):].strip()
- for k, v in [e.split(' : ') for e in tmp.split('\n')]:
- if k == 'rename':
- old, new = v.split(' => ', 1)
- files[new]['rename'] = old
- elif k == 'branch':
- extra[k] = v
- elif k == 'extra':
- ek, ev = v.split(' : ', 1)
- extra[ek] = urllib.unquote(ev)
- data = data[:i]
-
- ctx = context.memctx(repo, (p1, p2), data,
- files.keys(), getfilectx,
- user, (date, tz), extra)
-
- tmp = encoding.encoding
- encoding.encoding = 'utf-8'
-
- node = repo.commitctx(ctx)
-
- encoding.encoding = tmp
-
- rev = repo[node].rev()
-
- parsed_refs[ref] = node
-
- marks.new_mark(rev, commit_mark)
-
-def parse_reset(parser):
- ref = parser[1]
- parser.next()
- # ugh
- if parser.check('commit'):
- parse_commit(parser)
- return
- if not parser.check('from'):
- return
- from_mark = parser.get_mark()
- parser.next()
-
- node = parser.repo.changelog.node(mark_to_rev(from_mark))
- parsed_refs[ref] = node
-
-def parse_tag(parser):
- name = parser[1]
- parser.next()
- from_mark = parser.get_mark()
- parser.next()
- tagger = parser.get_author()
- parser.next()
- data = parser.get_data()
- parser.next()
-
- # nothing to do
-
-def do_export(parser):
- global parsed_refs, bmarks, peer
-
- parser.next()
-
- for line in parser.each_block('done'):
- if parser.check('blob'):
- parse_blob(parser)
- elif parser.check('commit'):
- parse_commit(parser)
- elif parser.check('reset'):
- parse_reset(parser)
- elif parser.check('tag'):
- parse_tag(parser)
- elif parser.check('feature'):
- pass
- else:
- die('unhandled export command: %s' % line)
-
- for ref, node in parsed_refs.iteritems():
- if ref.startswith('refs/heads/branches'):
- pass
- elif ref.startswith('refs/heads/'):
- bmark = ref[len('refs/heads/'):]
- if bmark in bmarks:
- old = bmarks[bmark].hex()
- else:
- old = ''
- if not bookmarks.pushbookmark(parser.repo, bmark, old, node):
- continue
- elif ref.startswith('refs/tags/'):
- tag = ref[len('refs/tags/'):]
- parser.repo.tag([tag], node, None, True, None, {})
- else:
- # transport-helper/fast-export bugs
- continue
- print "ok %s" % ref
-
- print
-
- if peer:
- parser.repo.push(peer, force=False)
-
-def main(args):
- global prefix, dirname, branches, bmarks
- global marks, blob_marks, parsed_refs
- global peer, mode, bad_mail, bad_name
- global track_branches
-
- alias = args[1]
- url = args[2]
- peer = None
-
- hg_git_compat = False
- track_branches = True
- try:
- if get_config('remote-hg.hg-git-compat') == 'true\n':
- hg_git_compat = True
- track_branches = False
- if get_config('remote-hg.track-branches') == 'false\n':
- track_branches = False
- except subprocess.CalledProcessError:
- pass
-
- if hg_git_compat:
- mode = 'hg'
- bad_mail = 'none@none'
- bad_name = ''
- else:
- mode = 'git'
- bad_mail = 'unknown'
- bad_name = 'Unknown'
-
- if alias[4:] == url:
- is_tmp = True
- alias = util.sha1(alias).hexdigest()
- else:
- is_tmp = False
-
- gitdir = os.environ['GIT_DIR']
- dirname = os.path.join(gitdir, 'hg', alias)
- branches = {}
- bmarks = {}
- blob_marks = {}
- parsed_refs = {}
-
- repo = get_repo(url, alias)
- prefix = 'refs/hg/%s' % alias
-
- if not os.path.exists(dirname):
- os.makedirs(dirname)
-
- marks_path = os.path.join(dirname, 'marks-hg')
- marks = Marks(marks_path)
-
- parser = Parser(repo)
- for line in parser:
- if parser.check('capabilities'):
- do_capabilities(parser)
- elif parser.check('list'):
- do_list(parser)
- elif parser.check('import'):
- do_import(parser)
- elif parser.check('export'):
- do_export(parser)
- else:
- die('unhandled command: %s' % line)
- sys.stdout.flush()
-
- if not is_tmp:
- marks.store()
- else:
- shutil.rmtree(dirname)
-
-sys.exit(main(sys.argv))
+#!/bin/sh
+
+cat >&2 <<'EOT'
+WARNING: git-remote-hg is now maintained independently.
+WARNING: For more information visit https://github.com/felipec/git-remote-hg
+WARNING:
+WARNING: You can pick a directory on your $PATH and download it, e.g.:
+WARNING: $ wget -O $HOME/bin/git-remote-hg \
+WARNING: https://raw.github.com/felipec/git-remote-hg/master/git-remote-hg
+WARNING: $ chmod +x $HOME/bin/git-remote-hg
+EOT
diff --git a/contrib/remote-helpers/test-hg-bidi.sh b/contrib/remote-helpers/test-hg-bidi.sh
deleted file mode 100755
index 1d61982436..0000000000
--- a/contrib/remote-helpers/test-hg-bidi.sh
+++ /dev/null
@@ -1,243 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2012 Felipe Contreras
-#
-# Base commands from hg-git tests:
-# https://bitbucket.org/durin42/hg-git/src
-#
-
-test_description='Test bidirectionality of remote-hg'
-
-. ./test-lib.sh
-
-if ! test_have_prereq PYTHON; then
- skip_all='skipping remote-hg tests; python not available'
- test_done
-fi
-
-if ! "$PYTHON_PATH" -c 'import mercurial'; then
- skip_all='skipping remote-hg tests; mercurial not available'
- test_done
-fi
-
-# clone to a git repo
-git_clone () {
- hg -R $1 bookmark -f -r tip master &&
- git clone -q "hg::$PWD/$1" $2
-}
-
-# clone to an hg repo
-hg_clone () {
- (
- hg init $2 &&
- cd $1 &&
- git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
- ) &&
-
- (cd $2 && hg -q update)
-}
-
-# push an hg repo
-hg_push () {
- (
- cd $2
- old=$(git symbolic-ref --short HEAD)
- git checkout -q -b tmp &&
- git fetch -q "hg::$PWD/../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
- git checkout -q $old &&
- git branch -q -D tmp 2> /dev/null || true
- )
-}
-
-hg_log () {
- hg -R $1 log --graph --debug | grep -v 'tag: *default/'
-}
-
-setup () {
- (
- echo "[ui]"
- echo "username = A U Thor <author@example.com>"
- echo "[defaults]"
- echo "backout = -d \"0 0\""
- echo "commit = -d \"0 0\""
- echo "debugrawcommit = -d \"0 0\""
- echo "tag = -d \"0 0\""
- ) >> "$HOME"/.hgrc &&
- git config --global remote-hg.hg-git-compat true
-
- export HGEDITOR=/usr/bin/true
-
- export GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230"
- export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
-}
-
-setup
-
-test_expect_success 'encoding' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
-
- echo alpha > alpha &&
- git add alpha &&
- git commit -m "add älphà" &&
-
- export GIT_AUTHOR_NAME="tést èncödîng" &&
- echo beta > beta &&
- git add beta &&
- git commit -m "add beta" &&
-
- echo gamma > gamma &&
- git add gamma &&
- git commit -m "add gämmâ" &&
-
- : TODO git config i18n.commitencoding latin-1 &&
- echo delta > delta &&
- git add delta &&
- git commit -m "add déltà"
- ) &&
-
- hg_clone gitrepo hgrepo &&
- git_clone hgrepo gitrepo2 &&
- hg_clone gitrepo2 hgrepo2 &&
-
- HGENCODING=utf-8 hg_log hgrepo > expected &&
- HGENCODING=utf-8 hg_log hgrepo2 > actual &&
-
- test_cmp expected actual
-'
-
-test_expect_success 'file removal' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
- echo alpha > alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- echo beta > beta &&
- git add beta &&
- git commit -m "add beta"
- mkdir foo &&
- echo blah > foo/bar &&
- git add foo &&
- git commit -m "add foo" &&
- git rm alpha &&
- git commit -m "remove alpha" &&
- git rm foo/bar &&
- git commit -m "remove foo/bar"
- ) &&
-
- hg_clone gitrepo hgrepo &&
- git_clone hgrepo gitrepo2 &&
- hg_clone gitrepo2 hgrepo2 &&
-
- hg_log hgrepo > expected &&
- hg_log hgrepo2 > actual &&
-
- test_cmp expected actual
-'
-
-test_expect_success 'git tags' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
- git config receive.denyCurrentBranch ignore &&
- echo alpha > alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- git tag alpha &&
-
- echo beta > beta &&
- git add beta &&
- git commit -m "add beta" &&
- git tag -a -m "added tag beta" beta
- ) &&
-
- hg_clone gitrepo hgrepo &&
- git_clone hgrepo gitrepo2 &&
- hg_clone gitrepo2 hgrepo2 &&
-
- hg_log hgrepo > expected &&
- hg_log hgrepo2 > actual &&
-
- test_cmp expected actual
-'
-
-test_expect_success 'hg branch' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
-
- echo alpha > alpha &&
- git add alpha &&
- git commit -q -m "add alpha" &&
- git checkout -q -b not-master
- ) &&
-
- (
- hg_clone gitrepo hgrepo &&
-
- cd hgrepo &&
- hg -q co master &&
- hg mv alpha beta &&
- hg -q commit -m "rename alpha to beta" &&
- hg branch gamma | grep -v "permanent and global" &&
- hg -q commit -m "started branch gamma"
- ) &&
-
- hg_push hgrepo gitrepo &&
- hg_clone gitrepo hgrepo2 &&
-
- : TODO, avoid "master" bookmark &&
- (cd hgrepo2 && hg checkout gamma) &&
-
- hg_log hgrepo > expected &&
- hg_log hgrepo2 > actual &&
-
- test_cmp expected actual
-'
-
-test_expect_success 'hg tags' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
-
- echo alpha > alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- git checkout -q -b not-master
- ) &&
-
- (
- hg_clone gitrepo hgrepo &&
-
- cd hgrepo &&
- hg co master &&
- hg tag alpha
- ) &&
-
- hg_push hgrepo gitrepo &&
- hg_clone gitrepo hgrepo2 &&
-
- hg_log hgrepo > expected &&
- hg_log hgrepo2 > actual &&
-
- test_cmp expected actual
-'
-
-test_done
diff --git a/contrib/remote-helpers/test-hg-hg-git.sh b/contrib/remote-helpers/test-hg-hg-git.sh
deleted file mode 100755
index 3e76d9fb60..0000000000
--- a/contrib/remote-helpers/test-hg-hg-git.sh
+++ /dev/null
@@ -1,466 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2012 Felipe Contreras
-#
-# Base commands from hg-git tests:
-# https://bitbucket.org/durin42/hg-git/src
-#
-
-test_description='Test remote-hg output compared to hg-git'
-
-. ./test-lib.sh
-
-if ! test_have_prereq PYTHON; then
- skip_all='skipping remote-hg tests; python not available'
- test_done
-fi
-
-if ! "$PYTHON_PATH" -c 'import mercurial'; then
- skip_all='skipping remote-hg tests; mercurial not available'
- test_done
-fi
-
-if ! "$PYTHON_PATH" -c 'import hggit'; then
- skip_all='skipping remote-hg tests; hg-git not available'
- test_done
-fi
-
-# clone to a git repo with git
-git_clone_git () {
- hg -R $1 bookmark -f -r tip master &&
- git clone -q "hg::$PWD/$1" $2
-}
-
-# clone to an hg repo with git
-hg_clone_git () {
- (
- hg init $2 &&
- cd $1 &&
- git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
- ) &&
-
- (cd $2 && hg -q update)
-}
-
-# clone to a git repo with hg
-git_clone_hg () {
- (
- git init -q $2 &&
- cd $1 &&
- hg bookmark -f -r tip master &&
- hg -q push -r master ../$2 || true
- )
-}
-
-# clone to an hg repo with hg
-hg_clone_hg () {
- hg -q clone $1 $2
-}
-
-# push an hg repo with git
-hg_push_git () {
- (
- cd $2
- old=$(git symbolic-ref --short HEAD)
- git checkout -q -b tmp &&
- git fetch -q "hg::$PWD/../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
- git checkout -q $old &&
- git branch -q -D tmp 2> /dev/null || true
- )
-}
-
-# push an hg git repo with hg
-hg_push_hg () {
- (
- cd $1 &&
- hg -q push ../$2 || true
- )
-}
-
-hg_log () {
- hg -R $1 log --graph --debug | grep -v 'tag: *default/'
-}
-
-git_log () {
- git --git-dir=$1/.git fast-export --branches
-}
-
-setup () {
- (
- echo "[ui]"
- echo "username = A U Thor <author@example.com>"
- echo "[defaults]"
- echo "backout = -d \"0 0\""
- echo "commit = -d \"0 0\""
- echo "debugrawcommit = -d \"0 0\""
- echo "tag = -d \"0 0\""
- echo "[extensions]"
- echo "hgext.bookmarks ="
- echo "hggit ="
- ) >> "$HOME"/.hgrc &&
- git config --global receive.denycurrentbranch warn
- git config --global remote-hg.hg-git-compat true
-
- export HGEDITOR=/usr/bin/true
-
- export GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230"
- export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
-}
-
-setup
-
-test_expect_success 'merge conflict 1' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- hg init hgrepo1 &&
- cd hgrepo1 &&
- echo A > afile &&
- hg add afile &&
- hg ci -m "origin" &&
-
- echo B > afile &&
- hg ci -m "A->B" &&
-
- hg up -r0 &&
- echo C > afile &&
- hg ci -m "A->C" &&
-
- hg merge -r1 || true &&
- echo C > afile &&
- hg resolve -m afile &&
- hg ci -m "merge to C"
- ) &&
-
- for x in hg git; do
- git_clone_$x hgrepo1 gitrepo-$x &&
- hg_clone_$x gitrepo-$x hgrepo2-$x &&
- hg_log hgrepo2-$x > hg-log-$x &&
- git_log gitrepo-$x > git-log-$x
- done &&
-
- test_cmp hg-log-hg hg-log-git &&
- test_cmp git-log-hg git-log-git
-'
-
-test_expect_success 'merge conflict 2' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- hg init hgrepo1 &&
- cd hgrepo1 &&
- echo A > afile &&
- hg add afile &&
- hg ci -m "origin" &&
-
- echo B > afile &&
- hg ci -m "A->B" &&
-
- hg up -r0 &&
- echo C > afile &&
- hg ci -m "A->C" &&
-
- hg merge -r1 || true &&
- echo B > afile &&
- hg resolve -m afile &&
- hg ci -m "merge to B"
- ) &&
-
- for x in hg git; do
- git_clone_$x hgrepo1 gitrepo-$x &&
- hg_clone_$x gitrepo-$x hgrepo2-$x &&
- hg_log hgrepo2-$x > hg-log-$x &&
- git_log gitrepo-$x > git-log-$x
- done &&
-
- test_cmp hg-log-hg hg-log-git &&
- test_cmp git-log-hg git-log-git
-'
-
-test_expect_success 'converged merge' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- hg init hgrepo1 &&
- cd hgrepo1 &&
- echo A > afile &&
- hg add afile &&
- hg ci -m "origin" &&
-
- echo B > afile &&
- hg ci -m "A->B" &&
-
- echo C > afile &&
- hg ci -m "B->C" &&
-
- hg up -r0 &&
- echo C > afile &&
- hg ci -m "A->C" &&
-
- hg merge -r2 || true &&
- hg ci -m "merge"
- ) &&
-
- for x in hg git; do
- git_clone_$x hgrepo1 gitrepo-$x &&
- hg_clone_$x gitrepo-$x hgrepo2-$x &&
- hg_log hgrepo2-$x > hg-log-$x &&
- git_log gitrepo-$x > git-log-$x
- done &&
-
- test_cmp hg-log-hg hg-log-git &&
- test_cmp git-log-hg git-log-git
-'
-
-test_expect_success 'encoding' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
-
- echo alpha > alpha &&
- git add alpha &&
- git commit -m "add älphà" &&
-
- export GIT_AUTHOR_NAME="tést èncödîng" &&
- echo beta > beta &&
- git add beta &&
- git commit -m "add beta" &&
-
- echo gamma > gamma &&
- git add gamma &&
- git commit -m "add gämmâ" &&
-
- : TODO git config i18n.commitencoding latin-1 &&
- echo delta > delta &&
- git add delta &&
- git commit -m "add déltà"
- ) &&
-
- for x in hg git; do
- hg_clone_$x gitrepo hgrepo-$x &&
- git_clone_$x hgrepo-$x gitrepo2-$x &&
-
- HGENCODING=utf-8 hg_log hgrepo-$x > hg-log-$x &&
- git_log gitrepo2-$x > git-log-$x
- done &&
-
- test_cmp hg-log-hg hg-log-git &&
- test_cmp git-log-hg git-log-git
-'
-
-test_expect_success 'file removal' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
- echo alpha > alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- echo beta > beta &&
- git add beta &&
- git commit -m "add beta"
- mkdir foo &&
- echo blah > foo/bar &&
- git add foo &&
- git commit -m "add foo" &&
- git rm alpha &&
- git commit -m "remove alpha" &&
- git rm foo/bar &&
- git commit -m "remove foo/bar"
- ) &&
-
- for x in hg git; do
- (
- hg_clone_$x gitrepo hgrepo-$x &&
- cd hgrepo-$x &&
- hg_log . &&
- hg manifest -r 3 &&
- hg manifest
- ) > output-$x &&
-
- git_clone_$x hgrepo-$x gitrepo2-$x &&
- git_log gitrepo2-$x > log-$x
- done &&
-
- test_cmp output-hg output-git &&
- test_cmp log-hg log-git
-'
-
-test_expect_success 'git tags' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- (
- git init -q gitrepo &&
- cd gitrepo &&
- git config receive.denyCurrentBranch ignore &&
- echo alpha > alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- git tag alpha &&
-
- echo beta > beta &&
- git add beta &&
- git commit -m "add beta" &&
- git tag -a -m "added tag beta" beta
- ) &&
-
- for x in hg git; do
- hg_clone_$x gitrepo hgrepo-$x &&
- hg_log hgrepo-$x > log-$x
- done &&
-
- test_cmp log-hg log-git
-'
-
-test_expect_success 'hg author' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- for x in hg git; do
- (
- git init -q gitrepo-$x &&
- cd gitrepo-$x &&
-
- echo alpha > alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- git checkout -q -b not-master
- ) &&
-
- (
- hg_clone_$x gitrepo-$x hgrepo-$x &&
- cd hgrepo-$x &&
-
- hg co master &&
- echo beta > beta &&
- hg add beta &&
- hg commit -u "test" -m "add beta" &&
-
- echo gamma >> beta &&
- hg commit -u "test <test@example.com> (comment)" -m "modify beta" &&
-
- echo gamma > gamma &&
- hg add gamma &&
- hg commit -u "<test@example.com>" -m "add gamma" &&
-
- echo delta > delta &&
- hg add delta &&
- hg commit -u "name<test@example.com>" -m "add delta" &&
-
- echo epsilon > epsilon &&
- hg add epsilon &&
- hg commit -u "name <test@example.com" -m "add epsilon" &&
-
- echo zeta > zeta &&
- hg add zeta &&
- hg commit -u " test " -m "add zeta" &&
-
- echo eta > eta &&
- hg add eta &&
- hg commit -u "test < test@example.com >" -m "add eta" &&
-
- echo theta > theta &&
- hg add theta &&
- hg commit -u "test >test@example.com>" -m "add theta" &&
-
- echo iota > iota &&
- hg add iota &&
- hg commit -u "test <test <at> example <dot> com>" -m "add iota"
- ) &&
-
- hg_push_$x hgrepo-$x gitrepo-$x &&
- hg_clone_$x gitrepo-$x hgrepo2-$x &&
-
- hg_log hgrepo2-$x > hg-log-$x &&
- git_log gitrepo-$x > git-log-$x
- done &&
-
- test_cmp git-log-hg git-log-git &&
-
- test_cmp hg-log-hg hg-log-git &&
- test_cmp git-log-hg git-log-git
-'
-
-test_expect_success 'hg branch' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- for x in hg git; do
- (
- git init -q gitrepo-$x &&
- cd gitrepo-$x &&
-
- echo alpha > alpha &&
- git add alpha &&
- git commit -q -m "add alpha" &&
- git checkout -q -b not-master
- ) &&
-
- (
- hg_clone_$x gitrepo-$x hgrepo-$x &&
-
- cd hgrepo-$x &&
- hg -q co master &&
- hg mv alpha beta &&
- hg -q commit -m "rename alpha to beta" &&
- hg branch gamma | grep -v "permanent and global" &&
- hg -q commit -m "started branch gamma"
- ) &&
-
- hg_push_$x hgrepo-$x gitrepo-$x &&
- hg_clone_$x gitrepo-$x hgrepo2-$x &&
-
- hg_log hgrepo2-$x > hg-log-$x &&
- git_log gitrepo-$x > git-log-$x
- done &&
-
- test_cmp hg-log-hg hg-log-git &&
- test_cmp git-log-hg git-log-git
-'
-
-test_expect_success 'hg tags' '
- mkdir -p tmp && cd tmp &&
- test_when_finished "cd .. && rm -rf tmp" &&
-
- for x in hg git; do
- (
- git init -q gitrepo-$x &&
- cd gitrepo-$x &&
-
- echo alpha > alpha &&
- git add alpha &&
- git commit -m "add alpha" &&
- git checkout -q -b not-master
- ) &&
-
- (
- hg_clone_$x gitrepo-$x hgrepo-$x &&
-
- cd hgrepo-$x &&
- hg co master &&
- hg tag alpha
- ) &&
-
- hg_push_$x hgrepo-$x gitrepo-$x &&
- hg_clone_$x gitrepo-$x hgrepo2-$x &&
-
- (
- git --git-dir=gitrepo-$x/.git tag -l &&
- hg_log hgrepo2-$x &&
- cat hgrepo2-$x/.hgtags
- ) > output-$x
- done &&
-
- test_cmp output-hg output-git
-'
-
-test_done
diff --git a/contrib/remote-helpers/test-hg.sh b/contrib/remote-helpers/test-hg.sh
deleted file mode 100755
index 7bb81f2f8e..0000000000
--- a/contrib/remote-helpers/test-hg.sh
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2012 Felipe Contreras
-#
-# Base commands from hg-git tests:
-# https://bitbucket.org/durin42/hg-git/src
-#
-
-test_description='Test remote-hg'
-
-. ./test-lib.sh
-
-if ! test_have_prereq PYTHON; then
- skip_all='skipping remote-hg tests; python not available'
- test_done
-fi
-
-if ! "$PYTHON_PATH" -c 'import mercurial'; then
- skip_all='skipping remote-hg tests; mercurial not available'
- test_done
-fi
-
-check () {
- (cd $1 &&
- git log --format='%s' -1 &&
- git symbolic-ref HEAD) > actual &&
- (echo $2 &&
- echo "refs/heads/$3") > expected &&
- test_cmp expected actual
-}
-
-setup () {
- (
- echo "[ui]"
- echo "username = H G Wells <wells@example.com>"
- ) >> "$HOME"/.hgrc
-}
-
-setup
-
-test_expect_success 'cloning' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- hg init hgrepo &&
- cd hgrepo &&
- echo zero > content &&
- hg add content &&
- hg commit -m zero
- ) &&
-
- git clone "hg::$PWD/hgrepo" gitrepo &&
- check gitrepo zero master
-'
-
-test_expect_success 'cloning with branches' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- cd hgrepo &&
- hg branch next &&
- echo next > content &&
- hg commit -m next
- ) &&
-
- git clone "hg::$PWD/hgrepo" gitrepo &&
- check gitrepo next next &&
-
- (cd hgrepo && hg checkout default) &&
-
- git clone "hg::$PWD/hgrepo" gitrepo2 &&
- check gitrepo2 zero master
-'
-
-test_expect_success 'cloning with bookmarks' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- cd hgrepo &&
- hg bookmark feature-a &&
- echo feature-a > content &&
- hg commit -m feature-a
- ) &&
-
- git clone "hg::$PWD/hgrepo" gitrepo &&
- check gitrepo feature-a feature-a
-'
-
-test_expect_success 'cloning with detached head' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- cd hgrepo &&
- hg update -r 0
- ) &&
-
- git clone "hg::$PWD/hgrepo" gitrepo &&
- check gitrepo zero master
-'
-
-test_expect_success 'update bookmark' '
- test_when_finished "rm -rf gitrepo*" &&
-
- (
- cd hgrepo &&
- hg bookmark devel
- ) &&
-
- (
- git clone "hg::$PWD/hgrepo" gitrepo &&
- cd gitrepo &&
- git checkout devel &&
- echo devel > content &&
- git commit -a -m devel &&
- git push
- ) &&
-
- hg -R hgrepo bookmarks | egrep "devel[ ]+3:"
-'
-
-test_done
diff --git a/contrib/rerere-train.sh b/contrib/rerere-train.sh
index 36b6feebe0..75125d6ae0 100755
--- a/contrib/rerere-train.sh
+++ b/contrib/rerere-train.sh
@@ -3,11 +3,57 @@
# Prime rerere database from existing merge commits
me=rerere-train
-USAGE="$me rev-list-args"
+USAGE=$(cat <<-EOF
+usage: $me [--overwrite] <rev-list-args>
+
+ -h, --help show the help
+ -o, --overwrite overwrite any existing rerere cache
+EOF
+)
SUBDIRECTORY_OK=Yes
-OPTIONS_SPEC=
-. $(git --exec-path)/git-sh-setup
+
+overwrite=0
+
+while test $# -gt 0
+do
+ opt="$1"
+ case "$opt" in
+ -h|--help)
+ echo "$USAGE"
+ exit 0
+ ;;
+ -o|--overwrite)
+ overwrite=1
+ shift
+ break
+ ;;
+ --)
+ shift
+ break
+ ;;
+ *)
+ break
+ ;;
+ esac
+done
+
+# Overwrite or help options are not valid except as first arg
+for opt in "$@"
+do
+ case "$opt" in
+ -h|--help)
+ echo "$USAGE"
+ exit 0
+ ;;
+ -o|--overwrite)
+ echo "$USAGE"
+ exit 0
+ ;;
+ esac
+done
+
+. "$(git --exec-path)/git-sh-setup"
require_work_tree
cd_to_toplevel
@@ -34,6 +80,10 @@ do
# Cleanly merges
continue
fi
+ if test $overwrite = 1
+ then
+ git rerere forget .
+ fi
if test -s "$GIT_DIR/MERGE_RR"
then
git show -s --pretty=format:"Learning from %h %s" "$commit"
@@ -41,7 +91,7 @@ do
git checkout -q $commit -- .
git rerere
fi
- git reset -q --hard
+ git reset -q --hard # Might nuke untracked files...
done
if test -z "$branch"
diff --git a/contrib/subtree/.gitignore b/contrib/subtree/.gitignore
index 91360a3d7f..0b9381abca 100644
--- a/contrib/subtree/.gitignore
+++ b/contrib/subtree/.gitignore
@@ -1,6 +1,7 @@
*~
git-subtree
-git-subtree.xml
git-subtree.1
+git-subtree.html
+git-subtree.xml
mainline
subproj
diff --git a/contrib/subtree/Makefile b/contrib/subtree/Makefile
index 05cdd5c9b2..6fa7496bfd 100644
--- a/contrib/subtree/Makefile
+++ b/contrib/subtree/Makefile
@@ -1,19 +1,48 @@
+# The default target of this Makefile is...
+all::
+
-include ../../config.mak.autogen
-include ../../config.mak
prefix ?= /usr/local
+gitexecdir ?= $(prefix)/libexec/git-core
mandir ?= $(prefix)/share/man
-libexecdir ?= $(prefix)/libexec/git-core
-gitdir ?= $(shell git --exec-path)
man1dir ?= $(mandir)/man1
+htmldir ?= $(prefix)/share/doc/git-doc
-gitver ?= $(word 3,$(shell git --version))
+../../GIT-VERSION-FILE: FORCE
+ $(MAKE) -C ../../ GIT-VERSION-FILE
-# this should be set to a 'standard' bsd-type install program
-INSTALL ?= install
+-include ../../GIT-VERSION-FILE
-ASCIIDOC_CONF = ../../Documentation/asciidoc.conf
-MANPAGE_NORMAL_XSL = ../../Documentation/manpage-normal.xsl
+# this should be set to a 'standard' bsd-type install program
+INSTALL ?= install
+RM ?= rm -f
+
+ASCIIDOC = asciidoc
+ASCIIDOC_CONF = -f ../../Documentation/asciidoc.conf
+ASCIIDOC_HTML = xhtml11
+ASCIIDOC_DOCBOOK = docbook
+ASCIIDOC_EXTRA =
+XMLTO = xmlto
+XMLTO_EXTRA =
+
+ifdef USE_ASCIIDOCTOR
+ASCIIDOC = asciidoctor
+ASCIIDOC_CONF =
+ASCIIDOC_HTML = xhtml5
+ASCIIDOC_DOCBOOK = docbook
+ASCIIDOC_EXTRA += -I../../Documentation -rasciidoctor-extensions
+ASCIIDOC_EXTRA += -alitdd='&\#x2d;&\#x2d;'
+XMLTO_EXTRA += --skip-validation
+endif
+
+ifndef SHELL_PATH
+ SHELL_PATH = /bin/sh
+endif
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+
+MANPAGE_XSL = ../../Documentation/manpage-normal.xsl
GIT_SUBTREE_SH := git-subtree.sh
GIT_SUBTREE := git-subtree
@@ -21,32 +50,54 @@ GIT_SUBTREE := git-subtree
GIT_SUBTREE_DOC := git-subtree.1
GIT_SUBTREE_XML := git-subtree.xml
GIT_SUBTREE_TXT := git-subtree.txt
+GIT_SUBTREE_HTML := git-subtree.html
+GIT_SUBTREE_TEST := ../../git-subtree
-all: $(GIT_SUBTREE)
+all:: $(GIT_SUBTREE)
$(GIT_SUBTREE): $(GIT_SUBTREE_SH)
- cp $< $@ && chmod +x $@
+ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' $< >$@
+ chmod +x $@
+
+doc: $(GIT_SUBTREE_DOC) $(GIT_SUBTREE_HTML)
+
+man: $(GIT_SUBTREE_DOC)
-doc: $(GIT_SUBTREE_DOC)
+html: $(GIT_SUBTREE_HTML)
install: $(GIT_SUBTREE)
- $(INSTALL) -m 755 $(GIT_SUBTREE) $(libexecdir)
+ $(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir)
+ $(INSTALL) -m 755 $(GIT_SUBTREE) $(DESTDIR)$(gitexecdir)
-install-doc: install-man
+install-doc: install-man install-html
install-man: $(GIT_SUBTREE_DOC)
- $(INSTALL) -m 644 $^ $(man1dir)
+ $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir)
+ $(INSTALL) -m 644 $^ $(DESTDIR)$(man1dir)
+
+install-html: $(GIT_SUBTREE_HTML)
+ $(INSTALL) -d -m 755 $(DESTDIR)$(htmldir)
+ $(INSTALL) -m 644 $^ $(DESTDIR)$(htmldir)
$(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML)
- xmlto -m $(MANPAGE_NORMAL_XSL) man $^
+ $(XMLTO) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $^
$(GIT_SUBTREE_XML): $(GIT_SUBTREE_TXT)
- asciidoc -b docbook -d manpage -f $(ASCIIDOC_CONF) \
- -agit_version=$(gitver) $^
+ $(ASCIIDOC) -b $(ASCIIDOC_DOCBOOK) -d manpage $(ASCIIDOC_CONF) \
+ -agit_version=$(GIT_VERSION) $(ASCIIDOC_EXTRA) $^
+
+$(GIT_SUBTREE_HTML): $(GIT_SUBTREE_TXT)
+ $(ASCIIDOC) -b $(ASCIIDOC_HTML) -d manpage $(ASCIIDOC_CONF) \
+ -agit_version=$(GIT_VERSION) $(ASCIIDOC_EXTRA) $^
-test:
+$(GIT_SUBTREE_TEST): $(GIT_SUBTREE)
+ cp $< $@
+
+test: $(GIT_SUBTREE_TEST)
$(MAKE) -C t/ test
clean:
- rm -f *~ *.xml *.html *.1
- rm -rf subproj mainline
+ $(RM) $(GIT_SUBTREE)
+ $(RM) *.xml *.html *.1
+
+.PHONY: FORCE
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 920c664bb7..7f767b5c38 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -1,302 +1,469 @@
-#!/bin/bash
+#!/bin/sh
#
# git-subtree.sh: split/join git repositories in subdirectories of this one
#
# Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
#
-if [ $# -eq 0 ]; then
- set -- -h
+
+if test -z "$GIT_EXEC_PATH" || ! test -f "$GIT_EXEC_PATH/git-sh-setup" || {
+ test "${PATH#"${GIT_EXEC_PATH}:"}" = "$PATH" &&
+ test ! "$GIT_EXEC_PATH" -ef "${PATH%%:*}" 2>/dev/null
+}
+then
+ basename=${0##*[/\\]}
+ echo >&2 'It looks like either your git installation or your'
+ echo >&2 'git-subtree installation is broken.'
+ echo >&2
+ echo >&2 "Tips:"
+ echo >&2 " - If \`git --exec-path\` does not print the correct path to"
+ echo >&2 " your git install directory, then set the GIT_EXEC_PATH"
+ echo >&2 " environment variable to the correct directory."
+ echo >&2 " - Make sure that your \`$basename\` file is either in your"
+ echo >&2 " PATH or in your git exec path (\`$(git --exec-path)\`)."
+ echo >&2 " - You should run git-subtree as \`git ${basename#git-}\`,"
+ echo >&2 " not as \`$basename\`." >&2
+ exit 126
fi
+
OPTS_SPEC="\
git subtree add --prefix=<prefix> <commit>
+git subtree add --prefix=<prefix> <repository> <ref>
git subtree merge --prefix=<prefix> <commit>
-git subtree pull --prefix=<prefix> <repository> <refspec...>
-git subtree push --prefix=<prefix> <repository> <refspec...>
-git subtree split --prefix=<prefix> <commit...>
+git subtree split --prefix=<prefix> [<commit>]
+git subtree pull --prefix=<prefix> <repository> <ref>
+git subtree push --prefix=<prefix> <repository> <refspec>
--
h,help show the help
q quiet
d show debug messages
P,prefix= the name of the subdir to split out
-m,message= use the given message as the commit message for the merge commit
- options for 'split'
+ options for 'split' (also: 'push')
annotate= add a prefix to commit message of new commits
b,branch= create a new branch from the split subtree
ignore-joins ignore prior --rejoin commits
onto= try connecting new tree to an existing one
rejoin merge the new branch back into HEAD
- options for 'add', 'merge', 'pull' and 'push'
+ options for 'add' and 'merge' (also: 'pull', 'split --rejoin', and 'push --rejoin')
squash merge subtree changes as a single commit
+m,message= use the given message as the commit message for the merge commit
"
-eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
-
-PATH=$PATH:$(git --exec-path)
-. git-sh-setup
-require_work_tree
+indent=0
-quiet=
-branch=
-debug=
-command=
-onto=
-rejoin=
-ignore_joins=
-annotate=
-squash=
-message=
-
-debug()
-{
- if [ -n "$debug" ]; then
- echo "$@" >&2
+# Usage: debug [MSG...]
+debug () {
+ if test -n "$arg_debug"
+ then
+ printf "%$(($indent * 2))s%s\n" '' "$*" >&2
fi
}
-say()
-{
- if [ -z "$quiet" ]; then
- echo "$@" >&2
+# Usage: progress [MSG...]
+progress () {
+ if test -z "$GIT_QUIET"
+ then
+ if test -z "$arg_debug"
+ then
+ # Debug mode is off.
+ #
+ # Print one progress line that we keep updating (use
+ # "\r" to return to the beginning of the line, rather
+ # than "\n" to start a new line). This only really
+ # works when stderr is a terminal.
+ printf "%s\r" "$*" >&2
+ else
+ # Debug mode is on. The `debug` function is regularly
+ # printing to stderr.
+ #
+ # Don't do the one-line-with-"\r" thing, because on a
+ # terminal the debug output would overwrite and hide the
+ # progress output. Add a "progress:" prefix to make the
+ # progress output and the debug output easy to
+ # distinguish. This ensures maximum readability whether
+ # stderr is a terminal or a file.
+ printf "progress: %s\n" "$*" >&2
+ fi
fi
}
-assert()
-{
- if "$@"; then
- :
- else
- die "assertion failed: " "$@"
+# Usage: assert CMD...
+assert () {
+ if ! "$@"
+ then
+ die "assertion failed: $*"
fi
}
+main () {
+ if test $# -eq 0
+ then
+ set -- -h
+ fi
+ set_args="$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
+ eval "$set_args"
+ . git-sh-setup
+ require_work_tree
-#echo "Options: $*"
-
-while [ $# -gt 0 ]; do
- opt="$1"
- shift
- case "$opt" in
- -q) quiet=1 ;;
- -d) debug=1 ;;
- --annotate) annotate="$1"; shift ;;
- --no-annotate) annotate= ;;
- -b) branch="$1"; shift ;;
- -P) prefix="$1"; shift ;;
- -m) message="$1"; shift ;;
- --no-prefix) prefix= ;;
- --onto) onto="$1"; shift ;;
- --no-onto) onto= ;;
- --rejoin) rejoin=1 ;;
- --no-rejoin) rejoin= ;;
- --ignore-joins) ignore_joins=1 ;;
- --no-ignore-joins) ignore_joins= ;;
- --squash) squash=1 ;;
- --no-squash) squash= ;;
- --) break ;;
- *) die "Unexpected option: $opt" ;;
+ # First figure out the command and whether we use --rejoin, so
+ # that we can provide more helpful validation when we do the
+ # "real" flag parsing.
+ arg_split_rejoin=
+ allow_split=
+ allow_addmerge=
+ while test $# -gt 0
+ do
+ opt="$1"
+ shift
+ case "$opt" in
+ --annotate|-b|-P|-m|--onto)
+ shift
+ ;;
+ --rejoin)
+ arg_split_rejoin=1
+ ;;
+ --no-rejoin)
+ arg_split_rejoin=
+ ;;
+ --)
+ break
+ ;;
+ esac
+ done
+ arg_command=$1
+ case "$arg_command" in
+ add|merge|pull)
+ allow_addmerge=1
+ ;;
+ split|push)
+ allow_split=1
+ allow_addmerge=$arg_split_rejoin
+ ;;
+ *)
+ die "Unknown command '$arg_command'"
+ ;;
esac
-done
-
-command="$1"
-shift
-case "$command" in
- add|merge|pull) default= ;;
- split|push) default="--default HEAD" ;;
- *) die "Unknown command '$command'" ;;
-esac
-
-if [ -z "$prefix" ]; then
- die "You must provide the --prefix option."
-fi
+ # Reset the arguments array for "real" flag parsing.
+ eval "$set_args"
-case "$command" in
- add) [ -e "$prefix" ] &&
- die "prefix '$prefix' already exists." ;;
- *) [ -e "$prefix" ] ||
- die "'$prefix' does not exist; use 'git subtree add'" ;;
-esac
+ # Begin "real" flag parsing.
+ arg_debug=
+ arg_prefix=
+ arg_split_branch=
+ arg_split_onto=
+ arg_split_ignore_joins=
+ arg_split_annotate=
+ arg_addmerge_squash=
+ arg_addmerge_message=
+ while test $# -gt 0
+ do
+ opt="$1"
+ shift
-dir="$(dirname "$prefix/.")"
+ case "$opt" in
+ -q)
+ GIT_QUIET=1
+ ;;
+ -d)
+ arg_debug=1
+ ;;
+ --annotate)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_annotate="$1"
+ shift
+ ;;
+ --no-annotate)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_annotate=
+ ;;
+ -b)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_branch="$1"
+ shift
+ ;;
+ -P)
+ arg_prefix="${1%/}"
+ shift
+ ;;
+ -m)
+ test -n "$allow_addmerge" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_addmerge_message="$1"
+ shift
+ ;;
+ --no-prefix)
+ arg_prefix=
+ ;;
+ --onto)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_onto="$1"
+ shift
+ ;;
+ --no-onto)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_onto=
+ ;;
+ --rejoin)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ ;;
+ --no-rejoin)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ ;;
+ --ignore-joins)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_ignore_joins=1
+ ;;
+ --no-ignore-joins)
+ test -n "$allow_split" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_split_ignore_joins=
+ ;;
+ --squash)
+ test -n "$allow_addmerge" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_addmerge_squash=1
+ ;;
+ --no-squash)
+ test -n "$allow_addmerge" || die "The '$opt' flag does not make sense with 'git subtree $arg_command'."
+ arg_addmerge_squash=
+ ;;
+ --)
+ break
+ ;;
+ *)
+ die "Unexpected option: $opt"
+ ;;
+ esac
+ done
+ shift
-if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" ]; then
- revs=$(git rev-parse $default --revs-only "$@") || exit $?
- dirs="$(git rev-parse --no-revs --no-flags "$@")" || exit $?
- if [ -n "$dirs" ]; then
- die "Error: Use --prefix instead of bare filenames."
+ if test -z "$arg_prefix"
+ then
+ die "You must provide the --prefix option."
fi
-fi
-debug "command: {$command}"
-debug "quiet: {$quiet}"
-debug "revs: {$revs}"
-debug "dir: {$dir}"
-debug "opts: {$*}"
-debug
+ case "$arg_command" in
+ add)
+ test -e "$arg_prefix" &&
+ die "prefix '$arg_prefix' already exists."
+ ;;
+ *)
+ test -e "$arg_prefix" ||
+ die "'$arg_prefix' does not exist; use 'git subtree add'"
+ ;;
+ esac
+
+ dir="$(dirname "$arg_prefix/.")"
+
+ debug "command: {$arg_command}"
+ debug "quiet: {$GIT_QUIET}"
+ debug "dir: {$dir}"
+ debug "opts: {$*}"
+ debug
-cache_setup()
-{
+ "cmd_$arg_command" "$@"
+}
+
+# Usage: cache_setup
+cache_setup () {
+ assert test $# = 0
cachedir="$GIT_DIR/subtree-cache/$$"
- rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
- mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
- mkdir -p "$cachedir/notree" || die "Can't create new cachedir: $cachedir/notree"
+ rm -rf "$cachedir" ||
+ die "Can't delete old cachedir: $cachedir"
+ mkdir -p "$cachedir" ||
+ die "Can't create new cachedir: $cachedir"
+ mkdir -p "$cachedir/notree" ||
+ die "Can't create new cachedir: $cachedir/notree"
debug "Using cachedir: $cachedir" >&2
}
-cache_get()
-{
- for oldrev in $*; do
- if [ -r "$cachedir/$oldrev" ]; then
+# Usage: cache_get [REVS...]
+cache_get () {
+ for oldrev in "$@"
+ do
+ if test -r "$cachedir/$oldrev"
+ then
read newrev <"$cachedir/$oldrev"
echo $newrev
fi
done
}
-cache_miss()
-{
- for oldrev in $*; do
- if [ ! -r "$cachedir/$oldrev" ]; then
+# Usage: cache_miss [REVS...]
+cache_miss () {
+ for oldrev in "$@"
+ do
+ if ! test -r "$cachedir/$oldrev"
+ then
echo $oldrev
fi
done
}
-check_parents()
-{
- missed=$(cache_miss $*)
- for miss in $missed; do
- if [ ! -r "$cachedir/notree/$miss" ]; then
- debug " incorrect order: $miss"
+# Usage: check_parents PARENTS_EXPR
+check_parents () {
+ assert test $# = 1
+ missed=$(cache_miss "$1") || exit $?
+ local indent=$(($indent + 1))
+ for miss in $missed
+ do
+ if ! test -r "$cachedir/notree/$miss"
+ then
+ debug "incorrect order: $miss"
+ process_split_commit "$miss" ""
fi
done
}
-set_notree()
-{
+# Usage: set_notree REV
+set_notree () {
+ assert test $# = 1
echo "1" > "$cachedir/notree/$1"
}
-cache_set()
-{
+# Usage: cache_set OLDREV NEWREV
+cache_set () {
+ assert test $# = 2
oldrev="$1"
newrev="$2"
- if [ "$oldrev" != "latest_old" \
- -a "$oldrev" != "latest_new" \
- -a -e "$cachedir/$oldrev" ]; then
+ if test "$oldrev" != "latest_old" &&
+ test "$oldrev" != "latest_new" &&
+ test -e "$cachedir/$oldrev"
+ then
die "cache for $oldrev already exists!"
fi
echo "$newrev" >"$cachedir/$oldrev"
}
-rev_exists()
-{
- if git rev-parse "$1" >/dev/null 2>&1; then
- return 0
- else
- return 1
- fi
-}
-
-rev_is_descendant_of_branch()
-{
- newrev="$1"
- branch="$2"
- branch_hash=$(git rev-parse $branch)
- match=$(git rev-list -1 $branch_hash ^$newrev)
-
- if [ -z "$match" ]; then
+# Usage: rev_exists REV
+rev_exists () {
+ assert test $# = 1
+ if git rev-parse "$1" >/dev/null 2>&1
+ then
return 0
else
return 1
fi
}
-# if a commit doesn't have a parent, this might not work. But we only want
+# Usage: try_remove_previous REV
+#
+# If a commit doesn't have a parent, this might not work. But we only want
# to remove the parent from the rev-list, and since it doesn't exist, it won't
# be there anyway, so do nothing in that case.
-try_remove_previous()
-{
- if rev_exists "$1^"; then
+try_remove_previous () {
+ assert test $# = 1
+ if rev_exists "$1^"
+ then
echo "^$1^"
fi
}
-find_latest_squash()
-{
+# Usage: find_latest_squash DIR
+find_latest_squash () {
+ assert test $# = 1
debug "Looking for latest squash ($dir)..."
+ local indent=$(($indent + 1))
+
dir="$1"
sq=
main=
sub=
git log --grep="^git-subtree-dir: $dir/*\$" \
- --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
- while read a b junk; do
+ --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
+ while read a b junk
+ do
debug "$a $b $junk"
debug "{{$sq/$main/$sub}}"
case "$a" in
- START) sq="$b" ;;
- git-subtree-mainline:) main="$b" ;;
- git-subtree-split:) sub="$b" ;;
- END)
- if [ -n "$sub" ]; then
- if [ -n "$main" ]; then
- # a rejoin commit?
- # Pretend its sub was a squash.
- sq="$sub"
- fi
- debug "Squash found: $sq $sub"
- echo "$sq" "$sub"
- break
+ START)
+ sq="$b"
+ ;;
+ git-subtree-mainline:)
+ main="$b"
+ ;;
+ git-subtree-split:)
+ sub="$(git rev-parse "$b^{commit}")" ||
+ die "could not rev-parse split hash $b from commit $sq"
+ ;;
+ END)
+ if test -n "$sub"
+ then
+ if test -n "$main"
+ then
+ # a rejoin commit?
+ # Pretend its sub was a squash.
+ sq=$(git rev-parse --verify "$sq^2") ||
+ die
fi
- sq=
- main=
- sub=
- ;;
+ debug "Squash found: $sq $sub"
+ echo "$sq" "$sub"
+ break
+ fi
+ sq=
+ main=
+ sub=
+ ;;
esac
- done
+ done || exit $?
}
-find_existing_splits()
-{
+# Usage: find_existing_splits DIR REV
+find_existing_splits () {
+ assert test $# = 2
debug "Looking for prior splits..."
+ local indent=$(($indent + 1))
+
dir="$1"
- revs="$2"
+ rev="$2"
main=
sub=
- git log --grep="^git-subtree-dir: $dir/*\$" \
- --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
- while read a b junk; do
+ local grep_format="^git-subtree-dir: $dir/*\$"
+ if test -n "$arg_split_ignore_joins"
+ then
+ grep_format="^Add '$dir/' from commit '"
+ fi
+ git log --grep="$grep_format" \
+ --no-show-signature --pretty=format:'START %H%n%s%n%n%b%nEND%n' "$rev" |
+ while read a b junk
+ do
case "$a" in
- START) sq="$b" ;;
- git-subtree-mainline:) main="$b" ;;
- git-subtree-split:) sub="$b" ;;
- END)
- debug " Main is: '$main'"
- if [ -z "$main" -a -n "$sub" ]; then
- # squash commits refer to a subtree
- debug " Squash: $sq from $sub"
- cache_set "$sq" "$sub"
- fi
- if [ -n "$main" -a -n "$sub" ]; then
- debug " Prior: $main -> $sub"
- cache_set $main $sub
- cache_set $sub $sub
- try_remove_previous "$main"
- try_remove_previous "$sub"
- fi
- main=
- sub=
- ;;
+ START)
+ sq="$b"
+ ;;
+ git-subtree-mainline:)
+ main="$b"
+ ;;
+ git-subtree-split:)
+ sub="$(git rev-parse "$b^{commit}")" ||
+ die "could not rev-parse split hash $b from commit $sq"
+ ;;
+ END)
+ debug "Main is: '$main'"
+ if test -z "$main" -a -n "$sub"
+ then
+ # squash commits refer to a subtree
+ debug " Squash: $sq from $sub"
+ cache_set "$sq" "$sub"
+ fi
+ if test -n "$main" -a -n "$sub"
+ then
+ debug " Prior: $main -> $sub"
+ cache_set $main $sub
+ cache_set $sub $sub
+ try_remove_previous "$main"
+ try_remove_previous "$sub"
+ fi
+ main=
+ sub=
+ ;;
esac
- done
+ done || exit $?
}
-copy_commit()
-{
+# Usage: copy_commit REV TREE FLAGS_STR
+copy_commit () {
+ assert test $# = 3
# We're going to set some environment vars here, so
# do it in a subshell to get rid of them safely later
debug copy_commit "{$1}" "{$2}" "{$3}"
- git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
+ git log -1 --no-show-signature --pretty=format:'%an%n%ae%n%aD%n%cn%n%ce%n%cD%n%B' "$1" |
(
read GIT_AUTHOR_NAME
read GIT_AUTHOR_EMAIL
@@ -310,109 +477,133 @@ copy_commit()
GIT_COMMITTER_NAME \
GIT_COMMITTER_EMAIL \
GIT_COMMITTER_DATE
- (echo -n "$annotate"; cat ) |
+ (
+ printf "%s" "$arg_split_annotate"
+ cat
+ ) |
git commit-tree "$2" $3 # reads the rest of stdin
) || die "Can't copy commit $1"
}
-add_msg()
-{
+# Usage: add_msg DIR LATEST_OLD LATEST_NEW
+add_msg () {
+ assert test $# = 3
dir="$1"
latest_old="$2"
latest_new="$3"
- if [ -n "$message" ]; then
- commit_message="$message"
+ if test -n "$arg_addmerge_message"
+ then
+ commit_message="$arg_addmerge_message"
else
commit_message="Add '$dir/' from commit '$latest_new'"
fi
+ if test -n "$arg_split_rejoin"
+ then
+ # If this is from a --rejoin, then rejoin_msg has
+ # already inserted the `git-subtree-xxx:` tags
+ echo "$commit_message"
+ return
+ fi
cat <<-EOF
$commit_message
-
+
git-subtree-dir: $dir
git-subtree-mainline: $latest_old
git-subtree-split: $latest_new
EOF
}
-add_squashed_msg()
-{
- if [ -n "$message" ]; then
- echo "$message"
+# Usage: add_squashed_msg REV DIR
+add_squashed_msg () {
+ assert test $# = 2
+ if test -n "$arg_addmerge_message"
+ then
+ echo "$arg_addmerge_message"
else
echo "Merge commit '$1' as '$2'"
fi
}
-rejoin_msg()
-{
+# Usage: rejoin_msg DIR LATEST_OLD LATEST_NEW
+rejoin_msg () {
+ assert test $# = 3
dir="$1"
latest_old="$2"
latest_new="$3"
- if [ -n "$message" ]; then
- commit_message="$message"
+ if test -n "$arg_addmerge_message"
+ then
+ commit_message="$arg_addmerge_message"
else
commit_message="Split '$dir/' into commit '$latest_new'"
fi
cat <<-EOF
$commit_message
-
+
git-subtree-dir: $dir
git-subtree-mainline: $latest_old
git-subtree-split: $latest_new
EOF
}
-squash_msg()
-{
+# Usage: squash_msg DIR OLD_SUBTREE_COMMIT NEW_SUBTREE_COMMIT
+squash_msg () {
+ assert test $# = 3
dir="$1"
oldsub="$2"
newsub="$3"
newsub_short=$(git rev-parse --short "$newsub")
-
- if [ -n "$oldsub" ]; then
+
+ if test -n "$oldsub"
+ then
oldsub_short=$(git rev-parse --short "$oldsub")
echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
echo
- git log --pretty=tformat:'%h %s' "$oldsub..$newsub"
- git log --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
+ git log --no-show-signature --pretty=tformat:'%h %s' "$oldsub..$newsub"
+ git log --no-show-signature --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
else
echo "Squashed '$dir/' content from commit $newsub_short"
fi
-
+
echo
echo "git-subtree-dir: $dir"
echo "git-subtree-split: $newsub"
}
-toptree_for_commit()
-{
+# Usage: toptree_for_commit COMMIT
+toptree_for_commit () {
+ assert test $# = 1
commit="$1"
- git log -1 --pretty=format:'%T' "$commit" -- || exit $?
+ git rev-parse --verify "$commit^{tree}" || exit $?
}
-subtree_for_commit()
-{
+# Usage: subtree_for_commit COMMIT DIR
+subtree_for_commit () {
+ assert test $# = 2
commit="$1"
dir="$2"
git ls-tree "$commit" -- "$dir" |
- while read mode type tree name; do
- assert [ "$name" = "$dir" ]
- assert [ "$type" = "tree" -o "$type" = "commit" ]
- [ "$type" = "commit" ] && continue # ignore submodules
+ while read mode type tree name
+ do
+ assert test "$name" = "$dir"
+ assert test "$type" = "tree" -o "$type" = "commit"
+ test "$type" = "commit" && continue # ignore submodules
echo $tree
break
- done
+ done || exit $?
}
-tree_changed()
-{
+# Usage: tree_changed TREE [PARENTS...]
+tree_changed () {
+ assert test $# -gt 0
tree=$1
shift
- if [ $# -ne 1 ]; then
+ if test $# -ne 1
+ then
return 0 # weird parents, consider it changed
else
- ptree=$(toptree_for_commit $1)
- if [ "$ptree" != "$tree" ]; then
+ ptree=$(toptree_for_commit $1) || exit $?
+ if test "$ptree" != "$tree"
+ then
return 0 # changed
else
return 1 # not changed
@@ -420,248 +611,362 @@ tree_changed()
fi
}
-new_squash_commit()
-{
+# Usage: new_squash_commit OLD_SQUASHED_COMMIT OLD_NONSQUASHED_COMMIT NEW_NONSQUASHED_COMMIT
+new_squash_commit () {
+ assert test $# = 3
old="$1"
oldsub="$2"
newsub="$3"
tree=$(toptree_for_commit $newsub) || exit $?
- if [ -n "$old" ]; then
- squash_msg "$dir" "$oldsub" "$newsub" |
- git commit-tree "$tree" -p "$old" || exit $?
+ if test -n "$old"
+ then
+ squash_msg "$dir" "$oldsub" "$newsub" |
+ git commit-tree "$tree" -p "$old" || exit $?
else
squash_msg "$dir" "" "$newsub" |
- git commit-tree "$tree" || exit $?
+ git commit-tree "$tree" || exit $?
fi
}
-copy_or_skip()
-{
+# Usage: copy_or_skip REV TREE NEWPARENTS
+copy_or_skip () {
+ assert test $# = 3
rev="$1"
tree="$2"
newparents="$3"
- assert [ -n "$tree" ]
+ assert test -n "$tree"
identical=
nonidentical=
p=
gotparents=
- for parent in $newparents; do
+ copycommit=
+ for parent in $newparents
+ do
ptree=$(toptree_for_commit $parent) || exit $?
- [ -z "$ptree" ] && continue
- if [ "$ptree" = "$tree" ]; then
+ test -z "$ptree" && continue
+ if test "$ptree" = "$tree"
+ then
# an identical parent could be used in place of this rev.
- identical="$parent"
+ if test -n "$identical"
+ then
+ # if a previous identical parent was found, check whether
+ # one is already an ancestor of the other
+ mergebase=$(git merge-base $identical $parent)
+ if test "$identical" = "$mergebase"
+ then
+ # current identical commit is an ancestor of parent
+ identical="$parent"
+ elif test "$parent" != "$mergebase"
+ then
+ # no common history; commit must be copied
+ copycommit=1
+ fi
+ else
+ # first identical parent detected
+ identical="$parent"
+ fi
else
nonidentical="$parent"
fi
-
+
# sometimes both old parents map to the same newparent;
# eliminate duplicates
is_new=1
- for gp in $gotparents; do
- if [ "$gp" = "$parent" ]; then
+ for gp in $gotparents
+ do
+ if test "$gp" = "$parent"
+ then
is_new=
break
fi
done
- if [ -n "$is_new" ]; then
+ if test -n "$is_new"
+ then
gotparents="$gotparents $parent"
p="$p -p $parent"
fi
done
-
- if [ -n "$identical" ]; then
+
+ if test -n "$identical" && test -n "$nonidentical"
+ then
+ extras=$(git rev-list --count $identical..$nonidentical)
+ if test "$extras" -ne 0
+ then
+ # we need to preserve history along the other branch
+ copycommit=1
+ fi
+ fi
+ if test -n "$identical" && test -z "$copycommit"
+ then
echo $identical
else
- copy_commit $rev $tree "$p" || exit $?
+ copy_commit "$rev" "$tree" "$p" || exit $?
fi
}
-ensure_clean()
-{
- if ! git diff-index HEAD --exit-code --quiet 2>&1; then
+# Usage: ensure_clean
+ensure_clean () {
+ assert test $# = 0
+ if ! git diff-index HEAD --exit-code --quiet 2>&1
+ then
die "Working tree has modifications. Cannot add."
fi
- if ! git diff-index --cached HEAD --exit-code --quiet 2>&1; then
+ if ! git diff-index --cached HEAD --exit-code --quiet 2>&1
+ then
die "Index has modifications. Cannot add."
fi
}
-cmd_add()
-{
- if [ -e "$dir" ]; then
- die "'$dir' already exists. Cannot add."
+# Usage: ensure_valid_ref_format REF
+ensure_valid_ref_format () {
+ assert test $# = 1
+ git check-ref-format "refs/heads/$1" ||
+ die "'$1' does not look like a ref"
+}
+
+# Usage: process_split_commit REV PARENTS
+process_split_commit () {
+ assert test $# = 2
+ local rev="$1"
+ local parents="$2"
+
+ if test $indent -eq 0
+ then
+ revcount=$(($revcount + 1))
+ else
+ # processing commit without normal parent information;
+ # fetch from repo
+ parents=$(git rev-parse "$rev^@")
+ extracount=$(($extracount + 1))
+ fi
+
+ progress "$revcount/$revmax ($createcount) [$extracount]"
+
+ debug "Processing commit: $rev"
+ local indent=$(($indent + 1))
+ exists=$(cache_get "$rev") || exit $?
+ if test -n "$exists"
+ then
+ debug "prior: $exists"
+ return
fi
+ createcount=$(($createcount + 1))
+ debug "parents: $parents"
+ check_parents "$parents"
+ newparents=$(cache_get $parents) || exit $?
+ debug "newparents: $newparents"
+
+ tree=$(subtree_for_commit "$rev" "$dir") || exit $?
+ debug "tree is: $tree"
+
+ # ugly. is there no better way to tell if this is a subtree
+ # vs. a mainline commit? Does it matter?
+ if test -z "$tree"
+ then
+ set_notree "$rev"
+ if test -n "$newparents"
+ then
+ cache_set "$rev" "$rev"
+ fi
+ return
+ fi
+
+ newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
+ debug "newrev is: $newrev"
+ cache_set "$rev" "$newrev"
+ cache_set latest_new "$newrev"
+ cache_set latest_old "$rev"
+}
+
+# Usage: cmd_add REV
+# Or: cmd_add REPOSITORY REF
+cmd_add () {
ensure_clean
-
- if [ $# -eq 1 ]; then
- "cmd_add_commit" "$@"
- elif [ $# -eq 2 ]; then
- "cmd_add_repository" "$@"
+
+ if test $# -eq 1
+ then
+ git rev-parse -q --verify "$1^{commit}" >/dev/null ||
+ die "'$1' does not refer to a commit"
+
+ cmd_add_commit "$@"
+
+ elif test $# -eq 2
+ then
+ # Technically we could accept a refspec here but we're
+ # just going to turn around and add FETCH_HEAD under the
+ # specified directory. Allowing a refspec might be
+ # misleading because we won't do anything with any other
+ # branches fetched via the refspec.
+ ensure_valid_ref_format "$2"
+
+ cmd_add_repository "$@"
else
- say "error: parameters were '$@'"
- die "Provide either a refspec or a repository and refspec."
+ say >&2 "error: parameters were '$*'"
+ die "Provide either a commit or a repository and commit."
fi
}
-cmd_add_repository()
-{
+# Usage: cmd_add_repository REPOSITORY REFSPEC
+cmd_add_repository () {
+ assert test $# = 2
echo "git fetch" "$@"
repository=$1
refspec=$2
git fetch "$@" || exit $?
- revs=FETCH_HEAD
- set -- $revs
- cmd_add_commit "$@"
+ cmd_add_commit FETCH_HEAD
}
-cmd_add_commit()
-{
- revs=$(git rev-parse $default --revs-only "$@") || exit $?
- set -- $revs
- rev="$1"
-
+# Usage: cmd_add_commit REV
+cmd_add_commit () {
+ # The rev has already been validated by cmd_add(), we just
+ # need to normalize it.
+ assert test $# = 1
+ rev=$(git rev-parse --verify "$1^{commit}") || exit $?
+
debug "Adding $dir as '$rev'..."
- git read-tree --prefix="$dir" $rev || exit $?
+ if test -z "$arg_split_rejoin"
+ then
+ # Only bother doing this if this is a genuine 'add',
+ # not a synthetic 'add' from '--rejoin'.
+ git read-tree --prefix="$dir" $rev || exit $?
+ fi
git checkout -- "$dir" || exit $?
tree=$(git write-tree) || exit $?
-
+
headrev=$(git rev-parse HEAD) || exit $?
- if [ -n "$headrev" -a "$headrev" != "$rev" ]; then
+ if test -n "$headrev" && test "$headrev" != "$rev"
+ then
headp="-p $headrev"
else
headp=
fi
-
- if [ -n "$squash" ]; then
+
+ if test -n "$arg_addmerge_squash"
+ then
rev=$(new_squash_commit "" "" "$rev") || exit $?
commit=$(add_squashed_msg "$rev" "$dir" |
- git commit-tree $tree $headp -p "$rev") || exit $?
+ git commit-tree "$tree" $headp -p "$rev") || exit $?
else
- commit=$(add_msg "$dir" "$headrev" "$rev" |
- git commit-tree $tree $headp -p "$rev") || exit $?
+ revp=$(peel_committish "$rev") || exit $?
+ commit=$(add_msg "$dir" $headrev "$rev" |
+ git commit-tree "$tree" $headp -p "$revp") || exit $?
fi
git reset "$commit" || exit $?
-
- say "Added dir '$dir'"
+
+ say >&2 "Added dir '$dir'"
}
-cmd_split()
-{
+# Usage: cmd_split [REV]
+cmd_split () {
+ if test $# -eq 0
+ then
+ rev=$(git rev-parse HEAD)
+ elif test $# -eq 1
+ then
+ rev=$(git rev-parse -q --verify "$1^{commit}") ||
+ die "'$1' does not refer to a commit"
+ else
+ die "You must provide exactly one revision. Got: '$*'"
+ fi
+
+ if test -n "$arg_split_rejoin"
+ then
+ ensure_clean
+ fi
+
debug "Splitting $dir..."
cache_setup || exit $?
-
- if [ -n "$onto" ]; then
- debug "Reading history for --onto=$onto..."
- git rev-list $onto |
- while read rev; do
+
+ if test -n "$arg_split_onto"
+ then
+ debug "Reading history for --onto=$arg_split_onto..."
+ git rev-list $arg_split_onto |
+ while read rev
+ do
# the 'onto' history is already just the subdir, so
# any parent we find there can be used verbatim
- debug " cache: $rev"
- cache_set $rev $rev
- done
- fi
-
- if [ -n "$ignore_joins" ]; then
- unrevs=
- else
- unrevs="$(find_existing_splits "$dir" "$revs")"
+ debug "cache: $rev"
+ cache_set "$rev" "$rev"
+ done || exit $?
fi
-
+
+ unrevs="$(find_existing_splits "$dir" "$rev")" || exit $?
+
# We can't restrict rev-list to only $dir here, because some of our
# parents have the $dir contents the root, and those won't match.
# (and rev-list --follow doesn't seem to solve this)
- grl='git rev-list --topo-order --reverse --parents $revs $unrevs'
+ grl='git rev-list --topo-order --reverse --parents $rev $unrevs'
revmax=$(eval "$grl" | wc -l)
revcount=0
createcount=0
+ extracount=0
eval "$grl" |
- while read rev parents; do
- revcount=$(($revcount + 1))
- say -n "$revcount/$revmax ($createcount) "
- debug "Processing commit: $rev"
- exists=$(cache_get $rev)
- if [ -n "$exists" ]; then
- debug " prior: $exists"
- continue
- fi
- createcount=$(($createcount + 1))
- debug " parents: $parents"
- newparents=$(cache_get $parents)
- debug " newparents: $newparents"
-
- tree=$(subtree_for_commit $rev "$dir")
- debug " tree is: $tree"
-
- check_parents $parents
-
- # ugly. is there no better way to tell if this is a subtree
- # vs. a mainline commit? Does it matter?
- if [ -z $tree ]; then
- set_notree $rev
- if [ -n "$newparents" ]; then
- cache_set $rev $rev
- fi
- continue
- fi
-
- newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
- debug " newrev is: $newrev"
- cache_set $rev $newrev
- cache_set latest_new $newrev
- cache_set latest_old $rev
+ while read rev parents
+ do
+ process_split_commit "$rev" "$parents"
done || exit $?
- latest_new=$(cache_get latest_new)
- if [ -z "$latest_new" ]; then
+
+ latest_new=$(cache_get latest_new) || exit $?
+ if test -z "$latest_new"
+ then
die "No new revisions were found"
fi
-
- if [ -n "$rejoin" ]; then
+
+ if test -n "$arg_split_rejoin"
+ then
debug "Merging split branch into HEAD..."
- latest_old=$(cache_get latest_old)
- git merge -s ours \
- -m "$(rejoin_msg $dir $latest_old $latest_new)" \
- $latest_new >&2 || exit $?
- fi
- if [ -n "$branch" ]; then
- if rev_exists "refs/heads/$branch"; then
- if ! rev_is_descendant_of_branch $latest_new $branch; then
- die "Branch '$branch' is not an ancestor of commit '$latest_new'."
+ latest_old=$(cache_get latest_old) || exit $?
+ arg_addmerge_message="$(rejoin_msg "$dir" "$latest_old" "$latest_new")" || exit $?
+ if test -z "$(find_latest_squash "$dir")"
+ then
+ cmd_add "$latest_new" >&2 || exit $?
+ else
+ cmd_merge "$latest_new" >&2 || exit $?
+ fi
+ fi
+ if test -n "$arg_split_branch"
+ then
+ if rev_exists "refs/heads/$arg_split_branch"
+ then
+ if ! git merge-base --is-ancestor "$arg_split_branch" "$latest_new"
+ then
+ die "Branch '$arg_split_branch' is not an ancestor of commit '$latest_new'."
fi
action='Updated'
else
action='Created'
fi
- git update-ref -m 'subtree split' "refs/heads/$branch" $latest_new || exit $?
- say "$action branch '$branch'"
+ git update-ref -m 'subtree split' \
+ "refs/heads/$arg_split_branch" "$latest_new" || exit $?
+ say >&2 "$action branch '$arg_split_branch'"
fi
- echo $latest_new
+ echo "$latest_new"
exit 0
}
-cmd_merge()
-{
- revs=$(git rev-parse $default --revs-only "$@") || exit $?
+# Usage: cmd_merge REV
+cmd_merge () {
+ test $# -eq 1 ||
+ die "You must provide exactly one revision. Got: '$*'"
+ rev=$(git rev-parse -q --verify "$1^{commit}") ||
+ die "'$1' does not refer to a commit"
ensure_clean
-
- set -- $revs
- if [ $# -ne 1 ]; then
- die "You must provide exactly one revision. Got: '$revs'"
- fi
- rev="$1"
-
- if [ -n "$squash" ]; then
- first_split="$(find_latest_squash "$dir")"
- if [ -z "$first_split" ]; then
+
+ if test -n "$arg_addmerge_squash"
+ then
+ first_split="$(find_latest_squash "$dir")" || exit $?
+ if test -z "$first_split"
+ then
die "Can't squash-merge: '$dir' was never added."
fi
set $first_split
old=$1
sub=$2
- if [ "$sub" = "$rev" ]; then
- say "Subtree is already at commit $rev."
+ if test "$sub" = "$rev"
+ then
+ say >&2 "Subtree is already at commit $rev."
exit 0
fi
new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
@@ -669,44 +974,54 @@ cmd_merge()
rev="$new"
fi
- version=$(git version)
- if [ "$version" \< "git version 1.7" ]; then
- if [ -n "$message" ]; then
- git merge -s subtree --message="$message" $rev
- else
- git merge -s subtree $rev
- fi
+ if test -n "$arg_addmerge_message"
+ then
+ git merge -Xsubtree="$arg_prefix" \
+ --message="$arg_addmerge_message" "$rev"
else
- if [ -n "$message" ]; then
- git merge -Xsubtree="$prefix" --message="$message" $rev
- else
- git merge -Xsubtree="$prefix" $rev
- fi
+ git merge -Xsubtree="$arg_prefix" $rev
fi
}
-cmd_pull()
-{
+# Usage: cmd_pull REPOSITORY REMOTEREF
+cmd_pull () {
+ if test $# -ne 2
+ then
+ die "You must provide <repository> <ref>"
+ fi
ensure_clean
+ ensure_valid_ref_format "$2"
git fetch "$@" || exit $?
- revs=FETCH_HEAD
- set -- $revs
- cmd_merge "$@"
+ cmd_merge FETCH_HEAD
}
-cmd_push()
-{
- if [ $# -ne 2 ]; then
- die "You must provide <repository> <refspec>"
+# Usage: cmd_push REPOSITORY [+][LOCALREV:]REMOTEREF
+cmd_push () {
+ if test $# -ne 2
+ then
+ die "You must provide <repository> <refspec>"
fi
- if [ -e "$dir" ]; then
- repository=$1
- refspec=$2
- echo "git push using: " $repository $refspec
- git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec
+ if test -e "$dir"
+ then
+ repository=$1
+ refspec=${2#+}
+ remoteref=${refspec#*:}
+ if test "$remoteref" = "$refspec"
+ then
+ localrevname_presplit=HEAD
+ else
+ localrevname_presplit=${refspec%%:*}
+ fi
+ ensure_valid_ref_format "$remoteref"
+ localrev_presplit=$(git rev-parse -q --verify "$localrevname_presplit^{commit}") ||
+ die "'$localrevname_presplit' does not refer to a commit"
+
+ echo "git push using: " "$repository" "$refspec"
+ localrev=$(cmd_split "$localrev_presplit") || die
+ git push "$repository" "$localrev":"refs/heads/$remoteref"
else
- die "'$dir' must already exist. Try 'git subtree add'."
+ die "'$dir' must already exist. Try 'git subtree add'."
fi
}
-"cmd_$command" "$@"
+main "$@"
diff --git a/contrib/subtree/git-subtree.txt b/contrib/subtree/git-subtree.txt
index c5bce41ac7..9cddfa2654 100644
--- a/contrib/subtree/git-subtree.txt
+++ b/contrib/subtree/git-subtree.txt
@@ -9,12 +9,14 @@ git-subtree - Merge subtrees together and split repository into subtrees
SYNOPSIS
--------
[verse]
-'git subtree' add -P <prefix> <commit>
-'git subtree' pull -P <prefix> <repository> <refspec...>
-'git subtree' push -P <prefix> <repository> <refspec...>
-'git subtree' merge -P <prefix> <commit>
-'git subtree' split -P <prefix> [OPTIONS] [<commit>]
+'git subtree' [<options>] -P <prefix> add <local-commit>
+'git subtree' [<options>] -P <prefix> add <repository> <remote-ref>
+'git subtree' [<options>] -P <prefix> merge <local-commit>
+'git subtree' [<options>] -P <prefix> split [<local-commit>]
+[verse]
+'git subtree' [<options>] -P <prefix> pull <repository> <remote-ref>
+'git subtree' [<options>] -P <prefix> push <repository> <refspec>
DESCRIPTION
-----------
@@ -27,7 +29,7 @@ as a subdirectory of your application.
Subtrees are not to be confused with submodules, which are meant for
the same task. Unlike submodules, subtrees do not need any special
-constructions (like .gitmodule files or gitlinks) be present in
+constructions (like '.gitmodules' files or gitlinks) be present in
your repository, and do not force end-users of your
repository to do anything special or to understand how subtrees
work. A subtree is just a subdirectory that can be
@@ -58,73 +60,71 @@ project as much as possible. That is, if you make a change that
affects both the library and the main application, commit it in
two pieces. That way, when you split the library commits out
later, their descriptions will still make sense. But if this
-isn't important to you, it's not *necessary*. git subtree will
+isn't important to you, it's not *necessary*. 'git subtree' will
simply leave out the non-library-related parts of the commit
when it splits it out into the subproject later.
COMMANDS
--------
-add::
+add <local-commit>::
+add <repository> <remote-ref>::
Create the <prefix> subtree by importing its contents
- from the given <refspec> or <repository> and remote <refspec>.
+ from the given <local-commit> or <repository> and <remote-ref>.
A new commit is created automatically, joining the imported
- project's history with your own. With '--squash', imports
+ project's history with your own. With '--squash', import
only a single commit from the subproject, rather than its
entire history.
-merge::
- Merge recent changes up to <commit> into the <prefix>
+merge <local-commit>::
+ Merge recent changes up to <local-commit> into the <prefix>
subtree. As with normal 'git merge', this doesn't
remove your own local changes; it just merges those
- changes into the latest <commit>. With '--squash',
- creates only one commit that contains all the changes,
+ changes into the latest <local-commit>. With '--squash',
+ create only one commit that contains all the changes,
rather than merging in the entire history.
++
+If you use '--squash', the merge direction doesn't always have to be
+forward; you can use this command to go back in time from v2.5 to v2.4,
+for example. If your merge introduces a conflict, you can resolve it in
+the usual ways.
- If you use '--squash', the merge direction doesn't
- always have to be forward; you can use this command to
- go back in time from v2.5 to v2.4, for example. If your
- merge introduces a conflict, you can resolve it in the
- usual ways.
-
-pull::
- Exactly like 'merge', but parallels 'git pull' in that
- it fetches the given commit from the specified remote
- repository.
-
-push::
- Does a 'split' (see below) using the <prefix> supplied
- and then does a 'git push' to push the result to the
- repository and refspec. This can be used to push your
- subtree to different branches of the remote repository.
-
-split::
+split [<local-commit>]::
Extract a new, synthetic project history from the
- history of the <prefix> subtree. The new history
+ history of the <prefix> subtree of <local-commit>, or of
+ HEAD if no <local-commit> is given. The new history
includes only the commits (including merges) that
affected <prefix>, and each of those commits now has the
contents of <prefix> at the root of the project instead
of in a subdirectory. Thus, the newly created history
is suitable for export as a separate git repository.
-
- After splitting successfully, a single commit id is
- printed to stdout. This corresponds to the HEAD of the
- newly created tree, which you can manipulate however you
- want.
-
- Repeated splits of exactly the same history are
- guaranteed to be identical (ie. to produce the same
- commit ids). Because of this, if you add new commits
- and then re-split, the new commits will be attached as
- commits on top of the history you generated last time,
- so 'git merge' and friends will work as expected.
-
- Note that if you use '--squash' when you merge, you
- should usually not just '--rejoin' when you split.
-
-
-OPTIONS
--------
++
+After splitting successfully, a single commit ID is printed to stdout.
+This corresponds to the HEAD of the newly created tree, which you can
+manipulate however you want.
++
+Repeated splits of exactly the same history are guaranteed to be
+identical (i.e. to produce the same commit IDs) as long as the
+settings passed to 'split' (such as '--annotate') are the same.
+Because of this, if you add new commits and then re-split, the new
+commits will be attached as commits on top of the history you
+generated last time, so 'git merge' and friends will work as expected.
+
+pull <repository> <remote-ref>::
+ Exactly like 'merge', but parallels 'git pull' in that
+ it fetches the given ref from the specified remote
+ repository.
+
+push <repository> [+][<local-commit>:]<remote-ref>::
+ Does a 'split' using the <prefix> subtree of <local-commit>
+ and then does a 'git push' to push the result to the
+ <repository> and <remote-ref>. This can be used to push your
+ subtree to different branches of the remote repository. Just
+ as with 'split', if no <local-commit> is given, then HEAD is
+ used. The optional leading '+' is ignored.
+
+OPTIONS FOR ALL COMMANDS
+------------------------
-q::
--quiet::
Suppress unnecessary output messages on stderr.
@@ -139,124 +139,101 @@ OPTIONS
want to manipulate. This option is mandatory
for all commands.
+OPTIONS FOR 'add' AND 'merge' (ALSO: 'pull', 'split --rejoin', AND 'push --rejoin')
+-----------------------------------------------------------------------------------
+These options for 'add' and 'merge' may also be given to 'pull' (which
+wraps 'merge'), 'split --rejoin' (which wraps either 'add' or 'merge'
+as appropriate), and 'push --rejoin' (which wraps 'split --rejoin').
+
+--squash::
+ Instead of merging the entire history from the subtree project, produce
+ only a single commit that contains all the differences you want to
+ merge, and then merge that new commit into your project.
++
+Using this option helps to reduce log clutter. People rarely want to see
+every change that happened between v1.0 and v1.1 of the library they're
+using, since none of the interim versions were ever included in their
+application.
++
+Using '--squash' also helps avoid problems when the same subproject is
+included multiple times in the same project, or is removed and then
+re-added. In such a case, it doesn't make sense to combine the
+histories anyway, since it's unclear which part of the history belongs
+to which subtree.
++
+Furthermore, with '--squash', you can switch back and forth between
+different versions of a subtree, rather than strictly forward. 'git
+subtree merge --squash' always adjusts the subtree to match the exactly
+specified commit, even if getting to that commit would require undoing
+some changes that were added earlier.
++
+Whether or not you use '--squash', changes made in your local repository
+remain intact and can be later split and send upstream to the
+subproject.
+
-m <message>::
--message=<message>::
- This option is only valid for add, merge and pull (unsure).
Specify <message> as the commit message for the merge commit.
-
-OPTIONS FOR add, merge, push, pull
+OPTIONS FOR 'split' (ALSO: 'push')
----------------------------------
---squash::
- This option is only valid for add, merge, push and pull
- commands.
-
- Instead of merging the entire history from the subtree
- project, produce only a single commit that contains all
- the differences you want to merge, and then merge that
- new commit into your project.
-
- Using this option helps to reduce log clutter. People
- rarely want to see every change that happened between
- v1.0 and v1.1 of the library they're using, since none of the
- interim versions were ever included in their application.
-
- Using '--squash' also helps avoid problems when the same
- subproject is included multiple times in the same
- project, or is removed and then re-added. In such a
- case, it doesn't make sense to combine the histories
- anyway, since it's unclear which part of the history
- belongs to which subtree.
-
- Furthermore, with '--squash', you can switch back and
- forth between different versions of a subtree, rather
- than strictly forward. 'git subtree merge --squash'
- always adjusts the subtree to match the exactly
- specified commit, even if getting to that commit would
- require undoing some changes that were added earlier.
-
- Whether or not you use '--squash', changes made in your
- local repository remain intact and can be later split
- and send upstream to the subproject.
-
-
-OPTIONS FOR split
------------------
+These options for 'split' may also be given to 'push' (which wraps
+'split').
+
--annotate=<annotation>::
- This option is only valid for the split command.
-
- When generating synthetic history, add <annotation> as a
- prefix to each commit message. Since we're creating new
- commits with the same commit message, but possibly
- different content, from the original commits, this can help
- to differentiate them and avoid confusion.
-
- Whenever you split, you need to use the same
- <annotation>, or else you don't have a guarantee that
- the new re-created history will be identical to the old
- one. That will prevent merging from working correctly.
- git subtree tries to make it work anyway, particularly
- if you use --rejoin, but it may not always be effective.
+ When generating synthetic history, add <annotation> as a prefix to each
+ commit message. Since we're creating new commits with the same commit
+ message, but possibly different content, from the original commits, this
+ can help to differentiate them and avoid confusion.
++
+Whenever you split, you need to use the same <annotation>, or else you
+don't have a guarantee that the new re-created history will be identical
+to the old one. That will prevent merging from working correctly. git
+subtree tries to make it work anyway, particularly if you use '--rejoin',
+but it may not always be effective.
-b <branch>::
--branch=<branch>::
- This option is only valid for the split command.
-
- After generating the synthetic history, create a new
- branch called <branch> that contains the new history.
- This is suitable for immediate pushing upstream.
- <branch> must not already exist.
+ After generating the synthetic history, create a new branch called
+ <branch> that contains the new history. This is suitable for immediate
+ pushing upstream. <branch> must not already exist.
--ignore-joins::
- This option is only valid for the split command.
-
- If you use '--rejoin', git subtree attempts to optimize
- its history reconstruction to generate only the new
- commits since the last '--rejoin'. '--ignore-join'
- disables this behaviour, forcing it to regenerate the
- entire history. In a large project, this can take a
- long time.
+ If you use '--rejoin', git subtree attempts to optimize its history
+ reconstruction to generate only the new commits since the last
+ '--rejoin'. '--ignore-joins' disables this behavior, forcing it to
+ regenerate the entire history. In a large project, this can take a long
+ time.
--onto=<onto>::
- This option is only valid for the split command.
-
- If your subtree was originally imported using something
- other than git subtree, its history may not match what
- git subtree is expecting. In that case, you can specify
- the commit id <onto> that corresponds to the first
- revision of the subproject's history that was imported
- into your project, and git subtree will attempt to build
- its history from there.
-
- If you used 'git subtree add', you should never need
- this option.
+ If your subtree was originally imported using something other than git
+ subtree, its history may not match what git subtree is expecting. In
+ that case, you can specify the commit ID <onto> that corresponds to the
+ first revision of the subproject's history that was imported into your
+ project, and git subtree will attempt to build its history from there.
++
+If you used 'git subtree add', you should never need this option.
--rejoin::
- This option is only valid for the split command.
-
- After splitting, merge the newly created synthetic
- history back into your main project. That way, future
- splits can search only the part of history that has
- been added since the most recent --rejoin.
-
- If your split commits end up merged into the upstream
- subproject, and then you want to get the latest upstream
- version, this will allow git's merge algorithm to more
- intelligently avoid conflicts (since it knows these
- synthetic commits are already part of the upstream
- repository).
-
- Unfortunately, using this option results in 'git log'
- showing an extra copy of every new commit that was
- created (the original, and the synthetic one).
-
- If you do all your merges with '--squash', don't use
- '--rejoin' when you split, because you don't want the
- subproject's history to be part of your project anyway.
-
-
-EXAMPLE 1. Add command
-----------------------
+ After splitting, merge the newly created synthetic history back into
+ your main project. That way, future splits can search only the part of
+ history that has been added since the most recent '--rejoin'.
++
+If your split commits end up merged into the upstream subproject, and
+then you want to get the latest upstream version, this will allow git's
+merge algorithm to more intelligently avoid conflicts (since it knows
+these synthetic commits are already part of the upstream repository).
++
+Unfortunately, using this option results in 'git log' showing an extra
+copy of every new commit that was created (the original, and the
+synthetic one).
++
+If you do all your merges with '--squash', make sure you also use
+'--squash' when you 'split --rejoin'.
+
+
+EXAMPLE 1. 'add' command
+------------------------
Let's assume that you have a local repository that you would like
to add an external vendor library to. In this case we will add the
git-subtree repository as a subdirectory of your already existing
@@ -268,15 +245,15 @@ git-extensions repository in ~/git-extensions/:
'master' needs to be a valid remote ref and can be a different branch
name
-You can omit the --squash flag, but doing so will increase the number
-of commits that are incldued in your local repository.
+You can omit the '--squash' flag, but doing so will increase the number
+of commits that are included in your local repository.
We now have a ~/git-extensions/git-subtree directory containing code
from the master branch of git://github.com/apenwarr/git-subtree.git
in our git-extensions repository.
-EXAMPLE 2. Extract a subtree using commit, merge and pull
----------------------------------------------------------
+EXAMPLE 2. Extract a subtree using 'commit', 'merge' and 'pull'
+---------------------------------------------------------------
Let's use the repository for the git source code as an example.
First, get your own copy of the git.git repository:
@@ -284,7 +261,7 @@ First, get your own copy of the git.git repository:
$ cd test-git
gitweb (commit 1130ef3) was merged into git as of commit
-0a8f4f0, after which it was no longer maintained separately.
+0a8f4f0, after which it was no longer maintained separately.
But imagine it had been maintained separately, and we wanted to
extract git's changes to gitweb since that time, to share with
the upstream. You could do this:
@@ -294,14 +271,14 @@ the upstream. You could do this:
--branch gitweb-latest
$ gitk gitweb-latest
$ git push git@github.com:whatever/gitweb.git gitweb-latest:master
-
+
(We use '0a8f4f0^..' because that means "all the changes from
0a8f4f0 to the current version, including 0a8f4f0 itself.")
If gitweb had originally been merged using 'git subtree add' (or
-a previous split had already been done with --rejoin specified)
+a previous split had already been done with '--rejoin' specified)
then you can do all your splits without having to remember any
-weird commit ids:
+weird commit IDs:
$ git subtree split --prefix=gitweb --annotate='(split) ' --rejoin \
--branch gitweb-latest2
@@ -328,7 +305,7 @@ And fast forward again:
$ git subtree merge --prefix=gitweb --squash gitweb-latest
And notice that your change is still intact:
-
+
$ ls -l gitweb/myfile
And you can split it out and look at your changes versus
@@ -336,8 +313,8 @@ the standard gitweb:
git log gitweb-latest..$(git subtree split --prefix=gitweb)
-EXAMPLE 3. Extract a subtree using branch
------------------------------------------
+EXAMPLE 3. Extract a subtree using a branch
+-------------------------------------------
Suppose you have a source directory with many files and
subdirectories, and you want to extract the lib directory to its own
git project. Here's a short way to do it:
diff --git a/contrib/subtree/t/Makefile b/contrib/subtree/t/Makefile
index c864810389..276898eb6b 100644
--- a/contrib/subtree/t/Makefile
+++ b/contrib/subtree/t/Makefile
@@ -13,11 +13,23 @@ TAR ?= $(TAR)
RM ?= rm -f
PROVE ?= prove
DEFAULT_TEST_TARGET ?= test
+TEST_LINT ?= test-lint
+
+ifdef TEST_OUTPUT_DIRECTORY
+TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results
+else
+TEST_RESULTS_DIRECTORY = ../../../t/test-results
+endif
# Shell quote;
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY))
-T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
+T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh))
+TSVN = $(sort $(wildcard t91[0-9][0-9]-*.sh))
+TGITWEB = $(sort $(wildcard t95[0-9][0-9]-*.sh))
+THELPERS = $(sort $(filter-out $(T),$(wildcard *.sh)))
all: $(DEFAULT_TEST_TARGET)
@@ -26,20 +38,22 @@ test: pre-clean $(TEST_LINT)
prove: pre-clean $(TEST_LINT)
@echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
- $(MAKE) clean
+ $(MAKE) clean-except-prove-cache
$(T):
@echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
pre-clean:
- $(RM) -r test-results
+ $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)'
-clean:
- $(RM) -r 'trash directory'.* test-results
+clean-except-prove-cache:
+ $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)'
$(RM) -r valgrind/bin
+
+clean: clean-except-prove-cache
$(RM) .prove
-test-lint: test-lint-duplicates test-lint-executable
+test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax
test-lint-duplicates:
@dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \
@@ -51,12 +65,15 @@ test-lint-executable:
test -z "$$bad" || { \
echo >&2 "non-executable tests:" $$bad; exit 1; }
+test-lint-shell-syntax:
+ @'$(PERL_PATH_SQ)' ../../../t/check-non-portable-shell.pl $(T) $(THELPERS)
+
aggregate-results-and-cleanup: $(T)
$(MAKE) aggregate-results
$(MAKE) clean
aggregate-results:
- for f in ../../../t/test-results/t*-*.counts; do \
+ for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \
echo "$$f"; \
done | '$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
index bc2eeb0944..4153b65321 100755
--- a/contrib/subtree/t/t7900-subtree.sh
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -1,508 +1,1478 @@
#!/bin/sh
#
# Copyright (c) 2012 Avery Pennaraum
+# Copyright (c) 2015 Alexey Shumkin
#
test_description='Basic porcelain support for subtrees
-This test verifies the basic operation of the merge, pull, add
-and split subcommands of git subtree.
+This test verifies the basic operation of the add, merge, split, pull,
+and push subcommands of git subtree.
'
-export TEST_DIRECTORY=$(pwd)/../../../t
-
-. ../../../t/test-lib.sh
-
-create()
-{
- echo "$1" >"$1"
- git add "$1"
-}
-
-
-check_equal()
-{
- test_debug 'echo'
- test_debug "echo \"check a:\" \"{$1}\""
- test_debug "echo \" b:\" \"{$2}\""
- if [ "$1" = "$2" ]; then
- return 0
- else
- return 1
- fi
-}
-
-fixnl()
-{
- t=""
- while read x; do
- t="$t$x "
- done
- echo $t
+TEST_DIRECTORY=$(pwd)/../../../t
+. "$TEST_DIRECTORY"/test-lib.sh
+
+# Use our own wrapper around test-lib.sh's test_create_repo, in order
+# to set log.date=relative. `git subtree` parses the output of `git
+# log`, and so it must be careful to not be affected by settings that
+# change the `git log` output. We test this by setting
+# log.date=relative for every repo in the tests.
+subtree_test_create_repo () {
+ test_create_repo "$1" &&
+ git -C "$1" config log.date relative
}
-multiline()
-{
- while read x; do
- set -- $x
- for d in "$@"; do
- echo "$d"
- done
- done
+test_create_commit () (
+ repo=$1 &&
+ commit=$2 &&
+ cd "$repo" &&
+ mkdir -p "$(dirname "$commit")" \
+ || error "Could not create directory for commit"
+ echo "$commit" >"$commit" &&
+ git add "$commit" || error "Could not add commit"
+ git commit -m "$commit" || error "Could not commit"
+)
+
+test_wrong_flag() {
+ test_must_fail "$@" >out 2>err &&
+ test_must_be_empty out &&
+ grep "flag does not make sense with" err
}
-undo()
-{
- git reset --hard HEAD~
-}
-
-last_commit_message()
-{
+last_commit_subject () {
git log --pretty=format:%s -1
}
-# 1
-test_expect_success 'init subproj' '
- test_create_repo subproj
+test_expect_success 'shows short help text for -h' '
+ test_expect_code 129 git subtree -h >out 2>err &&
+ test_must_be_empty err &&
+ grep -e "^ *or: git subtree pull" out &&
+ grep -e --annotate out
'
-# To the subproject!
-cd subproj
+#
+# Tests for 'git subtree add'
+#
-# 2
-test_expect_success 'add sub1' '
- create sub1 &&
- git commit -m "sub1" &&
- git branch sub1 &&
- git branch -m master subproj
+test_expect_success 'no merge from non-existent subtree' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ test_must_fail git subtree merge --prefix="sub dir" FETCH_HEAD
+ )
'
-# 3
-test_expect_success 'add sub2' '
- create sub2 &&
- git commit -m "sub2" &&
- git branch sub2
+test_expect_success 'no pull from non-existent subtree' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" HEAD
+ )
'
-# 4
-test_expect_success 'add sub3' '
- create sub3 &&
- git commit -m "sub3" &&
- git branch sub3
+test_expect_success 'add rejects flags for split' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ test_wrong_flag git subtree add --prefix="sub dir" --annotate=foo FETCH_HEAD &&
+ test_wrong_flag git subtree add --prefix="sub dir" --branch=foo FETCH_HEAD &&
+ test_wrong_flag git subtree add --prefix="sub dir" --ignore-joins FETCH_HEAD &&
+ test_wrong_flag git subtree add --prefix="sub dir" --onto=foo FETCH_HEAD &&
+ test_wrong_flag git subtree add --prefix="sub dir" --rejoin FETCH_HEAD
+ )
'
-# Back to mainline
-cd ..
-
-# 5
-test_expect_success 'add main4' '
- create main4 &&
- git commit -m "main4" &&
- git branch -m master mainline &&
- git branch subdir
+test_expect_success 'add subproj as subtree into sub dir/ with --prefix' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD &&
+ test "$(last_commit_subject)" = "Add '\''sub dir/'\'' from commit '\''$(git rev-parse FETCH_HEAD)'\''"
+ )
'
-# 6
-test_expect_success 'fetch subproj history' '
- git fetch ./subproj sub1 &&
- git branch sub1 FETCH_HEAD
+test_expect_success 'add subproj as subtree into sub dir/ with --prefix and --message' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" --message="Added subproject" FETCH_HEAD &&
+ test "$(last_commit_subject)" = "Added subproject"
+ )
'
-# 7
-test_expect_success 'no subtree exists in main tree' '
- test_must_fail git subtree merge --prefix=subdir sub1
+test_expect_success 'add subproj as subtree into sub dir/ with --prefix as -P and --message as -m' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add -P "sub dir" -m "Added subproject" FETCH_HEAD &&
+ test "$(last_commit_subject)" = "Added subproject"
+ )
'
-# 8
-test_expect_success 'no pull from non-existant subtree' '
- test_must_fail git subtree pull --prefix=subdir ./subproj sub1
+test_expect_success 'add subproj as subtree into sub dir/ with --squash and --prefix and --message' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" --message="Added subproject with squash" --squash FETCH_HEAD &&
+ test "$(last_commit_subject)" = "Added subproject with squash"
+ )
'
-# 9
-test_expect_success 'check if --message works for add' '
- git subtree add --prefix=subdir --message="Added subproject" sub1 &&
- check_equal ''"$(last_commit_message)"'' "Added subproject" &&
- undo
-'
+#
+# Tests for 'git subtree merge'
+#
-# 10
-test_expect_success 'check if --message works as -m and --prefix as -P' '
- git subtree add -P subdir -m "Added subproject using git subtree" sub1 &&
- check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" &&
- undo
+test_expect_success 'merge rejects flags for split' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ test_wrong_flag git subtree merge --prefix="sub dir" --annotate=foo FETCH_HEAD &&
+ test_wrong_flag git subtree merge --prefix="sub dir" --branch=foo FETCH_HEAD &&
+ test_wrong_flag git subtree merge --prefix="sub dir" --ignore-joins FETCH_HEAD &&
+ test_wrong_flag git subtree merge --prefix="sub dir" --onto=foo FETCH_HEAD &&
+ test_wrong_flag git subtree merge --prefix="sub dir" --rejoin FETCH_HEAD
+ )
'
-# 11
-test_expect_success 'check if --message works with squash too' '
- git subtree add -P subdir -m "Added subproject with squash" --squash sub1 &&
- check_equal ''"$(last_commit_message)"'' "Added subproject with squash" &&
- undo
+test_expect_success 'merge new subproj history into sub dir/ with --prefix' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ test "$(last_commit_subject)" = "Merge commit '\''$(git rev-parse FETCH_HEAD)'\''"
+ )
'
-# 12
-test_expect_success 'add subproj to mainline' '
- git subtree add --prefix=subdir/ FETCH_HEAD &&
- check_equal ''"$(last_commit_message)"'' "Add '"'subdir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'"
+test_expect_success 'merge new subproj history into sub dir/ with --prefix and --message' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" --message="Merged changes from subproject" FETCH_HEAD &&
+ test "$(last_commit_subject)" = "Merged changes from subproject"
+ )
'
-# 13
-# this shouldn't actually do anything, since FETCH_HEAD is already a parent
-test_expect_success 'merge fetched subproj' '
- git merge -m "merge -s -ours" -s ours FETCH_HEAD
+test_expect_success 'merge new subproj history into sub dir/ with --squash and --prefix and --message' '
+ subtree_test_create_repo "$test_count/sub proj" &&
+ subtree_test_create_repo "$test_count" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" --message="Merged changes from subproject using squash" --squash FETCH_HEAD &&
+ test "$(last_commit_subject)" = "Merged changes from subproject using squash"
+ )
'
-# 14
-test_expect_success 'add main-sub5' '
- create subdir/main-sub5 &&
- git commit -m "main-sub5"
+test_expect_success 'merge the added subproj again, should do nothing' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD &&
+ # this shouldn not actually do anything, since FETCH_HEAD
+ # is already a parent
+ result=$(git merge -s ours -m "merge -s -ours" FETCH_HEAD) &&
+ test "${result}" = "Already up to date."
+ )
'
-# 15
-test_expect_success 'add main6' '
- create main6 &&
- git commit -m "main6 boring"
+test_expect_success 'merge new subproj history into subdir/ with a slash appended to the argument of --prefix' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/subproj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/subproj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./subproj HEAD &&
+ git subtree add --prefix=subdir/ FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/subproj" sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./subproj HEAD &&
+ git subtree merge --prefix=subdir/ FETCH_HEAD &&
+ test "$(last_commit_subject)" = "Merge commit '\''$(git rev-parse FETCH_HEAD)'\''"
+ )
'
-# 16
-test_expect_success 'add main-sub7' '
- create subdir/main-sub7 &&
- git commit -m "main-sub7"
-'
+#
+# Tests for 'git subtree split'
+#
-# 17
-test_expect_success 'fetch new subproj history' '
- git fetch ./subproj sub2 &&
- git branch sub2 FETCH_HEAD
+test_expect_success 'split requires option --prefix' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD &&
+ echo "You must provide the --prefix option." >expected &&
+ test_must_fail git subtree split >actual 2>&1 &&
+ test_debug "printf '"expected: "'" &&
+ test_debug "cat expected" &&
+ test_debug "printf '"actual: "'" &&
+ test_debug "cat actual" &&
+ test_cmp expected actual
+ )
'
-# 18
-test_expect_success 'check if --message works for merge' '
- git subtree merge --prefix=subdir -m "Merged changes from subproject" sub2 &&
- check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" &&
- undo
+test_expect_success 'split requires path given by option --prefix must exist' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD &&
+ echo "'\''non-existent-directory'\'' does not exist; use '\''git subtree add'\''" >expected &&
+ test_must_fail git subtree split --prefix=non-existent-directory >actual 2>&1 &&
+ test_debug "printf '"expected: "'" &&
+ test_debug "cat expected" &&
+ test_debug "printf '"actual: "'" &&
+ test_debug "cat actual" &&
+ test_cmp expected actual
+ )
'
-# 19
-test_expect_success 'check if --message for merge works with squash too' '
- git subtree merge --prefix subdir -m "Merged changes from subproject using squash" --squash sub2 &&
- check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" &&
- undo
+test_expect_success 'split rejects flags for add' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ test_wrong_flag git subtree split --prefix="sub dir" --squash &&
+ test_wrong_flag git subtree split --prefix="sub dir" --message=foo
+ )
'
-# 20
-test_expect_success 'merge new subproj history into subdir' '
- git subtree merge --prefix=subdir FETCH_HEAD &&
- git branch pre-split &&
- check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline"
+test_expect_success 'split sub dir/ with --rejoin' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree split --prefix="sub dir" --annotate="*" --rejoin &&
+ test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''"
+ )
'
-# 21
-test_expect_success 'Check that prefix argument is required for split' '
- echo "You must provide the --prefix option." > expected &&
- test_must_fail git subtree split > actual 2>&1 &&
- test_debug "echo -n expected: " &&
- test_debug "cat expected" &&
- test_debug "echo -n actual: " &&
- test_debug "cat actual" &&
- test_cmp expected actual &&
- rm -f expected actual
+test_expect_success 'split sub dir/ with --rejoin from scratch' '
+ subtree_test_create_repo "$test_count" &&
+ test_create_commit "$test_count" main1 &&
+ (
+ cd "$test_count" &&
+ mkdir "sub dir" &&
+ echo file >"sub dir"/file &&
+ git add "sub dir/file" &&
+ git commit -m"sub dir file" &&
+ split_hash=$(git subtree split --prefix="sub dir" --rejoin) &&
+ git subtree split --prefix="sub dir" --rejoin &&
+ test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''"
+ )
'
-# 22
-test_expect_success 'Check that the <prefix> exists for a split' '
- echo "'"'"'non-existent-directory'"'"'" does not exist\; use "'"'"'git subtree add'"'"'" > expected &&
- test_must_fail git subtree split --prefix=non-existent-directory > actual 2>&1 &&
- test_debug "echo -n expected: " &&
- test_debug "cat expected" &&
- test_debug "echo -n actual: " &&
- test_debug "cat actual" &&
- test_cmp expected actual
-# rm -f expected actual
+test_expect_success 'split sub dir/ with --rejoin and --message' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ git subtree split --prefix="sub dir" --message="Split & rejoin" --annotate="*" --rejoin &&
+ test "$(last_commit_subject)" = "Split & rejoin"
+ )
'
-# 23
-test_expect_success 'check if --message works for split+rejoin' '
- spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
- git branch spl1 "$spl1" &&
- check_equal ''"$(last_commit_message)"'' "Split & rejoin" &&
- undo
+test_expect_success 'split "sub dir"/ with --rejoin and --squash' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" --squash FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git subtree pull --prefix="sub dir" --squash ./"sub proj" HEAD &&
+ MAIN=$(git rev-parse --verify HEAD) &&
+ SUB=$(git -C "sub proj" rev-parse --verify HEAD) &&
+
+ SPLIT=$(git subtree split --prefix="sub dir" --annotate="*" --rejoin --squash) &&
+
+ test_must_fail git merge-base --is-ancestor $SUB HEAD &&
+ test_must_fail git merge-base --is-ancestor $SPLIT HEAD &&
+ git rev-list HEAD ^$MAIN >commit-list &&
+ test_line_count = 2 commit-list &&
+ test "$(git rev-parse --verify HEAD:)" = "$(git rev-parse --verify $MAIN:)" &&
+ test "$(git rev-parse --verify HEAD:"sub dir")" = "$(git rev-parse --verify $SPLIT:)" &&
+ test "$(git rev-parse --verify HEAD^1)" = $MAIN &&
+ test "$(git rev-parse --verify HEAD^2)" != $SPLIT &&
+ test "$(git rev-parse --verify HEAD^2:)" = "$(git rev-parse --verify $SPLIT:)" &&
+ test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$SPLIT'\''"
+ )
'
-# 24
-test_expect_success 'check split with --branch' '
- spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
- undo &&
- git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 &&
- check_equal ''"$(git rev-parse splitbr1)"'' "$spl1"
-'
+test_expect_success 'split then pull "sub dir"/ with --rejoin and --squash' '
+ # 1. "add"
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ git -C "$test_count" subtree --prefix="sub dir" add --squash ./"sub proj" HEAD &&
-# 25
-test_expect_success 'check split with --branch for an existing branch' '
- spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
- undo &&
- git branch splitbr2 sub1 &&
- git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr2 &&
- check_equal ''"$(git rev-parse splitbr2)"'' "$spl1"
-'
+ # 2. commit from parent
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
-# 26
-test_expect_success 'check split with --branch for an incompatible branch' '
- test_must_fail git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir
-'
+ # 3. "split --rejoin --squash"
+ git -C "$test_count" subtree --prefix="sub dir" split --rejoin --squash &&
+ # 4. "pull --squash"
+ test_create_commit "$test_count/sub proj" sub2 &&
+ git -C "$test_count" subtree -d --prefix="sub dir" pull --squash ./"sub proj" HEAD &&
-# 27
-test_expect_success 'check split+rejoin' '
- spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
- undo &&
- git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --rejoin &&
- check_equal ''"$(last_commit_message)"'' "Split '"'"'subdir/'"'"' into commit '"'"'"$spl1"'"'"'"
+ test_must_fail git merge-base HEAD FETCH_HEAD
'
-# 28
-test_expect_success 'add main-sub8' '
- create subdir/main-sub8 &&
- git commit -m "main-sub8"
+test_expect_success 'split "sub dir"/ with --branch' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br &&
+ test "$(git rev-parse subproj-br)" = "$split_hash"
+ )
'
-# To the subproject!
-cd ./subproj
+test_expect_success 'check hash of split' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br &&
+ test "$(git rev-parse subproj-br)" = "$split_hash" &&
+ # Check hash of split
+ new_hash=$(git rev-parse subproj-br^2) &&
+ (
+ cd ./"sub proj" &&
+ subdir_hash=$(git rev-parse HEAD) &&
+ test "$new_hash" = "$subdir_hash"
+ )
+ )
+'
-# 29
-test_expect_success 'merge split into subproj' '
- git fetch .. spl1 &&
- git branch spl1 FETCH_HEAD &&
- git merge FETCH_HEAD
+test_expect_success 'split "sub dir"/ with --branch for an existing branch' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git branch subproj-br FETCH_HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br &&
+ test "$(git rev-parse subproj-br)" = "$split_hash"
+ )
'
-# 30
-test_expect_success 'add sub9' '
- create sub9 &&
- git commit -m "sub9"
+test_expect_success 'split "sub dir"/ with --branch for an incompatible branch' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git branch init HEAD &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ test_must_fail git subtree split --prefix="sub dir" --branch init
+ )
'
-# Back to mainline
-cd ..
+#
+# Tests for 'git subtree pull'
+#
-# 31
-test_expect_success 'split for sub8' '
- split2=''"$(git subtree split --annotate='"'*'"' --prefix subdir/ --rejoin)"''
- git branch split2 "$split2"
+test_expect_success 'pull requires option --prefix' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ cd "$test_count" &&
+ test_must_fail git subtree pull ./"sub proj" HEAD >out 2>err &&
+
+ echo "You must provide the --prefix option." >expected &&
+ test_must_be_empty out &&
+ test_cmp expected err
+ )
'
-# 32
-test_expect_success 'add main-sub10' '
- create subdir/main-sub10 &&
- git commit -m "main-sub10"
+test_expect_success 'pull requires path given by option --prefix must exist' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" HEAD >out 2>err &&
+
+ echo "'\''sub dir'\'' does not exist; use '\''git subtree add'\''" >expected &&
+ test_must_be_empty out &&
+ test_cmp expected err
+ )
'
-# 33
-test_expect_success 'split for sub10' '
- spl3=''"$(git subtree split --annotate='"'*'"' --prefix subdir --rejoin)"'' &&
- git branch spl3 "$spl3"
+test_expect_success 'pull basic operation' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ cd "$test_count" &&
+ exp=$(git -C "sub proj" rev-parse --verify HEAD:) &&
+ git subtree pull --prefix="sub dir" ./"sub proj" HEAD &&
+ act=$(git rev-parse --verify HEAD:"sub dir") &&
+ test "$act" = "$exp"
+ )
'
-# To the subproject!
-cd ./subproj
-
-# 34
-test_expect_success 'merge split into subproj' '
- git fetch .. spl3 &&
- git branch spl3 FETCH_HEAD &&
- git merge FETCH_HEAD &&
- git branch subproj-merge-spl3
+test_expect_success 'pull rejects flags for split' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ test_must_fail git subtree pull --prefix="sub dir" --annotate=foo ./"sub proj" HEAD &&
+ test_must_fail git subtree pull --prefix="sub dir" --branch=foo ./"sub proj" HEAD &&
+ test_must_fail git subtree pull --prefix="sub dir" --ignore-joins ./"sub proj" HEAD &&
+ test_must_fail git subtree pull --prefix="sub dir" --onto=foo ./"sub proj" HEAD &&
+ test_must_fail git subtree pull --prefix="sub dir" --rejoin ./"sub proj" HEAD
+ )
'
-chkm="main4 main6"
-chkms="main-sub10 main-sub5 main-sub7 main-sub8"
-chkms_sub=$(echo $chkms | multiline | sed 's,^,subdir/,' | fixnl)
-chks="sub1 sub2 sub3 sub9"
-chks_sub=$(echo $chks | multiline | sed 's,^,subdir/,' | fixnl)
+#
+# Tests for 'git subtree push'
+#
-# 35
-test_expect_success 'make sure exactly the right set of files ends up in the subproj' '
- subfiles=''"$(git ls-files | fixnl)"'' &&
- check_equal "$subfiles" "$chkms $chks"
+test_expect_success 'push requires option --prefix' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD &&
+ echo "You must provide the --prefix option." >expected &&
+ test_must_fail git subtree push "./sub proj" from-mainline >actual 2>&1 &&
+ test_debug "printf '"expected: "'" &&
+ test_debug "cat expected" &&
+ test_debug "printf '"actual: "'" &&
+ test_debug "cat actual" &&
+ test_cmp expected actual
+ )
'
-# 36
-test_expect_success 'make sure the subproj history *only* contains commits that affect the subdir' '
- allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
- check_equal "$allchanges" "$chkms $chks"
+test_expect_success 'push requires path given by option --prefix must exist' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD &&
+ echo "'\''non-existent-directory'\'' does not exist; use '\''git subtree add'\''" >expected &&
+ test_must_fail git subtree push --prefix=non-existent-directory "./sub proj" from-mainline >actual 2>&1 &&
+ test_debug "printf '"expected: "'" &&
+ test_debug "cat expected" &&
+ test_debug "printf '"actual: "'" &&
+ test_debug "cat actual" &&
+ test_cmp expected actual
+ )
'
-# Back to mainline
-cd ..
-
-# 37
-test_expect_success 'pull from subproj' '
- git fetch ./subproj subproj-merge-spl3 &&
- git branch subproj-merge-spl3 FETCH_HEAD &&
- git subtree pull --prefix=subdir ./subproj subproj-merge-spl3
+test_expect_success 'push rejects flags for add' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ test_wrong_flag git subtree split --prefix="sub dir" --squash ./"sub proj" from-mainline &&
+ test_wrong_flag git subtree split --prefix="sub dir" --message=foo ./"sub proj" from-mainline
+ )
'
-# 38
-test_expect_success 'make sure exactly the right set of files ends up in the mainline' '
- mainfiles=''"$(git ls-files | fixnl)"'' &&
- check_equal "$mainfiles" "$chkm $chkms_sub $chks_sub"
+test_expect_success 'push basic operation' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ before=$(git rev-parse --verify HEAD) &&
+ split_hash=$(git subtree split --prefix="sub dir") &&
+ git subtree push --prefix="sub dir" ./"sub proj" from-mainline &&
+ test "$before" = "$(git rev-parse --verify HEAD)" &&
+ test "$split_hash" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
'
-# 39
-test_expect_success 'make sure each filename changed exactly once in the entire history' '
- # main-sub?? and /subdir/main-sub?? both change, because those are the
- # changes that were split into their own history. And subdir/sub?? never
- # change, since they were *only* changed in the subtree branch.
- allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
- check_equal "$allchanges" ''"$(echo $chkms $chkm $chks $chkms_sub | multiline | sort | fixnl)"''
+test_expect_success 'push sub dir/ with --rejoin' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree push --prefix="sub dir" --annotate="*" --rejoin ./"sub proj" from-mainline &&
+ test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''" &&
+ test "$split_hash" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
'
-# 40
-test_expect_success 'make sure the --rejoin commits never make it into subproj' '
- check_equal ''"$(git log --pretty=format:'"'%s'"' HEAD^2 | grep -i split)"'' ""
+test_expect_success 'push sub dir/ with --rejoin from scratch' '
+ subtree_test_create_repo "$test_count" &&
+ test_create_commit "$test_count" main1 &&
+ (
+ cd "$test_count" &&
+ mkdir "sub dir" &&
+ echo file >"sub dir"/file &&
+ git add "sub dir/file" &&
+ git commit -m"sub dir file" &&
+ split_hash=$(git subtree split --prefix="sub dir" --rejoin) &&
+ git init --bare "sub proj.git" &&
+ git subtree push --prefix="sub dir" --rejoin ./"sub proj.git" from-mainline &&
+ test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''" &&
+ test "$split_hash" = "$(git -C "sub proj.git" rev-parse --verify refs/heads/from-mainline)"
+ )
'
-# 41
-test_expect_success 'make sure no "git subtree" tagged commits make it into subproj' '
- # They are meaningless to subproj since one side of the merge refers to the mainline
- check_equal ''"$(git log --pretty=format:'"'%s%n%b'"' HEAD^2 | grep "git-subtree.*:")"'' ""
+test_expect_success 'push sub dir/ with --rejoin and --message' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ git subtree push --prefix="sub dir" --message="Split & rejoin" --annotate="*" --rejoin ./"sub proj" from-mainline &&
+ test "$(last_commit_subject)" = "Split & rejoin" &&
+ split_hash="$(git rev-parse --verify HEAD^2)" &&
+ test "$split_hash" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
'
-# prepare second pair of repositories
-mkdir test2
-cd test2
-
-# 42
-test_expect_success 'init main' '
- test_create_repo main
+test_expect_success 'push "sub dir"/ with --rejoin and --squash' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" --squash FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git subtree pull --prefix="sub dir" --squash ./"sub proj" HEAD &&
+ MAIN=$(git rev-parse --verify HEAD) &&
+ SUB=$(git -C "sub proj" rev-parse --verify HEAD) &&
+
+ SPLIT=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree push --prefix="sub dir" --annotate="*" --rejoin --squash ./"sub proj" from-mainline &&
+
+ test_must_fail git merge-base --is-ancestor $SUB HEAD &&
+ test_must_fail git merge-base --is-ancestor $SPLIT HEAD &&
+ git rev-list HEAD ^$MAIN >commit-list &&
+ test_line_count = 2 commit-list &&
+ test "$(git rev-parse --verify HEAD:)" = "$(git rev-parse --verify $MAIN:)" &&
+ test "$(git rev-parse --verify HEAD:"sub dir")" = "$(git rev-parse --verify $SPLIT:)" &&
+ test "$(git rev-parse --verify HEAD^1)" = $MAIN &&
+ test "$(git rev-parse --verify HEAD^2)" != $SPLIT &&
+ test "$(git rev-parse --verify HEAD^2:)" = "$(git rev-parse --verify $SPLIT:)" &&
+ test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$SPLIT'\''" &&
+ test "$SPLIT" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
'
-cd main
-
-# 43
-test_expect_success 'add main1' '
- create main1 &&
- git commit -m "main1"
+test_expect_success 'push "sub dir"/ with --branch' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree push --prefix="sub dir" --annotate="*" --branch subproj-br ./"sub proj" from-mainline &&
+ test "$(git rev-parse subproj-br)" = "$split_hash" &&
+ test "$split_hash" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
'
-cd ..
-
-# 44
-test_expect_success 'init sub' '
- test_create_repo sub
+test_expect_success 'check hash of push' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree push --prefix="sub dir" --annotate="*" --branch subproj-br ./"sub proj" from-mainline &&
+ test "$(git rev-parse subproj-br)" = "$split_hash" &&
+ # Check hash of split
+ new_hash=$(git rev-parse subproj-br^2) &&
+ (
+ cd ./"sub proj" &&
+ subdir_hash=$(git rev-parse HEAD) &&
+ test "$new_hash" = "$subdir_hash"
+ ) &&
+ test "$split_hash" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
'
-cd sub
+test_expect_success 'push "sub dir"/ with --branch for an existing branch' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git branch subproj-br FETCH_HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ split_hash=$(git subtree split --prefix="sub dir" --annotate="*") &&
+ git subtree push --prefix="sub dir" --annotate="*" --branch subproj-br ./"sub proj" from-mainline &&
+ test "$(git rev-parse subproj-br)" = "$split_hash" &&
+ test "$split_hash" = "$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline)"
+ )
+'
-# 45
-test_expect_success 'add sub2' '
- create sub2 &&
- git commit -m "sub2"
+test_expect_success 'push "sub dir"/ with --branch for an incompatible branch' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git branch init HEAD &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ test_must_fail git subtree push --prefix="sub dir" --branch init "./sub proj" from-mainline
+ )
'
-cd ../main
+test_expect_success 'push "sub dir"/ with a local rev' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ bad_tree=$(git rev-parse --verify HEAD:"sub dir") &&
+ good_tree=$(git rev-parse --verify HEAD^:"sub dir") &&
+ git subtree push --prefix="sub dir" --annotate="*" ./"sub proj" HEAD^:from-mainline &&
+ split_tree=$(git -C "sub proj" rev-parse --verify refs/heads/from-mainline:) &&
+ test "$split_tree" = "$good_tree"
+ )
+'
-# check if split can find proper base without --onto
+#
+# Validity checking
+#
-# 46
-test_expect_success 'add sub as subdir in main' '
- git fetch ../sub master &&
- git branch sub2 FETCH_HEAD &&
- git subtree add --prefix subdir sub2
+test_expect_success 'make sure exactly the right set of files ends up in the subproj' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ test_create_commit "$test_count/sub proj" sub3 &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
+ (
+ cd "$test_count/sub proj" &&
+ git fetch .. subproj-br &&
+ git merge FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub4 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub4 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ (
+ cd "$test_count/sub proj" &&
+ git fetch .. subproj-br &&
+ git merge FETCH_HEAD &&
+
+ test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 \
+ sub1 sub2 sub3 sub4 >expect &&
+ git ls-files >actual &&
+ test_cmp expect actual
+ )
'
-cd ../sub
-
-# 47
-test_expect_success 'add sub3' '
- create sub3 &&
- git commit -m "sub3"
+test_expect_success 'make sure the subproj *only* contains commits that affect the "sub dir"' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ test_create_commit "$test_count/sub proj" sub3 &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
+ (
+ cd "$test_count/sub proj" &&
+ git fetch .. subproj-br &&
+ git merge FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub4 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub4 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ (
+ cd "$test_count/sub proj" &&
+ git fetch .. subproj-br &&
+ git merge FETCH_HEAD &&
+
+ test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 \
+ sub1 sub2 sub3 sub4 >expect &&
+ git log --name-only --pretty=format:"" >log &&
+ sort <log | sed "/^\$/ d" >actual &&
+ test_cmp expect actual
+ )
'
-cd ../main
+test_expect_success 'make sure exactly the right set of files ends up in the mainline' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ test_create_commit "$test_count/sub proj" sub3 &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
+ (
+ cd "$test_count/sub proj" &&
+ git fetch .. subproj-br &&
+ git merge FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub4 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub4 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ (
+ cd "$test_count/sub proj" &&
+ git fetch .. subproj-br &&
+ git merge FETCH_HEAD
+ ) &&
+ (
+ cd "$test_count" &&
+ git subtree pull --prefix="sub dir" ./"sub proj" HEAD &&
+
+ test_write_lines main1 main2 >chkm &&
+ test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 >chkms &&
+ sed "s,^,sub dir/," chkms >chkms_sub &&
+ test_write_lines sub1 sub2 sub3 sub4 >chks &&
+ sed "s,^,sub dir/," chks >chks_sub &&
+
+ cat chkm chkms_sub chks_sub >expect &&
+ git ls-files >actual &&
+ test_cmp expect actual
+ )
+'
-# 48
-test_expect_success 'merge from sub' '
- git fetch ../sub master &&
- git branch sub3 FETCH_HEAD &&
- git subtree merge --prefix subdir sub3
+test_expect_success 'make sure each filename changed exactly once in the entire history' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git config log.date relative &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ test_create_commit "$test_count/sub proj" sub3 &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
+ (
+ cd "$test_count/sub proj" &&
+ git fetch .. subproj-br &&
+ git merge FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub4 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub4 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ (
+ cd "$test_count/sub proj" &&
+ git fetch .. subproj-br &&
+ git merge FETCH_HEAD
+ ) &&
+ (
+ cd "$test_count" &&
+ git subtree pull --prefix="sub dir" ./"sub proj" HEAD &&
+
+ test_write_lines main1 main2 >chkm &&
+ test_write_lines sub1 sub2 sub3 sub4 >chks &&
+ test_write_lines main-sub1 main-sub2 main-sub3 main-sub4 >chkms &&
+ sed "s,^,sub dir/," chkms >chkms_sub &&
+
+ # main-sub?? and /"sub dir"/main-sub?? both change, because those are the
+ # changes that were split into their own history. And "sub dir"/sub?? never
+ # change, since they were *only* changed in the subtree branch.
+ git log --name-only --pretty=format:"" >log &&
+ sort <log >sorted-log &&
+ sed "/^$/ d" sorted-log >actual &&
+
+ cat chkms chkm chks chkms_sub >expect-unsorted &&
+ sort expect-unsorted >expect &&
+ test_cmp expect actual
+ )
'
-# 49
-test_expect_success 'add main-sub4' '
- create subdir/main-sub4 &&
- git commit -m "main-sub4"
+test_expect_success 'make sure the --rejoin commits never make it into subproj' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ test_create_commit "$test_count/sub proj" sub3 &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
+ (
+ cd "$test_count/sub proj" &&
+ git fetch .. subproj-br &&
+ git merge FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub4 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub4 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ (
+ cd "$test_count/sub proj" &&
+ git fetch .. subproj-br &&
+ git merge FETCH_HEAD
+ ) &&
+ (
+ cd "$test_count" &&
+ git subtree pull --prefix="sub dir" ./"sub proj" HEAD &&
+ test "$(git log --pretty=format:"%s" HEAD^2 | grep -i split)" = ""
+ )
'
-# 50
-test_expect_success 'split for main-sub4 without --onto' '
- git subtree split --prefix subdir --branch mainsub4
+test_expect_success 'make sure no "git subtree" tagged commits make it into subproj' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ test_create_commit "$test_count/sub proj" sub3 &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
+ (
+ cd "$test_count/sub proj" &&
+ git fetch .. subproj-br &&
+ git merge FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub4 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub4 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin
+ ) &&
+ (
+ cd "$test_count/sub proj" &&
+ git fetch .. subproj-br &&
+ git merge FETCH_HEAD
+ ) &&
+ (
+ cd "$test_count" &&
+ git subtree pull --prefix="sub dir" ./"sub proj" HEAD &&
+
+ # They are meaningless to subproj since one side of the merge refers to the mainline
+ test "$(git log --pretty=format:"%s%n%b" HEAD^2 | grep "git-subtree.*:")" = ""
+ )
'
-# at this point, the new commit parent should be sub3 if it is not,
-# something went wrong (the "newparent" of "master~" commit should
-# have been sub3, but it was not, because its cache was not set to
-# itself)
+#
+# A new set of tests
+#
-# 51
-test_expect_success 'check that the commit parent is sub3' '
- check_equal ''"$(git log --pretty=format:%P -1 mainsub4)"'' ''"$(git rev-parse sub3)"''
+test_expect_success 'make sure "git subtree split" find the correct parent' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git branch subproj-ref FETCH_HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --branch subproj-br &&
+
+ # at this point, the new commit parent should be subproj-ref, if it is
+ # not, something went wrong (the "newparent" of "HEAD~" commit should
+ # have been sub2, but it was not, because its cache was not set to
+ # itself)
+ test "$(git log --pretty=format:%P -1 subproj-br)" = "$(git rev-parse subproj-ref)"
+ )
'
-# 52
-test_expect_success 'add main-sub5' '
- mkdir subdir2 &&
- create subdir2/main-sub5 &&
- git commit -m "main-sub5"
+test_expect_success 'split a new subtree without --onto option' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --branch subproj-br
+ ) &&
+ mkdir "$test_count"/"sub dir2" &&
+ test_create_commit "$test_count" "sub dir2"/main-sub2 &&
+ (
+ cd "$test_count" &&
+
+ # also test that we still can split out an entirely new subtree
+ # if the parent of the first commit in the tree is not empty,
+ # then the new subtree has accidentally been attached to something
+ git subtree split --prefix="sub dir2" --branch subproj2-br &&
+ test "$(git log --pretty=format:%P -1 subproj2-br)" = ""
+ )
'
-# 53
-test_expect_success 'split for main-sub5 without --onto' '
- # also test that we still can split out an entirely new subtree
- # if the parent of the first commit in the tree is not empty,
- # then the new subtree has accidently been attached to something
- git subtree split --prefix subdir2 --branch mainsub5 &&
- check_equal ''"$(git log --pretty=format:%P -1 mainsub5)"'' ""
+test_expect_success 'verify one file change per commit' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git branch sub1 FETCH_HEAD &&
+ git subtree add --prefix="sub dir" sub1
+ ) &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir" --branch subproj-br
+ ) &&
+ mkdir "$test_count"/"sub dir2" &&
+ test_create_commit "$test_count" "sub dir2"/main-sub2 &&
+ (
+ cd "$test_count" &&
+ git subtree split --prefix="sub dir2" --branch subproj2-br &&
+
+ git log --format="%H" >commit-list &&
+ while read commit
+ do
+ git log -n1 --format="" --name-only "$commit" >file-list &&
+ test_line_count -le 1 file-list || return 1
+ done <commit-list
+ )
'
-# make sure no patch changes more than one file. The original set of commits
-# changed only one file each. A multi-file change would imply that we pruned
-# commits too aggressively.
-joincommits()
-{
- commit=
- all=
- while read x y; do
- #echo "{$x}" >&2
- if [ -z "$x" ]; then
- continue
- elif [ "$x" = "commit:" ]; then
- if [ -n "$commit" ]; then
- echo "$commit $all"
- all=
- fi
- commit="$y"
- else
- all="$all $y"
- fi
- done
- echo "$commit $all"
-}
+test_expect_success 'push split to subproj' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/sub proj" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/sub proj" sub1 &&
+ (
+ cd "$test_count" &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree add --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub1 &&
+ test_create_commit "$test_count" main2 &&
+ test_create_commit "$test_count/sub proj" sub2 &&
+ test_create_commit "$test_count" "sub dir"/main-sub2 &&
+ (
+ cd $test_count/"sub proj" &&
+ git branch sub-branch-1 &&
+ cd .. &&
+ git fetch ./"sub proj" HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD
+ ) &&
+ test_create_commit "$test_count" "sub dir"/main-sub3 &&
+ (
+ cd "$test_count" &&
+ git subtree push ./"sub proj" --prefix "sub dir" sub-branch-1 &&
+ cd ./"sub proj" &&
+ git checkout sub-branch-1 &&
+ test "$(last_commit_subject)" = "sub dir/main-sub3"
+ )
+'
-# 54
-test_expect_success 'verify one file change per commit' '
- x= &&
- list=''"$(git log --pretty=format:'"'commit: %H'"' | joincommits)"'' &&
-# test_debug "echo HERE" &&
-# test_debug "echo ''"$list"''" &&
- (git log --pretty=format:'"'commit: %H'"' | joincommits |
- ( while read commit a b; do
- test_debug "echo Verifying commit "''"$commit"''
- test_debug "echo a: "''"$a"''
- test_debug "echo b: "''"$b"''
- check_equal "$b" ""
- x=1
- done
- check_equal "$x" 1
- ))
+#
+# This test covers 2 cases in subtree split copy_or_skip code
+# 1) Merges where one parent is a superset of the changes of the other
+# parent regarding changes to the subtree, in this case the merge
+# commit should be copied
+# 2) Merges where only one parent operate on the subtree, and the merge
+# commit should be skipped
+#
+# (1) is checked by ensuring subtree_tip is a descendent of subtree_branch
+# (2) should have a check added (not_a_subtree_change shouldn't be present
+# on the produced subtree)
+#
+# Other related cases which are not tested (or currently handled correctly)
+# - Case (1) where there are more than 2 parents, it will sometimes correctly copy
+# the merge, and sometimes not
+# - Merge commit where both parents have same tree as the merge, currently
+# will always be skipped, even if they reached that state via different
+# set of commits.
+#
+
+test_expect_success 'subtree descendant check' '
+ subtree_test_create_repo "$test_count" &&
+ defaultBranch=$(sed "s,ref: refs/heads/,," "$test_count/.git/HEAD") &&
+ test_create_commit "$test_count" folder_subtree/a &&
+ (
+ cd "$test_count" &&
+ git branch branch
+ ) &&
+ test_create_commit "$test_count" folder_subtree/0 &&
+ test_create_commit "$test_count" folder_subtree/b &&
+ cherry=$(cd "$test_count"; git rev-parse HEAD) &&
+ (
+ cd "$test_count" &&
+ git checkout branch
+ ) &&
+ test_create_commit "$test_count" commit_on_branch &&
+ (
+ cd "$test_count" &&
+ git cherry-pick $cherry &&
+ git checkout $defaultBranch &&
+ git merge -m "merge should be kept on subtree" branch &&
+ git branch no_subtree_work_branch
+ ) &&
+ test_create_commit "$test_count" folder_subtree/d &&
+ (
+ cd "$test_count" &&
+ git checkout no_subtree_work_branch
+ ) &&
+ test_create_commit "$test_count" not_a_subtree_change &&
+ (
+ cd "$test_count" &&
+ git checkout $defaultBranch &&
+ git merge -m "merge should be skipped on subtree" no_subtree_work_branch &&
+
+ git subtree split --prefix folder_subtree/ --branch subtree_tip $defaultBranch &&
+ git subtree split --prefix folder_subtree/ --branch subtree_branch branch &&
+ test $(git rev-list --count subtree_tip..subtree_branch) = 0
+ )
'
test_done
diff --git a/contrib/subtree/todo b/contrib/subtree/todo
index 7e44b0024f..32d2ce3a40 100644
--- a/contrib/subtree/todo
+++ b/contrib/subtree/todo
@@ -12,8 +12,6 @@
exactly the right subtree structure, rather than using
subtree merge...)
- add a 'push' subcommand to parallel 'pull'
-
add a 'log' subcommand to see what's new in a subtree?
add to-submodule and from-submodule commands
@@ -25,9 +23,9 @@
"pull" and "merge" commands should fail if you've never merged
that --prefix before
-
+
docs should provide an example of "add"
-
+
note that the initial split doesn't *have* to have a commitid
specified... that's just an optimization
@@ -35,7 +33,7 @@
get a misleading "prefix must end with /" message from
one of the other git tools that git-subtree calls. Should
detect this situation and print the *real* problem.
-
+
"pull --squash" should do fetch-synthesize-merge, but instead just
does "pull" directly, which doesn't work at all.
diff --git a/contrib/svn-fe/.gitignore b/contrib/svn-fe/.gitignore
deleted file mode 100644
index 02a7791585..0000000000
--- a/contrib/svn-fe/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-/*.xml
-/*.1
-/*.html
-/svn-fe
diff --git a/contrib/svn-fe/Makefile b/contrib/svn-fe/Makefile
deleted file mode 100644
index 360d8da417..0000000000
--- a/contrib/svn-fe/Makefile
+++ /dev/null
@@ -1,63 +0,0 @@
-all:: svn-fe$X
-
-CC = gcc
-RM = rm -f
-MV = mv
-
-CFLAGS = -g -O2 -Wall
-LDFLAGS =
-ALL_CFLAGS = $(CFLAGS)
-ALL_LDFLAGS = $(LDFLAGS)
-EXTLIBS =
-
-GIT_LIB = ../../libgit.a
-VCSSVN_LIB = ../../vcs-svn/lib.a
-LIBS = $(VCSSVN_LIB) $(GIT_LIB) $(EXTLIBS)
-
-QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
-QUIET_SUBDIR1 =
-
-ifneq ($(findstring $(MAKEFLAGS),w),w)
-PRINT_DIR = --no-print-directory
-else # "make -w"
-NO_SUBDIR = :
-endif
-
-ifneq ($(findstring $(MAKEFLAGS),s),s)
-ifndef V
- QUIET_CC = @echo ' ' CC $@;
- QUIET_LINK = @echo ' ' LINK $@;
- QUIET_SUBDIR0 = +@subdir=
- QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
- $(MAKE) $(PRINT_DIR) -C $$subdir
-endif
-endif
-
-svn-fe$X: svn-fe.o $(VCSSVN_LIB) $(GIT_LIB)
- $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ svn-fe.o \
- $(ALL_LDFLAGS) $(LIBS)
-
-svn-fe.o: svn-fe.c ../../vcs-svn/svndump.h
- $(QUIET_CC)$(CC) -I../../vcs-svn -o $*.o -c $(ALL_CFLAGS) $<
-
-svn-fe.html: svn-fe.txt
- $(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \
- MAN_TXT=../contrib/svn-fe/svn-fe.txt \
- ../contrib/svn-fe/$@
-
-svn-fe.1: svn-fe.txt
- $(QUIET_SUBDIR0)../../Documentation $(QUIET_SUBDIR1) \
- MAN_TXT=../contrib/svn-fe/svn-fe.txt \
- ../contrib/svn-fe/$@
- $(MV) ../../Documentation/svn-fe.1 .
-
-../../vcs-svn/lib.a: FORCE
- $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) vcs-svn/lib.a
-
-../../libgit.a: FORCE
- $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) libgit.a
-
-clean:
- $(RM) svn-fe$X svn-fe.o svn-fe.html svn-fe.xml svn-fe.1
-
-.PHONY: all clean FORCE
diff --git a/contrib/svn-fe/svn-fe.c b/contrib/svn-fe/svn-fe.c
deleted file mode 100644
index f363505abb..0000000000
--- a/contrib/svn-fe/svn-fe.c
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * This file is in the public domain.
- * You may freely use, modify, distribute, and relicense it.
- */
-
-#include <stdlib.h>
-#include "svndump.h"
-
-int main(int argc, char **argv)
-{
- if (svndump_init(NULL))
- return 1;
- svndump_read((argc > 1) ? argv[1] : NULL, "refs/heads/master",
- "refs/notes/svn/revs");
- svndump_deinit();
- svndump_reset();
- return 0;
-}
diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt
deleted file mode 100644
index 1128ab2ce4..0000000000
--- a/contrib/svn-fe/svn-fe.txt
+++ /dev/null
@@ -1,71 +0,0 @@
-svn-fe(1)
-=========
-
-NAME
-----
-svn-fe - convert an SVN "dumpfile" to a fast-import stream
-
-SYNOPSIS
---------
-[verse]
-mkfifo backchannel &&
-svnadmin dump --deltas REPO |
- svn-fe [url] 3<backchannel |
- git fast-import --cat-blob-fd=3 3>backchannel
-
-DESCRIPTION
------------
-
-Converts a Subversion dumpfile into input suitable for
-git-fast-import(1) and similar importers. REPO is a path to a
-Subversion repository mirrored on the local disk. Remote Subversion
-repositories can be mirrored on local disk using the `svnsync`
-command.
-
-Note: this tool is very young. The details of its commandline
-interface may change in backward incompatible ways.
-
-INPUT FORMAT
-------------
-Subversion's repository dump format is documented in full in
-`notes/dump-load-format.txt` from the Subversion source tree.
-Files in this format can be generated using the 'svnadmin dump' or
-'svk admin dump' command.
-
-OUTPUT FORMAT
--------------
-The fast-import format is documented by the git-fast-import(1)
-manual page.
-
-NOTES
------
-Subversion dumps do not record a separate author and committer for
-each revision, nor a separate display name and email address for
-each author. Like git-svn(1), 'svn-fe' will use the name
-
----------
-user <user@UUID>
----------
-
-as committer, where 'user' is the value of the `svn:author` property
-and 'UUID' the repository's identifier.
-
-To support incremental imports, 'svn-fe' puts a `git-svn-id` line at
-the end of each commit log message if passed a URL on the command
-line. This line has the form `git-svn-id: URL@REVNO UUID`.
-
-The resulting repository will generally require further processing
-to put each project in its own repository and to separate the history
-of each branch. The 'git filter-branch --subdirectory-filter' command
-may be useful for this purpose.
-
-BUGS
-----
-Empty directories and unknown properties are silently discarded.
-
-The exit status does not reflect whether an error was detected.
-
-SEE ALSO
---------
-git-svn(1), svn2git(1), svk(1), git-filter-branch(1), git-fast-import(1),
-https://svn.apache.org/repos/asf/subversion/trunk/notes/dump-load-format.txt
diff --git a/contrib/svn-fe/svnrdump_sim.py b/contrib/svn-fe/svnrdump_sim.py
deleted file mode 100755
index 1cfac4a6f8..0000000000
--- a/contrib/svn-fe/svnrdump_sim.py
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/python
-"""
-Simulates svnrdump by replaying an existing dump from a file, taking care
-of the specified revision range.
-To simulate incremental imports the environment variable SVNRMAX can be set
-to the highest revision that should be available.
-"""
-import sys, os
-
-
-def getrevlimit():
- var = 'SVNRMAX'
- if os.environ.has_key(var):
- return os.environ[var]
- return None
-
-def writedump(url, lower, upper):
- if url.startswith('sim://'):
- filename = url[6:]
- if filename[-1] == '/': filename = filename[:-1] #remove terminating slash
- else:
- raise ValueError('sim:// url required')
- f = open(filename, 'r');
- state = 'header'
- wroterev = False
- while(True):
- l = f.readline()
- if l == '': break
- if state == 'header' and l.startswith('Revision-number: '):
- state = 'prefix'
- if state == 'prefix' and l == 'Revision-number: %s\n' % lower:
- state = 'selection'
- if not upper == 'HEAD' and state == 'selection' and l == 'Revision-number: %s\n' % upper:
- break;
-
- if state == 'header' or state == 'selection':
- if state == 'selection': wroterev = True
- sys.stdout.write(l)
- return wroterev
-
-if __name__ == "__main__":
- if not (len(sys.argv) in (3, 4, 5)):
- print "usage: %s dump URL -rLOWER:UPPER"
- sys.exit(1)
- if not sys.argv[1] == 'dump': raise NotImplementedError('only "dump" is suppported.')
- url = sys.argv[2]
- r = ('0', 'HEAD')
- if len(sys.argv) == 4 and sys.argv[3][0:2] == '-r':
- r = sys.argv[3][2:].lstrip().split(':')
- if not getrevlimit() is None: r[1] = getrevlimit()
- if writedump(url, r[0], r[1]): ret = 0
- else: ret = 1
- sys.exit(ret)
diff --git a/contrib/thunderbird-patch-inline/appp.sh b/contrib/thunderbird-patch-inline/appp.sh
index 5eb4a51643..1053872eea 100755
--- a/contrib/thunderbird-patch-inline/appp.sh
+++ b/contrib/thunderbird-patch-inline/appp.sh
@@ -10,7 +10,7 @@ CONFFILE=~/.appprc
SEP="-=-=-=-=-=-=-=-=-=# Don't remove this line #=-=-=-=-=-=-=-=-=-"
if [ -e "$CONFFILE" ] ; then
- LAST_DIR=`grep -m 1 "^LAST_DIR=" "${CONFFILE}"|sed -e 's/^LAST_DIR=//'`
+ LAST_DIR=$(grep -m 1 "^LAST_DIR=" "${CONFFILE}"|sed -e 's/^LAST_DIR=//')
cd "${LAST_DIR}"
else
cd > /dev/null
@@ -25,14 +25,14 @@ fi
cd - > /dev/null
-SUBJECT=`sed -n -e '/^Subject: /p' "${PATCH}"`
-HEADERS=`sed -e '/^'"${SEP}"'$/,$d' $1`
-BODY=`sed -e "1,/${SEP}/d" $1`
-CMT_MSG=`sed -e '1,/^$/d' -e '/^---$/,$d' "${PATCH}"`
-DIFF=`sed -e '1,/^---$/d' "${PATCH}"`
+SUBJECT=$(sed -n -e '/^Subject: /p' "${PATCH}")
+HEADERS=$(sed -e '/^'"${SEP}"'$/,$d' $1)
+BODY=$(sed -e "1,/${SEP}/d" $1)
+CMT_MSG=$(sed -e '1,/^$/d' -e '/^---$/,$d' "${PATCH}")
+DIFF=$(sed -e '1,/^---$/d' "${PATCH}")
-CCS=`echo -e "$CMT_MSG\n$HEADERS" | sed -n -e 's/^Cc: \(.*\)$/\1,/gp' \
- -e 's/^Signed-off-by: \(.*\)/\1,/gp'`
+CCS=$(echo -e "$CMT_MSG\n$HEADERS" | sed -n -e 's/^Cc: \(.*\)$/\1,/gp' \
+ -e 's/^Signed-off-by: \(.*\)/\1,/gp')
echo "$SUBJECT" > $1
echo "Cc: $CCS" >> $1
@@ -48,7 +48,7 @@ if [ "x${BODY}x" != "xx" ] ; then
fi
echo "$DIFF" >> $1
-LAST_DIR=`dirname "${PATCH}"`
+LAST_DIR=$(dirname "${PATCH}")
grep -v "^LAST_DIR=" "${CONFFILE}" > "${CONFFILE}_"
echo "LAST_DIR=${LAST_DIR}" >> "${CONFFILE}_"
diff --git a/contrib/update-unicode/.gitignore b/contrib/update-unicode/.gitignore
new file mode 100644
index 0000000000..b0ebc6aad2
--- /dev/null
+++ b/contrib/update-unicode/.gitignore
@@ -0,0 +1,3 @@
+uniset/
+UnicodeData.txt
+EastAsianWidth.txt
diff --git a/contrib/update-unicode/README b/contrib/update-unicode/README
new file mode 100644
index 0000000000..151a197041
--- /dev/null
+++ b/contrib/update-unicode/README
@@ -0,0 +1,20 @@
+TL;DR: Run update_unicode.sh after the publication of a new Unicode
+standard and commit the resulting unicode-widths.h file.
+
+The long version
+================
+
+The Git source code ships the file unicode-widths.h which contains
+tables of zero and double width Unicode code points, respectively.
+These tables are generated using update_unicode.sh in this directory.
+update_unicode.sh itself uses a third-party tool, uniset, to query two
+Unicode data files for the interesting code points.
+
+On first run, update_unicode.sh clones uniset from Github and builds it.
+This requires a current-ish version of autoconf (2.69 works per December
+2016).
+
+On each run, update_unicode.sh checks whether more recent Unicode data
+files are available from the Unicode consortium, and rebuilds the header
+unicode-widths.h with the new data. The new header can then be
+committed.
diff --git a/contrib/update-unicode/update_unicode.sh b/contrib/update-unicode/update_unicode.sh
new file mode 100755
index 0000000000..aa90865bef
--- /dev/null
+++ b/contrib/update-unicode/update_unicode.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#See http://www.unicode.org/reports/tr44/
+#
+#Me Enclosing_Mark an enclosing combining mark
+#Mn Nonspacing_Mark a nonspacing combining mark (zero advance width)
+#Cf Format a format control character
+#
+cd "$(dirname "$0")"
+UNICODEWIDTH_H=$(git rev-parse --show-toplevel)/unicode-width.h
+
+wget -N http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt \
+ http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt &&
+if ! test -d uniset; then
+ git clone https://github.com/depp/uniset.git &&
+ ( cd uniset && git checkout 4b186196dd )
+fi &&
+(
+ cd uniset &&
+ if ! test -x uniset; then
+ autoreconf -i &&
+ ./configure --enable-warnings=-Werror CFLAGS='-O0 -ggdb'
+ fi &&
+ make
+) &&
+UNICODE_DIR=. && export UNICODE_DIR &&
+cat >$UNICODEWIDTH_H <<-EOF
+static const struct interval zero_width[] = {
+ $(uniset/uniset --32 cat:Me,Mn,Cf + U+1160..U+11FF - U+00AD)
+};
+static const struct interval double_width[] = {
+ $(uniset/uniset --32 eaw:F,W)
+};
+EOF
diff --git a/contrib/vim/README b/contrib/vim/README
deleted file mode 100644
index 8f16d06972..0000000000
--- a/contrib/vim/README
+++ /dev/null
@@ -1,22 +0,0 @@
-Syntax highlighting for git commit messages, config files, etc. is
-included with the vim distribution as of vim 7.2, and should work
-automatically.
-
-If you have an older version of vim, you can get the latest syntax
-files from the vim project:
-
- http://ftp.vim.org/pub/vim/runtime/syntax/git.vim
- http://ftp.vim.org/pub/vim/runtime/syntax/gitcommit.vim
- http://ftp.vim.org/pub/vim/runtime/syntax/gitconfig.vim
- http://ftp.vim.org/pub/vim/runtime/syntax/gitrebase.vim
- http://ftp.vim.org/pub/vim/runtime/syntax/gitsendemail.vim
-
-These files are also available via FTP at the same location.
-
-To install:
-
- 1. Copy these files to vim's syntax directory $HOME/.vim/syntax
- 2. To auto-detect the editing of various git-related filetypes:
-
- $ curl http://ftp.vim.org/pub/vim/runtime/filetype.vim |
- sed -ne '/^" Git$/, /^$/ p' >>$HOME/.vim/filetype.vim
diff --git a/contrib/vscode/.gitattributes b/contrib/vscode/.gitattributes
new file mode 100644
index 0000000000..e89f2236ef
--- /dev/null
+++ b/contrib/vscode/.gitattributes
@@ -0,0 +1 @@
+init.sh whitespace=-indent-with-non-tab
diff --git a/contrib/vscode/README.md b/contrib/vscode/README.md
new file mode 100644
index 0000000000..8202d62035
--- /dev/null
+++ b/contrib/vscode/README.md
@@ -0,0 +1,14 @@
+Configuration for VS Code
+=========================
+
+[VS Code](https://code.visualstudio.com/) is a lightweight but powerful source
+code editor which runs on your desktop and is available for
+[Windows](https://code.visualstudio.com/docs/setup/windows),
+[macOS](https://code.visualstudio.com/docs/setup/mac) and
+[Linux](https://code.visualstudio.com/docs/setup/linux). Among other languages,
+it has [support for C/C++ via an extension](https://github.com/Microsoft/vscode-cpptools).
+
+To start developing Git with VS Code, simply run the Unix shell script called
+`init.sh` in this directory, which creates the configuration files in
+`.vscode/` that VS Code consumes. `init.sh` needs access to `make` and `gcc`,
+so run the script in a Git SDK shell if you are using Windows.
diff --git a/contrib/vscode/init.sh b/contrib/vscode/init.sh
new file mode 100755
index 0000000000..27de94994b
--- /dev/null
+++ b/contrib/vscode/init.sh
@@ -0,0 +1,375 @@
+#!/bin/sh
+
+die () {
+ echo "$*" >&2
+ exit 1
+}
+
+cd "$(dirname "$0")"/../.. ||
+die "Could not cd to top-level directory"
+
+mkdir -p .vscode ||
+die "Could not create .vscode/"
+
+# General settings
+
+cat >.vscode/settings.json.new <<\EOF ||
+{
+ "C_Cpp.intelliSenseEngine": "Default",
+ "C_Cpp.intelliSenseEngineFallback": "Disabled",
+ "[git-commit]": {
+ "editor.wordWrap": "wordWrapColumn",
+ "editor.wordWrapColumn": 72
+ },
+ "[c]": {
+ "editor.detectIndentation": false,
+ "editor.insertSpaces": false,
+ "editor.tabSize": 8,
+ "editor.wordWrap": "wordWrapColumn",
+ "editor.wordWrapColumn": 80,
+ "files.trimTrailingWhitespace": true
+ },
+ "files.associations": {
+ "*.h": "c",
+ "*.c": "c"
+ },
+ "cSpell.ignorePaths": [
+ ],
+ "cSpell.words": [
+ "DATAW",
+ "DBCACHED",
+ "DFCHECK",
+ "DTYPE",
+ "Hamano",
+ "HCAST",
+ "HEXSZ",
+ "HKEY",
+ "HKLM",
+ "IFGITLINK",
+ "IFINVALID",
+ "ISBROKEN",
+ "ISGITLINK",
+ "ISSYMREF",
+ "Junio",
+ "LPDWORD",
+ "LPPROC",
+ "LPWSTR",
+ "MSVCRT",
+ "NOARG",
+ "NOCOMPLETE",
+ "NOINHERIT",
+ "RENORMALIZE",
+ "STARTF",
+ "STARTUPINFOEXW",
+ "Schindelin",
+ "UCRT",
+ "YESNO",
+ "argcp",
+ "beginthreadex",
+ "committish",
+ "contentp",
+ "cpath",
+ "cpidx",
+ "ctim",
+ "dequote",
+ "envw",
+ "ewah",
+ "fdata",
+ "fherr",
+ "fhin",
+ "fhout",
+ "fragp",
+ "fsmonitor",
+ "hnsec",
+ "idents",
+ "includeif",
+ "interpr",
+ "iprog",
+ "isexe",
+ "iskeychar",
+ "kompare",
+ "mksnpath",
+ "mktag",
+ "mktree",
+ "mmblob",
+ "mmbuffer",
+ "mmfile",
+ "noenv",
+ "nparents",
+ "ntpath",
+ "ondisk",
+ "ooid",
+ "oplen",
+ "osdl",
+ "pnew",
+ "pold",
+ "ppinfo",
+ "pushf",
+ "pushv",
+ "rawsz",
+ "rebasing",
+ "reencode",
+ "repo",
+ "rerere",
+ "scld",
+ "sharedrepo",
+ "spawnv",
+ "spawnve",
+ "spawnvpe",
+ "strdup'ing",
+ "submodule",
+ "submodules",
+ "topath",
+ "topo",
+ "tpatch",
+ "unexecutable",
+ "unhide",
+ "unkc",
+ "unkv",
+ "unmark",
+ "unmatch",
+ "unsets",
+ "unshown",
+ "untracked",
+ "untrackedcache",
+ "unuse",
+ "upos",
+ "uval",
+ "vreportf",
+ "wargs",
+ "wargv",
+ "wbuffer",
+ "wcmd",
+ "wcsnicmp",
+ "wcstoutfdup",
+ "wdeltaenv",
+ "wdir",
+ "wenv",
+ "wenvblk",
+ "wenvcmp",
+ "wenviron",
+ "wenvpos",
+ "wenvsz",
+ "wfile",
+ "wfilename",
+ "wfopen",
+ "wfreopen",
+ "wfullpath",
+ "which'll",
+ "wlink",
+ "wmain",
+ "wmkdir",
+ "wmktemp",
+ "wnewpath",
+ "wotype",
+ "wpath",
+ "wpathname",
+ "wpgmptr",
+ "wpnew",
+ "wpointer",
+ "wpold",
+ "wpos",
+ "wputenv",
+ "wrmdir",
+ "wship",
+ "wtarget",
+ "wtemplate",
+ "wunlink",
+ "xcalloc",
+ "xgetcwd",
+ "xmallocz",
+ "xmemdupz",
+ "xmmap",
+ "xopts",
+ "xrealloc",
+ "xsnprintf",
+ "xutftowcs",
+ "xutftowcsn",
+ "xwcstoutf"
+ ],
+ "cSpell.ignoreRegExpList": [
+ "\\\"(DIRC|FSMN|REUC|UNTR)\\\"",
+ "\\\\u[0-9a-fA-Fx]{4}\\b",
+ "\\b(filfre|frotz|xyzzy)\\b",
+ "\\bCMIT_FMT_DEFAULT\\b",
+ "\\bde-munge\\b",
+ "\\bGET_OID_DISAMBIGUATORS\\b",
+ "\\bHASH_RENORMALIZE\\b",
+ "\\bTREESAMEness\\b",
+ "\\bUSE_STDEV\\b",
+ "\\Wchar *\\*\\W*utfs\\W",
+ "cURL's",
+ "nedmalloc'ed",
+ "ntifs\\.h",
+ ],
+}
+EOF
+die "Could not write settings.json"
+
+# Infer some setup-specific locations/names
+
+GCCPATH="$(which gcc)"
+GDBPATH="$(which gdb)"
+MAKECOMMAND="make -j5 DEVELOPER=1"
+OSNAME=
+X=
+case "$(uname -s)" in
+MINGW*)
+ GCCPATH="$(cygpath -am "$GCCPATH")"
+ GDBPATH="$(cygpath -am "$GDBPATH")"
+ MAKE_BASH="$(cygpath -am /git-cmd.exe) --command=usr\\\\bin\\\\bash.exe"
+ MAKECOMMAND="$MAKE_BASH -lc \\\"$MAKECOMMAND\\\""
+ OSNAME=Win32
+ X=.exe
+ ;;
+Linux)
+ OSNAME=Linux
+ ;;
+Darwin)
+ OSNAME=macOS
+ ;;
+esac
+
+# Default build task
+
+cat >.vscode/tasks.json.new <<EOF ||
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
+ // for the documentation about the tasks.json format
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "make",
+ "type": "shell",
+ "command": "$MAKECOMMAND",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ }
+ }
+ ]
+}
+EOF
+die "Could not install default build task"
+
+# Debugger settings
+
+cat >.vscode/launch.json.new <<EOF ||
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit:
+ // https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "(gdb) Launch",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "\${workspaceFolder}/git$X",
+ "args": [],
+ "stopAtEntry": false,
+ "cwd": "\${workspaceFolder}",
+ "environment": [],
+ "externalConsole": true,
+ "MIMode": "gdb",
+ "miDebuggerPath": "$GDBPATH",
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ]
+ }
+ ]
+}
+EOF
+die "Could not write launch configuration"
+
+# C/C++ extension settings
+
+make -f - OSNAME=$OSNAME GCCPATH="$GCCPATH" vscode-init \
+ >.vscode/c_cpp_properties.json <<\EOF ||
+include Makefile
+
+vscode-init:
+ @mkdir -p .vscode && \
+ incs= && defs= && \
+ for e in $(ALL_CFLAGS) \
+ '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
+ '-DGIT_LOCALE_PATH="$(localedir_relative_SQ)"' \
+ '-DBINDIR="$(bindir_relative_SQ)"' \
+ '-DFALLBACK_RUNTIME_PREFIX="$(prefix_SQ)"' \
+ '-DDEFAULT_GIT_TEMPLATE_DIR="$(template_dir_SQ)"' \
+ '-DETC_GITCONFIG="$(ETC_GITCONFIG_SQ)"' \
+ '-DETC_GITATTRIBUTES="$(ETC_GITATTRIBUTES_SQ)"' \
+ '-DGIT_LOCALE_PATH="$(localedir_relative_SQ)"' \
+ '-DCURL_DISABLE_TYPECHECK', \
+ '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \
+ '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
+ '-DGIT_INFO_PATH="$(infodir_relative_SQ)"'; do \
+ case "$$e" in \
+ -I.) \
+ incs="$$(printf '% 16s"$${workspaceRoot}",\n%s' \
+ "" "$$incs")" \
+ ;; \
+ -I/*) \
+ incs="$$(printf '% 16s"%s",\n%s' \
+ "" "$${e#-I}" "$$incs")" \
+ ;; \
+ -I*) \
+ incs="$$(printf '% 16s"$${workspaceRoot}/%s",\n%s' \
+ "" "$${e#-I}" "$$incs")" \
+ ;; \
+ -D*) \
+ defs="$$(printf '% 16s"%s",\n%s' \
+ "" "$$(echo "$${e#-D}" | sed 's/"/\\&/g')" \
+ "$$defs")" \
+ ;; \
+ esac; \
+ done && \
+ echo '{' && \
+ echo ' "configurations": [' && \
+ echo ' {' && \
+ echo ' "name": "$(OSNAME)",' && \
+ echo ' "intelliSenseMode": "clang-x64",' && \
+ echo ' "includePath": [' && \
+ echo "$$incs" | sort | sed '$$s/,$$//' && \
+ echo ' ],' && \
+ echo ' "defines": [' && \
+ echo "$$defs" | sort | sed '$$s/,$$//' && \
+ echo ' ],' && \
+ echo ' "browse": {' && \
+ echo ' "limitSymbolsToIncludedHeaders": true,' && \
+ echo ' "databaseFilename": "",' && \
+ echo ' "path": [' && \
+ echo ' "$${workspaceRoot}"' && \
+ echo ' ]' && \
+ echo ' },' && \
+ echo ' "cStandard": "c11",' && \
+ echo ' "cppStandard": "c++17",' && \
+ echo ' "compilerPath": "$(GCCPATH)"' && \
+ echo ' }' && \
+ echo ' ],' && \
+ echo ' "version": 4' && \
+ echo '}'
+EOF
+die "Could not write settings for the C/C++ extension"
+
+for file in .vscode/settings.json .vscode/tasks.json .vscode/launch.json
+do
+ if test -f $file
+ then
+ if git diff --no-index --quiet --exit-code $file $file.new
+ then
+ rm $file.new
+ else
+ printf "The file $file.new has these changes:\n\n"
+ git --no-pager diff --no-index $file $file.new
+ printf "\n\nMaybe \`mv $file.new $file\`?\n\n"
+ fi
+ else
+ mv $file.new $file
+ fi
+done
diff --git a/contrib/workdir/.gitattributes b/contrib/workdir/.gitattributes
new file mode 100644
index 0000000000..1f78c5d1bd
--- /dev/null
+++ b/contrib/workdir/.gitattributes
@@ -0,0 +1 @@
+/git-new-workdir eol=lf
diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir
index 75e8b25817..888c34a521 100755
--- a/contrib/workdir/git-new-workdir
+++ b/contrib/workdir/git-new-workdir
@@ -10,6 +10,10 @@ die () {
exit 128
}
+failed () {
+ die "unable to create new workdir '$new_workdir'!"
+}
+
if test $# -lt 2 || test $# -gt 3
then
usage "$0 <repository> <new_workdir> [<branch>]"
@@ -35,7 +39,7 @@ esac
# don't link to a configured bare repository
isbare=$(git --git-dir="$git_dir" config --bool --get core.bare)
-if test ztrue = z$isbare
+if test ztrue = "z$isbare"
then
die "\"$git_dir\" has core.bare set to true," \
" remove from \"$git_dir/config\" to use $0"
@@ -48,35 +52,54 @@ then
"a complete repository."
fi
-# don't recreate a workdir over an existing repository
-if test -e "$new_workdir"
+# make sure the links in the workdir have full paths to the original repo
+git_dir=$(cd "$git_dir" && pwd) || exit 1
+
+# don't recreate a workdir over an existing directory, unless it's empty
+if test -d "$new_workdir"
then
- die "destination directory '$new_workdir' already exists."
+ if test $(ls -a1 "$new_workdir/." | wc -l) -ne 2
+ then
+ die "destination directory '$new_workdir' is not empty."
+ fi
+ cleandir="$new_workdir/.git"
+else
+ cleandir="$new_workdir"
fi
-# make sure the links use full paths
-git_dir=$(cd "$git_dir"; pwd)
+mkdir -p "$new_workdir/.git" || failed
+cleandir=$(cd "$cleandir" && pwd) || failed
-# create the workdir
-mkdir -p "$new_workdir/.git" || die "unable to create \"$new_workdir\"!"
+cleanup () {
+ rm -rf "$cleandir"
+}
+siglist="0 1 2 15"
+trap cleanup $siglist
# create the links to the original repo. explicitly exclude index, HEAD and
# logs/HEAD from the list since they are purely related to the current working
# directory, and should not be shared.
for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache svn
do
+ # create a containing directory if needed
case $x in
*/*)
- mkdir -p "$(dirname "$new_workdir/.git/$x")"
+ mkdir -p "$new_workdir/.git/${x%/*}"
;;
esac
- ln -s "$git_dir/$x" "$new_workdir/.git/$x"
+
+ ln -s "$git_dir/$x" "$new_workdir/.git/$x" || failed
done
-# now setup the workdir
-cd "$new_workdir"
+# commands below this are run in the context of the new workdir
+cd "$new_workdir" || failed
+
# copy the HEAD from the original repository as a default branch
-cp "$git_dir/HEAD" .git/HEAD
-# checkout the branch (either the same as HEAD from the original repository, or
-# the one that was asked for)
+cp "$git_dir/HEAD" .git/HEAD || failed
+
+# the workdir is set up. if the checkout fails, the user can fix it.
+trap - $siglist
+
+# checkout the branch (either the same as HEAD from the original repository,
+# or the one that was asked for)
git checkout -f $branch