aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--COPYING25
-rw-r--r--Documentation/Makefile4
-rw-r--r--Documentation/RelNotes-1.6.5.8.txt28
-rw-r--r--Documentation/RelNotes-1.6.6.1.txt34
-rw-r--r--Documentation/RelNotes-1.7.0.txt54
-rw-r--r--Documentation/blame-options.txt6
-rw-r--r--Documentation/config.txt9
-rw-r--r--Documentation/git-blame.txt2
-rw-r--r--Documentation/git-clone.txt12
-rw-r--r--Documentation/git-commit.txt14
-rw-r--r--Documentation/git-fast-import.txt79
-rw-r--r--Documentation/git-http-backend.txt10
-rw-r--r--Documentation/git-ls-files.txt1
-rw-r--r--Documentation/git-merge.txt7
-rw-r--r--Documentation/git-read-tree.txt52
-rw-r--r--Documentation/git-remote-helpers.txt30
-rw-r--r--Documentation/git-reset.txt89
-rw-r--r--Documentation/git-update-index.txt29
-rw-r--r--Documentation/git.txt6
-rw-r--r--Documentation/technical/api-directory-listing.txt3
-rw-r--r--Makefile95
-rw-r--r--archive.c25
-rw-r--r--bisect.c8
-rw-r--r--builtin-apply.c2
-rw-r--r--builtin-archive.c17
-rw-r--r--builtin-checkout.c9
-rw-r--r--builtin-clean.c4
-rw-r--r--builtin-clone.c6
-rw-r--r--builtin-commit.c21
-rw-r--r--builtin-grep.c24
-rw-r--r--builtin-ls-files.c11
-rw-r--r--builtin-merge.c4
-rw-r--r--builtin-pack-objects.c31
-rw-r--r--builtin-read-tree.c4
-rw-r--r--builtin-rerere.c23
-rw-r--r--builtin-reset.c45
-rw-r--r--builtin-revert.c4
-rw-r--r--builtin-update-index.c78
-rw-r--r--cache.h14
-rw-r--r--compat/mingw.c116
-rw-r--r--compat/mingw.h12
-rw-r--r--compat/msvc.h40
-rw-r--r--compat/win32/pthread.c110
-rw-r--r--compat/win32/pthread.h67
-rw-r--r--config.c5
-rwxr-xr-xcontrib/hg-to-git/hg-to-git.py8
-rw-r--r--convert.c3
-rw-r--r--diff-lib.c5
-rw-r--r--diff.c5
-rw-r--r--dir.c100
-rw-r--r--dir.h4
-rw-r--r--editor.c21
-rw-r--r--entry.c2
-rw-r--r--environment.c1
-rw-r--r--fast-import.c327
-rwxr-xr-xgit-am.sh6
-rw-r--r--git-compat-util.h1
-rwxr-xr-xgit-rebase--interactive.sh23
-rwxr-xr-xgit-rebase.sh30
-rw-r--r--http-backend.c3
-rw-r--r--imap-send.c8
-rw-r--r--ll-merge.c6
-rw-r--r--lockfile.c5
-rw-r--r--pager.c5
-rw-r--r--parse-options.c7
-rw-r--r--parse-options.h3
-rw-r--r--read-cache.c20
-rw-r--r--remote-curl.c2
-rw-r--r--rerere.c8
-rw-r--r--rerere.h10
-rw-r--r--run-command.c120
-rw-r--r--run-command.h2
-rw-r--r--sha1_name.c42
-rw-r--r--t/lib-httpd.sh30
-rw-r--r--t/lib-httpd/apache.conf5
-rwxr-xr-xt/t0021-conversion.sh3
-rwxr-xr-xt/t1011-read-tree-sparse-checkout.sh150
-rwxr-xr-xt/t2012-checkout-last.sh27
-rwxr-xr-xt/t2104-update-index-skip-worktree.sh57
-rwxr-xr-xt/t3001-ls-files-others-exclude.sh22
-rwxr-xr-xt/t3415-rebase-onto-threedots.sh105
-rwxr-xr-xt/t4030-diff-textconv.sh2
-rwxr-xr-xt/t4031-diff-rewrite-binary.sh2
-rwxr-xr-xt/t4200-rerere.sh15
-rwxr-xr-xt/t5551-http-fetch.sh2
-rwxr-xr-xt/t5560-http-backend-noserver.sh73
-rwxr-xr-xt/t5560-http-backend.sh260
-rwxr-xr-xt/t5561-http-backend.sh149
-rwxr-xr-xt/t556x_common122
-rwxr-xr-xt/t5702-clone-options.sh3
-rwxr-xr-xt/t7002-grep.sh24
-rwxr-xr-xt/t7011-skip-worktree-reading.sh163
-rwxr-xr-xt/t7012-skip-worktree-writing.sh146
-rwxr-xr-xt/t7110-reset-merge.sh183
-rwxr-xr-xt/t7111-reset-table.sh113
-rwxr-xr-xt/t7201-co.sh57
-rwxr-xr-xt/t7300-clean.sh19
-rwxr-xr-xt/t7400-submodule-basic.sh9
-rwxr-xr-xt/t7502-commit.sh109
-rwxr-xr-xt/t9300-fast-import.sh152
-rw-r--r--test-index-version.c14
-rw-r--r--transport-helper.c283
-rw-r--r--transport.c221
-rw-r--r--transport.h24
-rw-r--r--unpack-trees.c214
-rw-r--r--unpack-trees.h6
107 files changed, 3964 insertions, 851 deletions
diff --git a/.gitignore b/.gitignore
index 5d3228966..83cf1b753 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,6 +108,10 @@
/git-relink
/git-remote
/git-remote-curl
+/git-remote-http
+/git-remote-https
+/git-remote-ftp
+/git-remote-ftps
/git-repack
/git-replace
/git-repo-config
@@ -158,6 +162,7 @@
/test-delta
/test-dump-cache-tree
/test-genrandom
+/test-index-version
/test-match-trees
/test-parse-options
/test-path-utils
diff --git a/COPYING b/COPYING
index 6ff87c466..536e55524 100644
--- a/COPYING
+++ b/COPYING
@@ -22,8 +22,8 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -36,7 +36,7 @@ software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.) You can apply it to
+the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
@@ -76,7 +76,7 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
-
+
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
@@ -131,7 +131,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
-
+
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -189,7 +189,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
-
+
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -246,7 +246,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
-
+
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -299,7 +299,7 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
-
+
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
@@ -324,10 +324,9 @@ the "copyright" line and a pointer to where the full notice is found.
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
-
+ 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.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
@@ -357,5 +356,5 @@ necessary. Here is a sample; alter the names:
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Library General
+library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 4797b2dc3..8a8a3954d 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -204,7 +204,7 @@ install-pdf: pdf
install-html: html
'$(SHELL_PATH_SQ)' ./install-webdoc.sh $(DESTDIR)$(htmldir)
-../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+../GIT-VERSION-FILE: FORCE
$(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE
-include ../GIT-VERSION-FILE
@@ -337,4 +337,4 @@ quick-install-man:
quick-install-html:
'$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir)
-.PHONY: .FORCE-GIT-VERSION-FILE
+.PHONY: FORCE
diff --git a/Documentation/RelNotes-1.6.5.8.txt b/Documentation/RelNotes-1.6.5.8.txt
new file mode 100644
index 000000000..8b24bebb9
--- /dev/null
+++ b/Documentation/RelNotes-1.6.5.8.txt
@@ -0,0 +1,28 @@
+Git v1.6.5.8 Release Notes
+==========================
+
+Fixes since v1.6.5.7
+--------------------
+
+* "git count-objects" did not handle packfiles that are bigger than 4G on
+ platforms with 32-bit off_t.
+
+* "git rebase -i" did not abort cleanly if it failed to launch the editor.
+
+* "git blame" did not work well when commit lacked the author name.
+
+* "git fast-import" choked when handling a tag that points at an object
+ that is not a commit.
+
+* "git reset --hard" did not work correctly when GIT_WORK_TREE environment
+ variable is used to point at the root of the true work tree.
+
+* "git grep" fed a buffer that is not NUL-terminated to underlying
+ regexec().
+
+* "git checkout -m other" while on a branch that does not have any commit
+ segfaulted, instead of failing.
+
+* "git branch -a other" should have diagnosed the command as an error.
+
+Other minor documentation updates are also included.
diff --git a/Documentation/RelNotes-1.6.6.1.txt b/Documentation/RelNotes-1.6.6.1.txt
index 4c88bebb9..f1d0a4ae2 100644
--- a/Documentation/RelNotes-1.6.6.1.txt
+++ b/Documentation/RelNotes-1.6.6.1.txt
@@ -4,12 +4,34 @@ Git v1.6.6.1 Release Notes
Fixes since v1.6.6
------------------
+ * "git blame" did not work well when commit lacked the author name.
+
+ * "git branch -a name" wasn't diagnosed as an error.
+
+ * "git count-objects" did not handle packfiles that are bigger than 4G on
+ platforms with 32-bit off_t.
+
+ * "git checkout -m other" while on a branch that does not have any commit
+ segfaulted, instead of failing.
+
+ * "git fast-import" choked when fed a tag that do not point at a
+ commit.
+
+ * "git grep" finding from work tree files could have fed garbage to
+ the underlying regexec(3).
+
+ * "git grep -L" didn't show empty files (they should never match, and
+ they should always appear in -L output as unmatching).
+
+ * "git rebase -i" did not abort cleanly if it failed to launch the editor.
+
+ * "git reset --hard" did not work correctly when GIT_WORK_TREE environment
+ variable is used to point at the root of the true work tree.
+
* http-backend was not listed in the command list in the documentation.
-Other minor documentation updates are included.
+ * Building on FreeBSD (both 7 and 8) needs OLD_ICONV set in the Makefile
+
+ * "git checkout -m some-branch" while on an unborn branch crashed.
---
-exec >/var/tmp/1
-O=v1.6.6-4-gd828fdb
-echo O=$(git describe maint)
-git shortlog --no-merges $O..maint
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes-1.7.0.txt b/Documentation/RelNotes-1.7.0.txt
index d66a9732c..7a49b475d 100644
--- a/Documentation/RelNotes-1.7.0.txt
+++ b/Documentation/RelNotes-1.7.0.txt
@@ -44,18 +44,70 @@ Updates since v1.6.6
(subsystems)
+ * "git fast-import" updates; adds "option" and "feature" to detect the
+ mismatch between fast-import and the frontends that produce the input
+ stream.
+
(portability)
+ * Some more MSVC portability patches for msysgit port.
+
+ * Minimum Pthreads emulation for msysgit port.
+
(performance)
+ * More performance improvement patches for msysgit port.
+
(usability, bells and whistles)
+ * More commands learned "--quiet" and "--[no-]progress" options.
+
+ * Various commands given by the end user (e.g. diff.type.textconv,
+ and GIT_EDITOR) can be specified with command line arguments. E.g. it
+ is now possible to say "[diff "utf8doc"] textconv = nkf -w".
+
+ * "sparse checkout" feature allows only part of the work tree to be
+ checked out.
+
+ * HTTP transfer can use authentication scheme other than basic
+ (i.e./e.g. digest).
+
+ * Switching from a version of superproject that used to have a submodule
+ to another version of superproject that no longer has it did not remove
+ the submodule directory when it should (namely, when you are not
+ interested in the submodule at all and didn't clone/checkout).
+
+ * "git checkout A...B" is a way to detach HEAD at the merge base between
+ A and B.
+
* "git commit --date='<date>'" can be used to override the author date
just like "git commit --author='<name> <email>'" can be used to
override the author identity.
+ * "git commit --no-status" can be used to omit the listing of the index
+ and the work tree status in the editor used to prepare the log message.
+
+ * "git fetch --all" can now be used in place of "git remote update".
+
+ * "git push" learned "git push origin --delete branch", a syntactic sugar
+ for "git push origin :branch".
+
+ * "git rebase --onto A...B" means the history is replayed on top of the
+ merge base between A and B.
+
+ * Use of "git reset --merge" has become easier when resetting away a
+ conflicted mess left in the work tree.
+
+ * "git rerere" had rerere.autoupdate configuration but there was no way
+ to countermand it from the command line; --no-rerere-autoupdate option
+ given to "merge", "revert", etc. fixes this.
+
* "git status" learned "-s(hort)" output format.
+(developers)
+
+ * The infrastructure to build foreign SCM interface has been updated.
+
Fixes since v1.6.6
------------------
@@ -65,6 +117,6 @@ release, unless otherwise noted.
--
exec >/var/tmp/1
-O=v1.6.6-101-gf012d27
+O=v1.6.6-263-ge33fd3c
echo O=$(git describe master)
git shortlog --no-merges $O..master ^maint
diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt
index 1625ffce6..4833cac4b 100644
--- a/Documentation/blame-options.txt
+++ b/Documentation/blame-options.txt
@@ -98,8 +98,10 @@ commit.
files that were modified in the same commit. This is
useful when you reorganize your program and move code
around across files. When this option is given twice,
- the command additionally looks for copies from all other
- files in the parent for the commit that creates the file.
+ the command additionally looks for copies from other
+ files in the commit that creates the file. When this
+ option is given three times, the command additionally
+ looks for copies from other files in any commit.
+
<num> is optional but it is the lower bound on the number of
alphanumeric characters that git must detect as moving
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 9f40955f8..2d6775c13 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -502,6 +502,10 @@ notes should be printed.
This setting defaults to "refs/notes/commits", and can be overridden by
the `GIT_NOTES_REF` environment variable.
+core.sparseCheckout::
+ Enable "sparse checkout" feature. See section "Sparse checkout" in
+ linkgit:git-read-tree[1] for more information.
+
add.ignore-errors::
Tells 'git-add' to continue adding files when some files cannot be
added due to indexing errors. Equivalent to the '--ignore-errors'
@@ -712,6 +716,11 @@ color.ui::
terminal. When more specific variables of color.* are set, they always
take precedence over this setting. Defaults to false.
+commit.status
+ A boolean to enable/disable inclusion of status information in the
+ commit message template when using an editor to prepare the commit
+ message. Defaults to true.
+
commit.template::
Specify a file to use as the template for new commit messages.
"{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt
index 8c7b7b083..b786471dd 100644
--- a/Documentation/git-blame.txt
+++ b/Documentation/git-blame.txt
@@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [--incremental] [-L n,m]
- [-S <revs-file>] [-M] [-C] [-C] [--since=<date>]
+ [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
[<rev> | --contents <file> | --reverse <rev>] [--] <file>
DESCRIPTION
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 7ccd742a8..f43c8b2c0 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -96,13 +96,19 @@ objects from the source repository into a pack in the cloned repository.
--quiet::
-q::
- Operate quietly. This flag is also passed to the `rsync'
+ Operate quietly. Progress is not reported to the standard
+ error stream. This flag is also passed to the `rsync'
command when given.
--verbose::
-v::
- Display the progress bar, even in case the standard output is not
- a terminal.
+ Run verbosely.
+
+--progress::
+ Progress status is reported on the standard error stream
+ by default when it is attached to a terminal, unless -q
+ is specified. This flag forces progress status even if the
+ standard error stream is not directed to a terminal.
--no-checkout::
-n::
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 5fb43f932..d3a2dec21 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -11,7 +11,8 @@ SYNOPSIS
'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run]
[(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author]
[--allow-empty] [--no-verify] [-e] [--author=<author>]
- [--date=<date>] [--cleanup=<mode>] [--] [[-i | -o ]<file>...]
+ [--date=<date>] [--cleanup=<mode>] [--status | --no-status] [--]
+ [[-i | -o ]<file>...]
DESCRIPTION
-----------
@@ -224,6 +225,17 @@ specified.
to be committed, paths with local changes that will be left
uncommitted and paths that are untracked.
+--status::
+ Include the output of linkgit:git-status[1] in the commit
+ message template when using an editor to prepare the commit
+ message. Defaults to on, but can be used to override
+ configuration variable commit.status.
+
+--no-status::
+ Do not include the output of linkgit:git-status[1] in the
+ commit message template when using an editor to prepare the
+ default commit message.
+
\--::
Do not interpret any more arguments as options.
diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
index e6d364f53..ae87f0922 100644
--- a/Documentation/git-fast-import.txt
+++ b/Documentation/git-fast-import.txt
@@ -75,6 +75,20 @@ OPTIONS
set of marks. If a mark is defined to different values,
the last file wins.
+--relative-marks::
+ After specifying --relative-marks= the paths specified
+ with --import-marks= and --export-marks= are relative
+ to an internal directory in the current repository.
+ In git-fast-import this means that the paths are relative
+ to the .git/info/fast-import directory. However, other
+ importers may use a different location.
+
+--no-relative-marks::
+ Negates a previous --relative-marks. Allows for combining
+ relative and non-relative marks by interweaving
+ --(no-)-relative-marks= with the --(import|export)-marks=
+ options.
+
--export-pack-edges=<file>::
After creating a packfile, print a line of data to
<file> listing the filename of the packfile and the last
@@ -303,6 +317,15 @@ and control the current import process. More detailed discussion
standard output. This command is optional and is not needed
to perform an import.
+`feature`::
+ Require that fast-import supports the specified feature, or
+ abort if it does not.
+
+`option`::
+ Specify any of the options listed under OPTIONS that do not
+ change stream semantic to suit the frontend's needs. This
+ command is optional and is not needed to perform an import.
+
`commit`
~~~~~~~~
Create or update a branch with a new commit, recording one logical
@@ -846,6 +869,62 @@ Placing a `progress` command immediately after a `checkpoint` will
inform the reader when the `checkpoint` has been completed and it
can safely access the refs that fast-import updated.
+`feature`
+~~~~~~~~~
+Require that fast-import supports the specified feature, or abort if
+it does not.
+
+....
+ 'feature' SP <feature> LF
+....
+
+The <feature> part of the command may be any string matching
+^[a-zA-Z][a-zA-Z-]*$ and should be understood by fast-import.
+
+Feature work identical as their option counterparts with the
+exception of the import-marks feature, see below.
+
+The following features are currently supported:
+
+* date-format
+* import-marks
+* export-marks
+* relative-marks
+* no-relative-marks
+* force
+
+The import-marks behaves differently from when it is specified as
+commandline option in that only one "feature import-marks" is allowed
+per stream. Also, any --import-marks= specified on the commandline
+will override those from the stream (if any).
+
+`option`
+~~~~~~~~
+Processes the specified option so that git fast-import behaves in a
+way that suits the frontend's needs.
+Note that options specified by the frontend are overridden by any
+options the user may specify to git fast-import itself.
+
+....
+ 'option' SP <option> LF
+....
+
+The `<option>` part of the command may contain any of the options
+listed in the OPTIONS section that do not change import semantics,
+without the leading '--' and is treated in the same way.
+
+Option commands must be the first commands on the input (not counting
+feature commands), to give an option command after any non-option
+command is an error.
+
+The following commandline options change import semantics and may therefore
+not be passed as option:
+
+* date-format
+* import-marks
+* export-marks
+* force
+
Crash Reports
-------------
If fast-import is supplied invalid input it will terminate with a
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
index 67aec067c..c8fe08a0c 100644
--- a/Documentation/git-http-backend.txt
+++ b/Documentation/git-http-backend.txt
@@ -18,6 +18,11 @@ The program supports clients fetching using both the smart HTTP protcol
and the backwards-compatible dumb HTTP protocol, as well as clients
pushing using the smart HTTP protocol.
+It verifies that the directory has the magic file
+"git-daemon-export-ok", and it will refuse to export any git directory
+that hasn't explicitly been marked for export this way (unless the
+GIT_HTTP_EXPORT_ALL environmental variable is set).
+
By default, only the `upload-pack` service is enabled, which serves
'git-fetch-pack' and 'git-ls-remote' clients, which are invoked from
'git-fetch', 'git-pull', and 'git-clone'. If the client is authenticated,
@@ -70,6 +75,7 @@ Apache 2.x::
+
----------------------------------------------------------------
SetEnv GIT_PROJECT_ROOT /var/www/git
+SetEnv GIT_HTTP_EXPORT_ALL
ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
----------------------------------------------------------------
+
@@ -157,6 +163,10 @@ by the invoking web server, including:
* QUERY_STRING
* REQUEST_METHOD
+The GIT_HTTP_EXPORT_ALL environmental variable may be passed to
+'git-http-backend' to bypass the check for the "git-daemon-export-ok"
+file in each repository before allowing export of that repository.
+
The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and
GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}',
ensuring that any reflogs created by 'git-receive-pack' contain some
diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt
index 625723e41..98f3b9e75 100644
--- a/Documentation/git-ls-files.txt
+++ b/Documentation/git-ls-files.txt
@@ -109,6 +109,7 @@ OPTIONS
Identify the file status with the following tags (followed by
a space) at the start of each line:
H:: cached
+ S:: skip-worktree
M:: unmerged
R:: removed/deleted
C:: modified/changed
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index e886c2ef5..67470311e 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git merge' [-n] [--stat] [--no-commit] [--squash] [-s <strategy>]...
- [-m <msg>] <remote>...
+ [--[no-]rerere-autoupdate] [-m <msg>] <remote>...
'git merge' <msg> HEAD <remote>...
DESCRIPTION
@@ -33,6 +33,11 @@ include::merge-options.txt[]
used to give a good default for automated 'git merge'
invocations.
+--rerere-autoupdate::
+--no-rerere-autoupdate::
+ Allow the rerere mechanism to update the index with the
+ result of auto-conflict resolution if possible.
+
<remote>...::
Other branch heads to merge into our branch. You need at
least one <remote>. Specifying more than one <remote>
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index a10ce4ba4..d6faa1414 100644
--- a/Documentation/git-read-tree.txt
+++ b/Documentation/git-read-tree.txt
@@ -10,7 +10,7 @@ SYNOPSIS
--------
'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]
[-u [--exclude-per-directory=<gitignore>] | -i]]
- [--index-output=<file>]
+ [--index-output=<file>] [--no-sparse-checkout]
<tree-ish1> [<tree-ish2> [<tree-ish3>]]
@@ -110,6 +110,10 @@ OPTIONS
directories the index file and index output file are
located in.
+--no-sparse-checkout::
+ Disable sparse checkout support even if `core.sparseCheckout`
+ is true.
+
<tree-ish#>::
The id of the tree object(s) to be read/merged.
@@ -360,6 +364,52 @@ middle of doing, and when your working tree is ready (i.e. you
have finished your work-in-progress), attempt the merge again.
+Sparse checkout
+---------------
+
+"Sparse checkout" allows to sparsely populate working directory.
+It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell
+Git whether a file on working directory is worth looking at.
+
+"git read-tree" and other merge-based commands ("git merge", "git
+checkout"...) can help maintaining skip-worktree bitmap and working
+directory update. `$GIT_DIR/info/sparse-checkout` is used to
+define the skip-worktree reference bitmap. When "git read-tree" needs
+to update working directory, it will reset skip-worktree bit in index
+based on this file, which uses the same syntax as .gitignore files.
+If an entry matches a pattern in this file, skip-worktree will be
+set on that entry. Otherwise, skip-worktree will be unset.
+
+Then it compares the new skip-worktree value with the previous one. If
+skip-worktree turns from unset to set, it will add the corresponding
+file back. If it turns from set to unset, that file will be removed.
+
+While `$GIT_DIR/info/sparse-checkout` is usually used to specify what
+files are in. You can also specify what files are _not_ in, using
+negate patterns. For example, to remove file "unwanted":
+
+----------------
+*
+!unwanted
+----------------
+
+Another tricky thing is fully repopulating working directory when you
+no longer want sparse checkout. You cannot just disable "sparse
+checkout" because skip-worktree are still in the index and you working
+directory is still sparsely populated. You should re-populate working
+directory with the `$GIT_DIR/info/sparse-checkout` file content as
+follows:
+
+----------------
+*
+----------------
+
+Then you can disable sparse checkout. Sparse checkout support in "git
+read-tree" and similar commands is disabled by default. You need to
+turn `core.sparseCheckout` on in order to have sparse checkout
+support.
+
+
SEE ALSO
--------
linkgit:git-write-tree[1]; linkgit:git-ls-files[1];
diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt
index 5cfdc0cfc..4685a898f 100644
--- a/Documentation/git-remote-helpers.txt
+++ b/Documentation/git-remote-helpers.txt
@@ -25,7 +25,10 @@ Commands are given by the caller on the helper's standard input, one per line.
'capabilities'::
Lists the capabilities of the helper, one per line, ending
- with a blank line.
+ with a blank line. Each capability may be preceeded with '*'.
+ This marks them mandatory for git version using the remote
+ helper to understand (unknown mandatory capability is fatal
+ error).
'list'::
Lists the refs, one per line, in the format "<value> <name>
@@ -90,6 +93,20 @@ Supported if the helper has the "push" capability.
+
Supported if the helper has the "import" capability.
+'connect' <service>::
+ Connects to given service. Standard input and standard output
+ of helper are connected to specified service (git prefix is
+ included in service name so e.g. fetching uses 'git-upload-pack'
+ as service) on remote side. Valid replies to this command are
+ empty line (connection established), 'fallback' (no smart
+ transport support, fall back to dumb transports) and just
+ exiting with error message printed (can't connect, don't
+ bother trying to fall back). After line feed terminating the
+ positive (empty) response, the output of service starts. After
+ the connection ends, the remote helper exits.
++
+Supported if the helper has the "connect" capability.
+
If a fatal error occurs, the program writes the error message to
stderr and exits. The caller should expect that a suitable error
message has been printed if the child closes the connection without
@@ -123,6 +140,9 @@ CAPABILITIES
all, it must cover all refs reported by the list command; if
it is not used, it is effectively "*:*"
+'connect'::
+ This helper supports the 'connect' command.
+
REF LIST ATTRIBUTES
-------------------
@@ -165,9 +185,15 @@ OPTIONS
but don't actually change any repository data. For most
helpers this only applies to the 'push', if supported.
+'option servpath <c-style-quoted-path>'::
+ Set service path (--upload-pack, --receive-pack etc.) for
+ next connect. Remote helper MAY support this option. Remote
+ helper MUST NOT rely on this option being set before
+ connect request occurs.
+
Documentation
-------------
-Documentation by Daniel Barkalow.
+Documentation by Daniel Barkalow and Ilari Liusvaara
GIT
---
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 9df6de2e7..c7aa44431 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -68,6 +68,95 @@ linkgit:git-add[1]).
<commit>::
Commit to make the current HEAD. If not given defaults to HEAD.
+DISCUSSION
+----------
+
+The tables below show what happens when running:
+
+----------
+git reset --option target
+----------
+
+to reset the HEAD to another commit (`target`) with the different
+reset options depending on the state of the files.
+
+In these tables, A, B, C and D are some different states of a
+file. For example, the first line of the first table means that if a
+file is in state A in the working tree, in state B in the index, in
+state C in HEAD and in state D in the target, then "git reset --soft
+target" will put the file in state A in the working tree, in state B
+in the index and in state D in HEAD.
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ A B C D --soft A B D
+ --mixed A D D
+ --hard D D D
+ --merge (disallowed)
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ A B C C --soft A B C
+ --mixed A C C
+ --hard C C C
+ --merge (disallowed)
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ B B C D --soft B B D
+ --mixed B D D
+ --hard D D D
+ --merge D D D
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ B B C C --soft B B C
+ --mixed B C C
+ --hard C C C
+ --merge C C C
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ B C C D --soft B C D
+ --mixed B D D
+ --hard D D D
+ --merge (disallowed)
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ B C C C --soft B C C
+ --mixed B C C
+ --hard C C C
+ --merge B C C
+
+"reset --merge" is meant to be used when resetting out of a conflicted
+merge. Any mergy operation guarantees that the work tree file that is
+involved in the merge does not have local change wrt the index before
+it starts, and that it writes the result out to the work tree. So if
+we see some difference between the index and the target and also
+between the index and the work tree, then it means that we are not
+resetting out from a state that a mergy operation left after failing
+with a conflict. That is why we disallow --merge option in this case.
+
+The following tables show what happens when there are unmerged
+entries:
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ X U A B --soft (disallowed)
+ --mixed X B B
+ --hard B B B
+ --merge B B B
+
+ working index HEAD target working index HEAD
+ ----------------------------------------------------
+ X U A A --soft (disallowed)
+ --mixed X A A
+ --hard A A A
+ --merge A A A
+
+X means any state and U means an unmerged index.
+
Examples
--------
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
index 6052484ab..8d88018ee 100644
--- a/Documentation/git-update-index.txt
+++ b/Documentation/git-update-index.txt
@@ -15,6 +15,7 @@ SYNOPSIS
[--cacheinfo <mode> <object> <file>]\*
[--chmod=(+|-)x]
[--assume-unchanged | --no-assume-unchanged]
+ [--skip-worktree | --no-skip-worktree]
[--ignore-submodules]
[--really-refresh] [--unresolve] [--again | -g]
[--info-only] [--index-info]
@@ -103,6 +104,13 @@ you will need to handle the situation manually.
Like '--refresh', but checks stat information unconditionally,
without regard to the "assume unchanged" setting.
+--skip-worktree::
+--no-skip-worktree::
+ When one of these flags is specified, the object name recorded
+ for the paths are not updated. Instead, these options
+ set and unset the "skip-worktree" bit for the paths. See
+ section "Skip-worktree bit" below for more information.
+
-g::
--again::
Runs 'git-update-index' itself on the paths whose index
@@ -308,6 +316,27 @@ M foo.c
<9> now it checks with lstat(2) and finds it has been changed.
+Skip-worktree bit
+-----------------
+
+Skip-worktree bit can be defined in one (long) sentence: When reading
+an entry, if it is marked as skip-worktree, then Git pretends its
+working directory version is up to date and read the index version
+instead.
+
+To elaborate, "reading" means checking for file existence, reading
+file attributes or file content. The working directory version may be
+present or absent. If present, its content may match against the index
+version or not. Writing is not affected by this bit, content safety
+is still first priority. Note that Git _can_ update working directory
+file, that is marked skip-worktree, if it is safe to do so (i.e.
+working directory version matches index version)
+
+Although this bit looks similar to assume-unchanged bit, its goal is
+different from assume-unchanged bit's. Skip-worktree also takes
+precedence over assume-unchanged bit when both are set.
+
+
Configuration
-------------
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 352c23019..b6df39ba3 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -43,14 +43,16 @@ unreleased) version of git, that is available from 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.6.6/git.html[documentation for release 1.6.6]
+* link:v1.6.6.1/git.html[documentation for release 1.6.6.1]
* release notes for
+ link:RelNotes-1.6.6.1.txt[1.6.6.1],
link:RelNotes-1.6.6.txt[1.6.6].
-* link:v1.6.5.7/git.html[documentation for release 1.6.5.7]
+* link:v1.6.5.8/git.html[documentation for release 1.6.5.8]
* release notes for
+ link:RelNotes-1.6.5.8.txt[1.6.5.8],
link:RelNotes-1.6.5.7.txt[1.6.5.7],
link:RelNotes-1.6.5.6.txt[1.6.5.6],
link:RelNotes-1.6.5.5.txt[1.6.5.5],
diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt
index 5bbd18f02..add6f435b 100644
--- a/Documentation/technical/api-directory-listing.txt
+++ b/Documentation/technical/api-directory-listing.txt
@@ -58,6 +58,9 @@ The result of the enumeration is left in these fields::
Calling sequence
----------------
+Note: index may be looked at for .gitignore files that are CE_SKIP_WORKTREE
+marked. If you to exclude files, make sure you have loaded index first.
+
* Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0,
sizeof(dir))`.
diff --git a/Makefile b/Makefile
index be1838953..b7f56b7ea 100644
--- a/Makefile
+++ b/Makefile
@@ -222,7 +222,7 @@ all::
# DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
# DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
-GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+GIT-VERSION-FILE: FORCE
@$(SHELL_PATH) ./GIT-VERSION-GEN
-include GIT-VERSION-FILE
@@ -468,6 +468,7 @@ LIB_H += commit.h
LIB_H += compat/bswap.h
LIB_H += compat/cygwin.h
LIB_H += compat/mingw.h
+LIB_H += compat/win32/pthread.h
LIB_H += csum-file.h
LIB_H += decorate.h
LIB_H += delta.h
@@ -985,15 +986,16 @@ ifeq ($(uname_S),Windows)
OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
NO_REGEX = YesPlease
NO_CURL = YesPlease
- NO_PTHREADS = YesPlease
+ NO_PYTHON = YesPlease
BLK_SHA1 = YesPlease
+ THREADED_DELTA_SEARCH = YesPlease
CC = compat/vcbuild/scripts/clink.pl
AR = compat/vcbuild/scripts/lib.pl
CFLAGS =
BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
- COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o
- COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -DSTRIP_EXTENSION=\".exe\"
+ COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o
+ COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib
lib =
@@ -1035,10 +1037,13 @@ ifneq (,$(findstring MINGW,$(uname_S)))
UNRELIABLE_FSTAT = UnfortunatelyYes
OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
NO_REGEX = YesPlease
+ NO_PYTHON = YesPlease
BLK_SHA1 = YesPlease
+ THREADED_DELTA_SEARCH = YesPlease
COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
- COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o
+ COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \
+ compat/win32/pthread.o
EXTLIBS += -lws2_32
X = .exe
ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
@@ -1048,10 +1053,8 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
EXTLIBS += /mingw/lib/libz.a
NO_R_TO_GCC_LINKER = YesPlease
INTERNAL_QSORT = YesPlease
- THREADED_DELTA_SEARCH = YesPlease
else
NO_CURL = YesPlease
- NO_PTHREADS = YesPlease
endif
endif
@@ -1099,6 +1102,9 @@ endif
ifdef NO_CURL
BASIC_CFLAGS += -DNO_CURL
+ REMOTE_CURL_PRIMARY =
+ REMOTE_CURL_ALIASES =
+ REMOTE_CURL_NAMES =
else
ifdef CURLDIR
# Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case.
@@ -1107,7 +1113,10 @@ else
else
CURL_LIBCURL = -lcurl
endif
- PROGRAMS += git-remote-curl$X git-http-fetch$X
+ REMOTE_CURL_PRIMARY = git-remote-http$X
+ REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X
+ REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES)
+ PROGRAMS += $(REMOTE_CURL_NAMES) git-http-fetch$X
curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
ifeq "$(curl_check)" "070908"
ifndef NO_EXPAT
@@ -1476,20 +1485,19 @@ shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell
strip: $(PROGRAMS) git$X
$(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
-git.o: git.c common-cmds.h GIT-CFLAGS
- $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
- '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
- $(ALL_CFLAGS) -o $@ -c $(filter %.c,$^)
+git.o: common-cmds.h
+git.s git.o: ALL_CFLAGS += -DGIT_VERSION='"$(GIT_VERSION)"' \
+ '-DGIT_HTML_PATH="$(htmldir_SQ)"'
git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
-builtin-help.o: builtin-help.c common-cmds.h GIT-CFLAGS
- $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
- '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
- '-DGIT_MAN_PATH="$(mandir_SQ)"' \
- '-DGIT_INFO_PATH="$(infodir_SQ)"' $<
+builtin-help.o: common-cmds.h
+builtin-help.s builtin-help.o: ALL_CFLAGS += \
+ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
+ '-DGIT_MAN_PATH="$(mandir_SQ)"' \
+ '-DGIT_INFO_PATH="$(infodir_SQ)"'
$(BUILT_INS): git$X
$(QUIET_BUILT_IN)$(RM) $@ && \
@@ -1642,30 +1650,26 @@ git.o git.spec \
%.o: %.c GIT-CFLAGS
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
-%.s: %.c GIT-CFLAGS
+%.s: %.c GIT-CFLAGS FORCE
$(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<
%.o: %.S GIT-CFLAGS
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
-exec_cmd.o: exec_cmd.c GIT-CFLAGS
- $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
- '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
- '-DBINDIR="$(bindir_relative_SQ)"' \
- '-DPREFIX="$(prefix_SQ)"' \
- $<
+exec_cmd.s exec_cmd.o: ALL_CFLAGS += \
+ '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
+ '-DBINDIR="$(bindir_relative_SQ)"' \
+ '-DPREFIX="$(prefix_SQ)"'
-builtin-init-db.o: builtin-init-db.c GIT-CFLAGS
- $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $<
+builtin-init-db.s builtin-init-db.o: ALL_CFLAGS += \
+ -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"'
-config.o: config.c GIT-CFLAGS
- $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $<
+config.s config.o: ALL_CFLAGS += -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"'
-http.o: http.c GIT-CFLAGS
- $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $<
+http.s http.o: ALL_CFLAGS += -DGIT_USER_AGENT='"git/$(GIT_VERSION)"'
ifdef NO_EXPAT
-http-walker.o: http-walker.c http.h GIT-CFLAGS
- $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $<
+http-walker.o: http.h
+http-walker.s http-walker.o: ALL_CFLAGS += -DNO_EXPAT
endif
git-%$X: %.o $(GITLIBS)
@@ -1686,7 +1690,13 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
-git-remote-curl$X: remote-curl.o http.o http-walker.o $(GITLIBS)
+$(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
+ $(QUIET_LNCP)$(RM) $@ && \
+ ln $< $@ 2>/dev/null || \
+ ln -s $< $@ 2>/dev/null || \
+ cp $< $@
+
+$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
@@ -1737,7 +1747,7 @@ cscope:
TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
$(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
-GIT-CFLAGS: .FORCE-GIT-CFLAGS
+GIT-CFLAGS: FORCE
@FLAGS='$(TRACK_CFLAGS)'; \
if test x"$$FLAGS" != x"`cat GIT-CFLAGS 2>/dev/null`" ; then \
echo 1>&2 " * new build flags or prefix"; \
@@ -1747,7 +1757,7 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
# We need to apply sq twice, once to protect from the shell
# that runs GIT-BUILD-OPTIONS, and then again to protect it
# and the first level quoting from the shell that runs "echo".
-GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
+GIT-BUILD-OPTIONS: FORCE
@echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
@echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
@echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
@@ -1759,14 +1769,12 @@ GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
ifndef NO_TCLTK
TRACK_VARS = $(subst ','\'',-DTCLTK_PATH='$(TCLTK_PATH_SQ)')
-GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
+GIT-GUI-VARS: FORCE
@VARS='$(TRACK_VARS)'; \
if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \
echo 1>&2 " * new Tcl/Tk interpreter location"; \
echo "$$VARS" >$@; \
fi
-
-.PHONY: .FORCE-GIT-GUI-VARS
endif
### Testing rules
@@ -1782,6 +1790,7 @@ TEST_PROGRAMS_NEED_X += test-parse-options
TEST_PROGRAMS_NEED_X += test-path-utils
TEST_PROGRAMS_NEED_X += test-sha1
TEST_PROGRAMS_NEED_X += test-sigchain
+TEST_PROGRAMS_NEED_X += test-index-version
TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
@@ -1876,6 +1885,7 @@ endif
ifneq (,$X)
$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';)
endif
+
bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \
execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \
{ test "$$bindir/" = "$$execdir/" || \
@@ -1889,6 +1899,12 @@ endif
ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
cp "$$execdir/git$X" "$$execdir/$$p" || exit; \
done; } && \
+ { for p in $(REMOTE_CURL_ALIASES); do \
+ $(RM) "$$execdir/$$p" && \
+ ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
+ ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
+ cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; \
+ done; } && \
./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
install-doc:
@@ -2000,8 +2016,7 @@ endif
.PHONY: all install clean strip
.PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
-.PHONY: .FORCE-GIT-VERSION-FILE TAGS tags cscope .FORCE-GIT-CFLAGS
-.PHONY: .FORCE-GIT-BUILD-OPTIONS
+.PHONY: FORCE TAGS tags cscope
### Check documentation
#
diff --git a/archive.c b/archive.c
index 55b273246..5b8850737 100644
--- a/archive.c
+++ b/archive.c
@@ -211,10 +211,33 @@ static const struct archiver *lookup_archiver(const char *name)
return NULL;
}
+static int reject_entry(const unsigned char *sha1, const char *base,
+ int baselen, const char *filename, unsigned mode,
+ int stage, void *context)
+{
+ return -1;
+}
+
+static int path_exists(struct tree *tree, const char *path)
+{
+ const char *pathspec[] = { path, NULL };
+
+ if (read_tree_recursive(tree, "", 0, 0, pathspec, reject_entry, NULL))
+ return 1;
+ return 0;
+}
+
static void parse_pathspec_arg(const char **pathspec,
struct archiver_args *ar_args)
{
- ar_args->pathspec = get_pathspec("", pathspec);
+ ar_args->pathspec = pathspec = get_pathspec("", pathspec);
+ if (pathspec) {
+ while (*pathspec) {
+ if (!path_exists(ar_args->tree, *pathspec))
+ die("path not found: %s", *pathspec);
+ pathspec++;
+ }
+ }
}
static void parse_treeish_arg(const char **argv,
diff --git a/bisect.c b/bisect.c
index 5c0339884..6dc27ee7a 100644
--- a/bisect.c
+++ b/bisect.c
@@ -956,7 +956,7 @@ int bisect_next_all(const char *prefix)
{
struct rev_info revs;
struct commit_list *tried;
- int reaches = 0, all = 0, nr;
+ int reaches = 0, all = 0, nr, steps;
const unsigned char *bisect_rev;
char bisect_rev_hex[41];
@@ -998,8 +998,10 @@ int bisect_next_all(const char *prefix)
}
nr = all - reaches - 1;
- printf("Bisecting: %d revisions left to test after this "
- "(roughly %d steps)\n", nr, estimate_bisect_steps(all));
+ steps = estimate_bisect_steps(all);
+ printf("Bisecting: %d revision%s left to test after this "
+ "(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"),
+ steps, (steps == 1 ? "" : "s"));
return bisect_checkout(bisect_rev_hex);
}
diff --git a/builtin-apply.c b/builtin-apply.c
index 36e2f9dda..541493e1b 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -2666,7 +2666,7 @@ static int verify_index_match(struct cache_entry *ce, struct stat *st)
return -1;
return 0;
}
- return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID);
+ return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
}
static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
diff --git a/builtin-archive.c b/builtin-archive.c
index 446d6bff3..3fb41364a 100644
--- a/builtin-archive.c
+++ b/builtin-archive.c
@@ -5,6 +5,7 @@
#include "cache.h"
#include "builtin.h"
#include "archive.h"
+#include "transport.h"
#include "parse-options.h"
#include "pkt-line.h"
#include "sideband.h"
@@ -25,12 +26,16 @@ static void create_output_file(const char *output_file)
static int run_remote_archiver(int argc, const char **argv,
const char *remote, const char *exec)
{
- char *url, buf[LARGE_PACKET_MAX];
+ char buf[LARGE_PACKET_MAX];
int fd[2], i, len, rv;
- struct child_process *conn;
+ struct transport *transport;
+ struct remote *_remote;
- url = xstrdup(remote);
- conn = git_connect(fd, url, exec, 0);
+ _remote = remote_get(remote);
+ if (!_remote->url[0])
+ die("git archive: Remote with no URL");
+ transport = transport_get(_remote, _remote->url[0]);
+ transport_connect(transport, "git-upload-archive", exec, fd);
for (i = 1; i < argc; i++)
packet_write(fd[1], "argument %s\n", argv[i]);
@@ -53,9 +58,7 @@ static int run_remote_archiver(int argc, const char **argv,
/* Now, start reading from fd[0] and spit it out to stdout */
rv = recv_sideband("archive", fd[0], 1);
- close(fd[0]);
- close(fd[1]);
- rv |= finish_connect(conn);
+ rv |= transport_disconnect(transport);
return !!rv;
}
diff --git a/builtin-checkout.c b/builtin-checkout.c
index 270866938..e44e238c3 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -167,7 +167,7 @@ static int checkout_merged(int pos, struct checkout *state)
fill_mm(active_cache[pos+2]->sha1, &theirs);
status = ll_merge(&result_buf, path, &ancestor,
- &ours, "ours", &theirs, "theirs", 1);
+ &ours, "ours", &theirs, "theirs", 0);
free(ancestor.ptr);
free(ours.ptr);
free(theirs.ptr);
@@ -696,7 +696,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
* case 3: git checkout <something> [<paths>]
*
* With no paths, if <something> is a commit, that is to
- * switch to the branch or detach HEAD at it.
+ * switch to the branch or detach HEAD at it. As a special case,
+ * if <something> is A...B (missing A or B means HEAD but you can
+ * omit at most one side), and if there is a unique merge base
+ * between A and B, A...B names that merge base.
*
* With no paths, if <something> is _not_ a commit, no -t nor -b
* was given, and there is a tracking branch whose name is
@@ -722,7 +725,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "-"))
arg = "@{-1}";
- if (get_sha1(arg, rev)) {
+ if (get_sha1_mb(arg, rev)) {
if (has_dash_dash) /* case (1) */
die("invalid reference: %s", arg);
if (!patch_mode &&
diff --git a/builtin-clean.c b/builtin-clean.c
index 28cdcd027..3a70fa81b 100644
--- a/builtin-clean.c
+++ b/builtin-clean.c
@@ -75,11 +75,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
+ if (read_cache() < 0)
+ die("index file corrupt");
+
if (!ignored)
setup_standard_excludes(&dir);
pathspec = get_pathspec(prefix, argv);
- read_cache();
fill_directory(&dir, pathspec);
diff --git a/builtin-clone.c b/builtin-clone.c
index 5df8b0f72..58bacbd55 100644
--- a/builtin-clone.c
+++ b/builtin-clone.c
@@ -44,10 +44,13 @@ static char *option_origin = NULL;
static char *option_branch = NULL;
static char *option_upload_pack = "git-upload-pack";
static int option_verbose;
+static int option_progress;
static struct option builtin_clone_options[] = {
OPT__QUIET(&option_quiet),
OPT__VERBOSE(&option_verbose),
+ OPT_BOOLEAN(0, "progress", &option_progress,
+ "force progress reporting"),
OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
"don't create a checkout"),
OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
@@ -526,6 +529,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_quiet)
transport->verbose = -1;
else if (option_verbose)
+ transport->verbose = 1;
+
+ if (option_progress)
transport->progress = 1;
if (option_upload_pack)
diff --git a/builtin-commit.c b/builtin-commit.c
index 073fe90ba..69241f8ed 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -68,7 +68,7 @@ static enum {
} cleanup_mode;
static char *cleanup_arg;
-static int use_editor = 1, initial_commit, in_merge;
+static int use_editor = 1, initial_commit, in_merge, include_status = 1;
static const char *only_include_assumed;
static struct strbuf message;
@@ -107,6 +107,7 @@ static struct option builtin_commit_options[] = {
OPT_FILENAME('t', "template", &template_file, "use specified template file"),
OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+ OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
/* end commit message options */
OPT_GROUP("Commit contents options"),
@@ -183,11 +184,15 @@ static int list_paths(struct string_list *list, const char *with_tree,
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
+ struct string_list_item *item;
+
if (ce->ce_flags & CE_UPDATE)
continue;
if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
continue;
- string_list_insert(ce->name, list);
+ item = string_list_insert(ce->name, list);
+ if (ce_skip_worktree(ce))
+ item->util = item; /* better a valid pointer than a fake one */
}
return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
@@ -200,6 +205,10 @@ static void add_remove_files(struct string_list *list)
struct stat st;
struct string_list_item *p = &(list->items[i]);
+ /* p->util is skip-worktree */
+ if (p->util)
+ continue;
+
if (!lstat(p->string, &st)) {
if (add_to_cache(p->string, &st, 0))
die("updating files failed");
@@ -582,7 +591,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
/* This checks if committer ident is explicitly given */
git_committer_info(0);
- if (use_editor) {
+ if (use_editor && include_status) {
char *author_ident;
const char *committer_ident;
@@ -1097,6 +1106,10 @@ static int git_commit_config(const char *k, const char *v, void *cb)
if (!strcmp(k, "commit.template"))
return git_config_pathname(&template_file, k, v);
+ if (!strcmp(k, "commit.status")) {
+ include_status = git_config_bool(k, v);
+ return 0;
+ }
return git_status_config(k, v, s);
}
@@ -1242,7 +1255,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
"new_index file. Check that disk is not full or quota is\n"
"not exceeded, and then \"git reset HEAD\" to recover.");
- rerere();
+ rerere(0);
run_hook(get_index_file(), "post-commit", NULL);
if (!quiet)
print_summary(prefix, commit_sha1);
diff --git a/builtin-grep.c b/builtin-grep.c
index a5b6719a1..cea973ba6 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -191,8 +191,6 @@ static int grep_file(struct grep_opt *opt, const char *filename)
error("'%s': %s", filename, strerror(errno));
return 0;
}
- if (!st.st_size)
- return 0; /* empty file -- no grep hit */
if (!S_ISREG(st.st_mode))
return 0;
sz = xsize_t(st.st_size);
@@ -207,6 +205,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
return 0;
}
close(i);
+ data[sz] = 0;
if (opt->relative && opt->prefix_length)
filename = quote_path_relative(filename, -1, &buf, opt->prefix);
i = grep_buffer(opt, filename, data, sz);
@@ -222,6 +221,7 @@ static int exec_grep(int argc, const char **argv)
int status;
argv[argc] = NULL;
+ trace_argv_printf(argv, "trace: grep:");
pid = fork();
if (pid < 0)
return pid;
@@ -347,6 +347,21 @@ static void grep_add_color(struct strbuf *sb, const char *escape_seq)
strbuf_setlen(sb, sb->len - 1);
}
+static int has_skip_worktree_entry(struct grep_opt *opt, const char **paths)
+{
+ int nr;
+ for (nr = 0; nr < active_nr; nr++) {
+ struct cache_entry *ce = active_cache[nr];
+ if (!S_ISREG(ce->ce_mode))
+ continue;
+ if (!pathspec_matches(paths, ce->name, opt->max_depth))
+ continue;
+ if (ce_skip_worktree(ce))
+ return 1;
+ }
+ return 0;
+}
+
static int external_grep(struct grep_opt *opt, const char **paths, int cached)
{
int i, nr, argc, hit, len, status;
@@ -355,7 +370,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
char *argptr = randarg;
struct grep_pat *p;
- if (opt->extended || (opt->relative && opt->prefix_length))
+ if (opt->extended || (opt->relative && opt->prefix_length)
+ || has_skip_worktree_entry(opt, paths))
return -1;
len = nr = 0;
push_arg("grep");
@@ -512,7 +528,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached,
* are identical, even if worktree file has been modified, so use
* cache version instead
*/
- if (cached || (ce->ce_flags & CE_VALID)) {
+ if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {
if (ce_stage(ce))
continue;
hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index c9a03e542..738215768 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -37,6 +37,7 @@ static const char *tag_removed = "";
static const char *tag_other = "";
static const char *tag_killed = "";
static const char *tag_modified = "";
+static const char *tag_skip_worktree = "";
static void show_dir_entry(const char *tag, struct dir_entry *ent)
{
@@ -178,7 +179,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
continue;
if (ce->ce_flags & CE_UPDATE)
continue;
- show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
+ show_ce_entry(ce_stage(ce) ? tag_unmerged :
+ (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce);
}
}
if (show_deleted | show_modified) {
@@ -192,6 +194,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
continue;
if (ce->ce_flags & CE_UPDATE)
continue;
+ if (ce_skip_worktree(ce))
+ continue;
err = lstat(ce->name, &st);
if (show_deleted && err)
show_ce_entry(tag_removed, ce);
@@ -481,6 +485,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
prefix_offset = strlen(prefix);
git_config(git_default_config, NULL);
+ if (read_cache() < 0)
+ die("index file corrupt");
+
argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
ls_files_usage, 0);
if (show_tag || show_valid_bit) {
@@ -490,6 +497,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
tag_modified = "C ";
tag_other = "? ";
tag_killed = "K ";
+ tag_skip_worktree = "S ";
}
if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)
require_work_tree = 1;
@@ -508,7 +516,6 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
pathspec = get_pathspec(prefix, argv);
/* be nice with submodule paths ending in a slash */
- read_cache();
if (pathspec)
strip_trailing_slash_from_submodules();
diff --git a/builtin-merge.c b/builtin-merge.c
index f1c84d759..82e2a0491 100644
--- a/builtin-merge.c
+++ b/builtin-merge.c
@@ -52,6 +52,7 @@ static struct strategy **use_strategies;
static size_t use_strategies_nr, use_strategies_alloc;
static const char *branch;
static int verbosity;
+static int allow_rerere_auto;
static struct strategy all_strategy[] = {
{ "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -170,6 +171,7 @@ static struct option builtin_merge_options[] = {
"allow fast-forward (default)"),
OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
"abort if fast-forward is not possible"),
+ OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
"merge strategy to use", option_parse_strategy),
OPT_CALLBACK('m', "message", &merge_msg, "message",
@@ -790,7 +792,7 @@ static int suggest_conflicts(void)
}
}
fclose(fp);
- rerere();
+ rerere(allow_rerere_auto);
printf("Automatic merge failed; "
"fix conflicts and then commit the result.\n");
return 1;
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 4429d53a1..890f45cf2 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -1256,15 +1256,15 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size,
#ifdef THREADED_DELTA_SEARCH
-static pthread_mutex_t read_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t read_mutex;
#define read_lock() pthread_mutex_lock(&read_mutex)
#define read_unlock() pthread_mutex_unlock(&read_mutex)
-static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t cache_mutex;
#define cache_lock() pthread_mutex_lock(&cache_mutex)
#define cache_unlock() pthread_mutex_unlock(&cache_mutex)
-static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t progress_mutex;
#define progress_lock() pthread_mutex_lock(&progress_mutex)
#define progress_unlock() pthread_mutex_unlock(&progress_mutex)
@@ -1591,7 +1591,26 @@ struct thread_params {
unsigned *processed;
};
-static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER;
+static pthread_cond_t progress_cond;
+
+/*
+ * Mutex and conditional variable can't be statically-initialized on Windows.
+ */
+static void init_threaded_search(void)
+{
+ pthread_mutex_init(&read_mutex, NULL);
+ pthread_mutex_init(&cache_mutex, NULL);
+ pthread_mutex_init(&progress_mutex, NULL);
+ pthread_cond_init(&progress_cond, NULL);
+}
+
+static void cleanup_threaded_search(void)
+{
+ pthread_cond_destroy(&progress_cond);
+ pthread_mutex_destroy(&read_mutex);
+ pthread_mutex_destroy(&cache_mutex);
+ pthread_mutex_destroy(&progress_mutex);
+}
static void *threaded_find_deltas(void *arg)
{
@@ -1630,10 +1649,13 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
struct thread_params *p;
int i, ret, active_threads = 0;
+ init_threaded_search();
+
if (!delta_search_threads) /* --threads=0 means autodetect */
delta_search_threads = online_cpus();
if (delta_search_threads <= 1) {
find_deltas(list, &list_size, window, depth, processed);
+ cleanup_threaded_search();
return;
}
if (progress > pack_to_stdout)
@@ -1748,6 +1770,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
active_threads--;
}
}
+ cleanup_threaded_search();
free(p);
}
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 2a3a32cbf..50413ca17 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -31,7 +31,7 @@ static int list_tree(unsigned char *sha1)
}
static const char * const read_tree_usage[] = {
- "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]",
+ "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]",
NULL
};
@@ -98,6 +98,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
PARSE_OPT_NONEG, exclude_per_directory_cb },
OPT_SET_INT('i', NULL, &opts.index_only,
"don't check the working tree after merging", 1),
+ OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
+ "skip applying sparse checkout filter", 1),
OPT_END()
};
diff --git a/builtin-rerere.c b/builtin-rerere.c
index 2be9ffb77..502813889 100644
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
@@ -103,15 +103,24 @@ static int diff_two(const char *file1, const char *label1,
int cmd_rerere(int argc, const char **argv, const char *prefix)
{
struct string_list merge_rr = { NULL, 0, 0, 1 };
- int i, fd;
-
+ int i, fd, flags = 0;
+
+ if (2 < argc) {
+ if (!strcmp(argv[1], "-h"))
+ usage(git_rerere_usage);
+ if (!strcmp(argv[1], "--rerere-autoupdate"))
+ flags = RERERE_AUTOUPDATE;
+ else if (!strcmp(argv[1], "--no-rerere-autoupdate"))
+ flags = RERERE_NOAUTOUPDATE;
+ if (flags) {
+ argc--;
+ argv++;
+ }
+ }
if (argc < 2)
- return rerere();
-
- if (!strcmp(argv[1], "-h"))
- usage(git_rerere_usage);
+ return rerere(flags);
- fd = setup_rerere(&merge_rr);
+ fd = setup_rerere(&merge_rr, flags);
if (fd < 0)
return 0;
diff --git a/builtin-reset.c b/builtin-reset.c
index 5b647422d..0f5022eed 100644
--- a/builtin-reset.c
+++ b/builtin-reset.c
@@ -18,6 +18,8 @@
#include "tree.h"
#include "branch.h"
#include "parse-options.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
static const char * const git_reset_usage[] = {
"git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
@@ -54,27 +56,44 @@ static inline int is_merge(void)
static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
{
- int i = 0;
- const char *args[6];
+ int nr = 1;
+ int newfd;
+ struct tree_desc desc[2];
+ struct unpack_trees_options opts;
+ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
- args[i++] = "read-tree";
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.fn = oneway_merge;
+ opts.merge = 1;
if (!quiet)
- args[i++] = "-v";
+ opts.verbose_update = 1;
switch (reset_type) {
case MERGE:
- args[i++] = "-u";
- args[i++] = "-m";
+ opts.update = 1;
break;
case HARD:
- args[i++] = "-u";
+ opts.update = 1;
/* fallthrough */
default:
- args[i++] = "--reset";
+ opts.reset = 1;
}
- args[i++] = sha1_to_hex(sha1);
- args[i] = NULL;
- return run_command_v_opt(args, RUN_GIT_CMD);
+ newfd = hold_locked_index(lock, 1);
+
+ read_cache_unmerged();
+
+ if (!fill_tree_descriptor(desc + nr - 1, sha1))
+ return error("Failed to find tree of %s.", sha1_to_hex(sha1));
+ if (unpack_trees(nr, desc, &opts))
+ return -1;
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(lock))
+ return error("Could not write new index file.");
+
+ return 0;
}
static void print_new_head_line(struct commit *commit)
@@ -288,6 +307,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
if (reset_type == HARD || reset_type == MERGE)
setup_work_tree();
+ if (reset_type == MIXED && is_bare_repository())
+ die("%s reset is not allowed in a bare repository",
+ reset_type_names[reset_type]);
+
/* 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. */
diff --git a/builtin-revert.c b/builtin-revert.c
index 151aa6a98..857ca2eef 100644
--- a/builtin-revert.c
+++ b/builtin-revert.c
@@ -38,6 +38,7 @@ static const char * const cherry_pick_usage[] = {
static int edit, no_replay, no_commit, mainline, signoff;
static enum { REVERT, CHERRY_PICK } action;
static struct commit *commit;
+static int allow_rerere_auto;
static const char *me;
@@ -57,6 +58,7 @@ static void parse_args(int argc, const char **argv)
OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"),
OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
OPT_INTEGER('m', "mainline", &mainline, "parent number"),
+ OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
OPT_END(),
};
@@ -395,7 +397,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
die ("Error wrapping up %s", defmsg);
fprintf(stderr, "Automatic %s failed.%s\n",
me, help_msg(commit->object.sha1));
- rerere();
+ rerere(allow_rerere_auto);
exit(1);
}
if (commit_lock_file(&msg_file) < 0)
diff --git a/builtin-update-index.c b/builtin-update-index.c
index a6b7f2d63..a64a75299 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -24,8 +24,9 @@ static int info_only;
static int force_remove;
static int verbose;
static int mark_valid_only;
-#define MARK_VALID 1
-#define UNMARK_VALID 2
+static int mark_skip_worktree_only;
+#define MARK_FLAG 1
+#define UNMARK_FLAG 2
__attribute__((format (printf, 1, 2)))
static void report(const char *fmt, ...)
@@ -41,19 +42,15 @@ static void report(const char *fmt, ...)
va_end(vp);
}
-static int mark_valid(const char *path)
+static int mark_ce_flags(const char *path, int flag, int mark)
{
int namelen = strlen(path);
int pos = cache_name_pos(path, namelen);
if (0 <= pos) {
- switch (mark_valid_only) {
- case MARK_VALID:
- active_cache[pos]->ce_flags |= CE_VALID;
- break;
- case UNMARK_VALID:
- active_cache[pos]->ce_flags &= ~CE_VALID;
- break;
- }
+ if (mark)
+ active_cache[pos]->ce_flags |= flag;
+ else
+ active_cache[pos]->ce_flags &= ~flag;
cache_tree_invalidate_path(active_cache_tree, path);
active_cache_changed = 1;
return 0;
@@ -176,29 +173,29 @@ static int process_directory(const char *path, int len, struct stat *st)
return error("%s: is a directory - add files inside instead", path);
}
-/*
- * Process a regular file
- */
-static int process_file(const char *path, int len, struct stat *st)
-{
- int pos = cache_name_pos(path, len);
- struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos];
-
- if (ce && S_ISGITLINK(ce->ce_mode))
- return error("%s is already a gitlink, not replacing", path);
-
- return add_one_path(ce, path, len, st);
-}
-
static int process_path(const char *path)
{
- int len;
+ int pos, len;
struct stat st;
+ struct cache_entry *ce;
len = strlen(path);
if (has_symlink_leading_path(path, len))
return error("'%s' is beyond a symbolic link", path);
+ pos = cache_name_pos(path, len);
+ ce = pos < 0 ? NULL : active_cache[pos];
+ if (ce && ce_skip_worktree(ce)) {
+ /*
+ * working directory version is assumed "good"
+ * so updating it does not make sense.
+ * On the other hand, removing it from index should work
+ */
+ if (allow_remove && remove_file_from_cache(path))
+ return error("%s: cannot remove from the index", path);
+ return 0;
+ }
+
/*
* First things first: get the stat information, to decide
* what to do about the pathname!
@@ -209,7 +206,13 @@ static int process_path(const char *path)
if (S_ISDIR(st.st_mode))
return process_directory(path, len, &st);
- return process_file(path, len, &st);
+ /*
+ * Process a regular file
+ */
+ if (ce && S_ISGITLINK(ce->ce_mode))
+ return error("%s is already a gitlink, not replacing", path);
+
+ return add_one_path(ce, path, len, &st);
}
static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
@@ -277,7 +280,12 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
goto free_return;
}
if (mark_valid_only) {
- if (mark_valid(p))
+ if (mark_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG))
+ die("Unable to mark file %s", path);
+ goto free_return;
+ }
+ if (mark_skip_worktree_only) {
+ if (mark_ce_flags(p, CE_SKIP_WORKTREE, mark_skip_worktree_only == MARK_FLAG))
die("Unable to mark file %s", path);
goto free_return;
}
@@ -389,7 +397,7 @@ static void read_index_info(int line_termination)
}
static const char update_index_usage[] =
-"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
static unsigned char head_sha1[20];
static unsigned char merge_head_sha1[20];
@@ -648,11 +656,19 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(path, "--assume-unchanged")) {
- mark_valid_only = MARK_VALID;
+ mark_valid_only = MARK_FLAG;
continue;
}
if (!strcmp(path, "--no-assume-unchanged")) {
- mark_valid_only = UNMARK_VALID;
+ mark_valid_only = UNMARK_FLAG;
+ continue;
+ }
+ if (!strcmp(path, "--no-skip-worktree")) {
+ mark_skip_worktree_only = UNMARK_FLAG;
+ continue;
+ }
+ if (!strcmp(path, "--skip-worktree")) {
+ mark_skip_worktree_only = MARK_FLAG;
continue;
}
if (!strcmp(path, "--info-only")) {
diff --git a/cache.h b/cache.h
index b4b2ba70f..caeafb229 100644
--- a/cache.h
+++ b/cache.h
@@ -177,15 +177,20 @@ struct cache_entry {
#define CE_HASHED (0x100000)
#define CE_UNHASHED (0x200000)
+#define CE_CONFLICTED (0x800000)
+
+/* Only remove in work directory, not index */
+#define CE_WT_REMOVE (0x400000)
/*
* Extended on-disk flags
*/
#define CE_INTENT_TO_ADD 0x20000000
+#define CE_SKIP_WORKTREE 0x40000000
/* CE_EXTENDED2 is for future extension */
#define CE_EXTENDED2 0x80000000
-#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD)
+#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE)
/*
* Safeguard to avoid saving wrong flags:
@@ -234,6 +239,7 @@ static inline size_t ce_namelen(const struct cache_entry *ce)
ondisk_cache_entry_size(ce_namelen(ce)))
#define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
#define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
+#define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE)
#define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
#define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
@@ -463,7 +469,9 @@ extern int index_name_is_other(const struct index_state *, const char *, int);
/* do stat comparison even if CE_VALID is true */
#define CE_MATCH_IGNORE_VALID 01
/* do not check the contents but report dirty on racily-clean entries */
-#define CE_MATCH_RACY_IS_DIRTY 02
+#define CE_MATCH_RACY_IS_DIRTY 02
+/* do stat comparison even if CE_SKIP_WORKTREE is true */
+#define CE_MATCH_IGNORE_SKIP_WORKTREE 04
extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
@@ -525,6 +533,7 @@ extern int auto_crlf;
extern int read_replace_refs;
extern int fsync_object_files;
extern int core_preload_index;
+extern int core_apply_sparse_checkout;
enum safe_crlf {
SAFE_CRLF_FALSE = 0,
@@ -708,6 +717,7 @@ extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *
extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
extern int interpret_branch_name(const char *str, struct strbuf *);
+extern int get_sha1_mb(const char *str, unsigned char *sha1);
extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
extern const char *ref_rev_parse_rules[];
diff --git a/compat/mingw.c b/compat/mingw.c
index 0d73f15fa..ab65f77ab 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -3,9 +3,7 @@
#include <conio.h>
#include "../strbuf.h"
-#include <shellapi.h>
-
-static int err_win_to_posix(DWORD winerr)
+int err_win_to_posix(DWORD winerr)
{
int error = ENOSYS;
switch(winerr) {
@@ -142,12 +140,20 @@ int mingw_open (const char *filename, int oflags, ...)
return fd;
}
-static inline time_t filetime_to_time_t(const FILETIME *ft)
+/*
+ * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC.
+ * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch.
+ */
+static inline long long filetime_to_hnsec(const FILETIME *ft)
{
long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
- winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */
- winTime /= 10000000; /* Nano to seconds resolution */
- return (time_t)winTime;
+ /* Windows to Unix Epoch conversion */
+ return winTime - 116444736000000000LL;
+}
+
+static inline time_t filetime_to_time_t(const FILETIME *ft)
+{
+ return (time_t)(filetime_to_hnsec(ft) / 10000000);
}
/* We keep the do_lstat code in a separate function to avoid recursion.
@@ -283,64 +289,37 @@ int mkstemp(char *template)
int gettimeofday(struct timeval *tv, void *tz)
{
- SYSTEMTIME st;
- struct tm tm;
- GetSystemTime(&st);
- tm.tm_year = st.wYear-1900;
- tm.tm_mon = st.wMonth-1;
- tm.tm_mday = st.wDay;
- tm.tm_hour = st.wHour;
- tm.tm_min = st.wMinute;
- tm.tm_sec = st.wSecond;
- tv->tv_sec = tm_to_time_t(&tm);
- if (tv->tv_sec < 0)
- return -1;
- tv->tv_usec = st.wMilliseconds*1000;
+ FILETIME ft;
+ long long hnsec;
+
+ GetSystemTimeAsFileTime(&ft);
+ hnsec = filetime_to_hnsec(&ft);
+ tv->tv_sec = hnsec / 10000000;
+ tv->tv_usec = (hnsec % 10000000) / 10;
return 0;
}
int pipe(int filedes[2])
{
- int fd;
- HANDLE h[2], parent;
+ HANDLE h[2];
- if (_pipe(filedes, 8192, 0) < 0)
- return -1;
-
- parent = GetCurrentProcess();
-
- if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[0]),
- parent, &h[0], 0, FALSE, DUPLICATE_SAME_ACCESS)) {
- close(filedes[0]);
- close(filedes[1]);
- return -1;
- }
- if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[1]),
- parent, &h[1], 0, FALSE, DUPLICATE_SAME_ACCESS)) {
- close(filedes[0]);
- close(filedes[1]);
- CloseHandle(h[0]);
+ /* this creates non-inheritable handles */
+ if (!CreatePipe(&h[0], &h[1], NULL, 8192)) {
+ errno = err_win_to_posix(GetLastError());
return -1;
}
- fd = _open_osfhandle((int)h[0], O_NOINHERIT);
- if (fd < 0) {
- close(filedes[0]);
- close(filedes[1]);
+ filedes[0] = _open_osfhandle((int)h[0], O_NOINHERIT);
+ if (filedes[0] < 0) {
CloseHandle(h[0]);
CloseHandle(h[1]);
return -1;
}
- close(filedes[0]);
- filedes[0] = fd;
- fd = _open_osfhandle((int)h[1], O_NOINHERIT);
- if (fd < 0) {
+ filedes[1] = _open_osfhandle((int)h[1], O_NOINHERIT);
+ if (filedes[0] < 0) {
close(filedes[0]);
- close(filedes[1]);
CloseHandle(h[1]);
return -1;
}
- close(filedes[1]);
- filedes[1] = fd;
return 0;
}
@@ -638,8 +617,8 @@ static int env_compare(const void *a, const void *b)
return strcasecmp(*ea, *eb);
}
-static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
- int prepend_cmd)
+static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
+ int prepend_cmd, int fhin, int fhout, int fherr)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
@@ -675,9 +654,9 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
- si.hStdInput = (HANDLE) _get_osfhandle(0);
- si.hStdOutput = (HANDLE) _get_osfhandle(1);
- si.hStdError = (HANDLE) _get_osfhandle(2);
+ si.hStdInput = (HANDLE) _get_osfhandle(fhin);
+ si.hStdOutput = (HANDLE) _get_osfhandle(fhout);
+ si.hStdError = (HANDLE) _get_osfhandle(fherr);
/* concatenate argv, quoting args as we go */
strbuf_init(&args, 0);
@@ -732,7 +711,14 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
return (pid_t)pi.hProcess;
}
-pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env)
+static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
+ int prepend_cmd)
+{
+ return mingw_spawnve_fd(cmd, argv, env, prepend_cmd, 0, 1, 2);
+}
+
+pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
+ int fhin, int fhout, int fherr)
{
pid_t pid;
char **path = get_path_split();
@@ -754,13 +740,15 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env)
pid = -1;
}
else {
- pid = mingw_spawnve(iprog, argv, env, 1);
+ pid = mingw_spawnve_fd(iprog, argv, env, 1,
+ fhin, fhout, fherr);
free(iprog);
}
argv[0] = argv0;
}
else
- pid = mingw_spawnve(prog, argv, env, 0);
+ pid = mingw_spawnve_fd(prog, argv, env, 0,
+ fhin, fhout, fherr);
free(prog);
}
free_path_split(path);
@@ -1338,8 +1326,22 @@ static const char *make_backslash_path(const char *path)
void mingw_open_html(const char *unixpath)
{
const char *htmlpath = make_backslash_path(unixpath);
+ typedef HINSTANCE (WINAPI *T)(HWND, const char *,
+ const char *, const char *, const char *, INT);
+ T ShellExecute;
+ HMODULE shell32;
+
+ shell32 = LoadLibrary("shell32.dll");
+ if (!shell32)
+ die("cannot load shell32.dll");
+ ShellExecute = (T)GetProcAddress(shell32, "ShellExecuteA");
+ if (!ShellExecute)
+ die("cannot run browser");
+
printf("Launching default browser to display HTML ...\n");
ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
+
+ FreeLibrary(shell32);
}
int link(const char *oldpath, const char *newpath)
diff --git a/compat/mingw.h b/compat/mingw.h
index b3d299f5b..e254fb4e0 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -209,18 +209,21 @@ int mingw_getpagesize(void);
* mingw_fstat() instead of fstat() on Windows.
*/
#define off_t off64_t
-#define stat _stati64
#define lseek _lseeki64
+#ifndef ALREADY_DECLARED_STAT_FUNCS
+#define stat _stati64
int mingw_lstat(const char *file_name, struct stat *buf);
int mingw_fstat(int fd, struct stat *buf);
#define fstat mingw_fstat
#define lstat mingw_lstat
#define _stati64(x,y) mingw_lstat(x,y)
+#endif
int mingw_utime(const char *file_name, const struct utimbuf *times);
#define utime mingw_utime
-pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env);
+pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
+ int fhin, int fhout, int fherr);
void mingw_execvp(const char *cmd, char *const *argv);
#define execvp mingw_execvp
@@ -307,3 +310,8 @@ struct mingw_dirent
#define readdir(x) mingw_readdir(x)
struct dirent *mingw_readdir(DIR *dir);
#endif // !NO_MINGW_REPLACE_READDIR
+
+/*
+ * Used by Pthread API implementation for Windows
+ */
+extern int err_win_to_posix(DWORD winerr);
diff --git a/compat/msvc.h b/compat/msvc.h
index 9c753a560..023aba023 100644
--- a/compat/msvc.h
+++ b/compat/msvc.h
@@ -21,30 +21,22 @@ static __inline int strcasecmp (const char *s1, const char *s2)
}
#undef ERROR
-#undef stat
-#undef _stati64
-#include "compat/mingw.h"
-#undef stat
-#define stat _stati64
+
+/* Use mingw_lstat() instead of lstat()/stat() and mingw_fstat() instead
+ * of fstat(). We add the declaration of these functions here, suppressing
+ * the corresponding declarations in mingw.h, so that we can use the
+ * appropriate structure type (and function) names from the msvc headers.
+ */
+#define stat _stat64
+int mingw_lstat(const char *file_name, struct stat *buf);
+int mingw_fstat(int fd, struct stat *buf);
+#define fstat mingw_fstat
+#define lstat mingw_lstat
#define _stat64(x,y) mingw_lstat(x,y)
+#define ALREADY_DECLARED_STAT_FUNCS
+
+#include "compat/mingw.h"
+
+#undef ALREADY_DECLARED_STAT_FUNCS
-/*
- Even though _stati64 is normally just defined at _stat64
- on Windows, we specify it here as a proper struct to avoid
- compiler warnings about macro redefinition due to magic in
- mingw.h. Struct taken from ReactOS (GNU GPL license).
-*/
-struct _stati64 {
- _dev_t st_dev;
- _ino_t st_ino;
- unsigned short st_mode;
- short st_nlink;
- short st_uid;
- short st_gid;
- _dev_t st_rdev;
- __int64 st_size;
- time_t st_atime;
- time_t st_mtime;
- time_t st_ctime;
-};
#endif
diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c
new file mode 100644
index 000000000..631c0a46e
--- /dev/null
+++ b/compat/win32/pthread.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2009 Andrzej K. Haczewski <ahaczewski@gmail.com>
+ *
+ * DISCLAMER: The implementation is Git-specific, it is subset of original
+ * Pthreads API, without lots of other features that Git doesn't use.
+ * Git also makes sure that the passed arguments are valid, so there's
+ * no need for double-checking.
+ */
+
+#include "../../git-compat-util.h"
+#include "pthread.h"
+
+#include <errno.h>
+#include <limits.h>
+
+static unsigned __stdcall win32_start_routine(void *arg)
+{
+ pthread_t *thread = arg;
+ thread->arg = thread->start_routine(thread->arg);
+ return 0;
+}
+
+int pthread_create(pthread_t *thread, const void *unused,
+ void *(*start_routine)(void*), void *arg)
+{
+ thread->arg = arg;
+ thread->start_routine = start_routine;
+ thread->handle = (HANDLE)
+ _beginthreadex(NULL, 0, win32_start_routine, thread, 0, NULL);
+
+ if (!thread->handle)
+ return errno;
+ else
+ return 0;
+}
+
+int win32_pthread_join(pthread_t *thread, void **value_ptr)
+{
+ DWORD result = WaitForSingleObject(thread->handle, INFINITE);
+ switch (result) {
+ case WAIT_OBJECT_0:
+ if (value_ptr)
+ *value_ptr = thread->arg;
+ return 0;
+ case WAIT_ABANDONED:
+ return EINVAL;
+ default:
+ return err_win_to_posix(GetLastError());
+ }
+}
+
+int pthread_cond_init(pthread_cond_t *cond, const void *unused)
+{
+ cond->waiters = 0;
+
+ cond->sema = CreateSemaphore(NULL, 0, LONG_MAX, NULL);
+ if (!cond->sema)
+ die("CreateSemaphore() failed");
+ return 0;
+}
+
+int pthread_cond_destroy(pthread_cond_t *cond)
+{
+ CloseHandle(cond->sema);
+ cond->sema = NULL;
+
+ return 0;
+}
+
+int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex)
+{
+ InterlockedIncrement(&cond->waiters);
+
+ /*
+ * Unlock external mutex and wait for signal.
+ * NOTE: we've held mutex locked long enough to increment
+ * waiters count above, so there's no problem with
+ * leaving mutex unlocked before we wait on semaphore.
+ */
+ LeaveCriticalSection(mutex);
+
+ /* let's wait - ignore return value */
+ WaitForSingleObject(cond->sema, INFINITE);
+
+ /* we're done waiting, so make sure we decrease waiters count */
+ InterlockedDecrement(&cond->waiters);
+
+ /* lock external mutex again */
+ EnterCriticalSection(mutex);
+
+ return 0;
+}
+
+int pthread_cond_signal(pthread_cond_t *cond)
+{
+ /*
+ * Access to waiters count is atomic; see "Interlocked Variable Access"
+ * http://msdn.microsoft.com/en-us/library/ms684122(VS.85).aspx
+ */
+ int have_waiters = cond->waiters > 0;
+
+ /*
+ * Signal only when there are waiters
+ */
+ if (have_waiters)
+ return ReleaseSemaphore(cond->sema, 1, NULL) ?
+ 0 : err_win_to_posix(GetLastError());
+ else
+ return 0;
+}
diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h
new file mode 100644
index 000000000..b8e1bcb04
--- /dev/null
+++ b/compat/win32/pthread.h
@@ -0,0 +1,67 @@
+/*
+ * Header used to adapt pthread-based POSIX code to Windows API threads.
+ *
+ * Copyright (C) 2009 Andrzej K. Haczewski <ahaczewski@gmail.com>
+ */
+
+#ifndef PTHREAD_H
+#define PTHREAD_H
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <windows.h>
+
+/*
+ * Defines that adapt Windows API threads to pthreads API
+ */
+#define pthread_mutex_t CRITICAL_SECTION
+
+#define pthread_mutex_init(a,b) InitializeCriticalSection((a))
+#define pthread_mutex_destroy(a) DeleteCriticalSection((a))
+#define pthread_mutex_lock EnterCriticalSection
+#define pthread_mutex_unlock LeaveCriticalSection
+
+/*
+ * Implement simple condition variable for Windows threads, based on ACE
+ * implementation.
+ *
+ * See original implementation: http://bit.ly/1vkDjo
+ * ACE homepage: http://www.cse.wustl.edu/~schmidt/ACE.html
+ * See also: http://www.cse.wustl.edu/~schmidt/win32-cv-1.html
+ */
+typedef struct {
+ volatile LONG waiters;
+ HANDLE sema;
+} pthread_cond_t;
+
+extern int pthread_cond_init(pthread_cond_t *cond, const void *unused);
+
+extern int pthread_cond_destroy(pthread_cond_t *cond);
+
+extern int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex);
+
+extern int pthread_cond_signal(pthread_cond_t *cond);
+
+/*
+ * Simple thread creation implementation using pthread API
+ */
+typedef struct {
+ HANDLE handle;
+ void *(*start_routine)(void*);
+ void *arg;
+} pthread_t;
+
+extern int pthread_create(pthread_t *thread, const void *unused,
+ void *(*start_routine)(void*), void *arg);
+
+/*
+ * To avoid the need of copying a struct, we use small macro wrapper to pass
+ * pointer to win32_pthread_join instead.
+ */
+#define pthread_join(a, b) win32_pthread_join(&(a), (b))
+
+extern int win32_pthread_join(pthread_t *thread, void **value_ptr);
+
+#endif /* PTHREAD_H */
diff --git a/config.c b/config.c
index 37385ce9d..f4f2c59a3 100644
--- a/config.c
+++ b/config.c
@@ -518,6 +518,11 @@ static int git_default_core_config(const char *var, const char *value)
return 0;
}
+ if (!strcmp(var, "core.sparsecheckout")) {
+ core_apply_sparse_checkout = git_config_bool(var, value);
+ return 0;
+ }
+
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py
index 2a6839d81..854cd94ba 100755
--- a/contrib/hg-to-git/hg-to-git.py
+++ b/contrib/hg-to-git/hg-to-git.py
@@ -59,14 +59,14 @@ def getgitenv(user, date):
elems = re.compile('(.*?)\s+<(.*)>').match(user)
if elems:
env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1)
- env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1)
+ env += 'export GIT_COMMITTER_NAME="%s" ;' % elems.group(1)
env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2)
- env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2)
+ env += 'export GIT_COMMITTER_EMAIL="%s" ;' % elems.group(2)
else:
env += 'export GIT_AUTHOR_NAME="%s" ;' % user
- env += 'export GIT_COMMITER_NAME="%s" ;' % user
+ env += 'export GIT_COMMITTER_NAME="%s" ;' % user
env += 'export GIT_AUTHOR_EMAIL= ;'
- env += 'export GIT_COMMITER_EMAIL= ;'
+ env += 'export GIT_COMMITTER_EMAIL= ;'
env += 'export GIT_AUTHOR_DATE="%s" ;' % date
env += 'export GIT_COMMITTER_DATE="%s" ;' % date
diff --git a/convert.c b/convert.c
index 491e7141b..950b1f984 100644
--- a/convert.c
+++ b/convert.c
@@ -249,10 +249,11 @@ static int filter_buffer(int fd, void *data)
struct child_process child_process;
struct filter_params *params = (struct filter_params *)data;
int write_err, status;
- const char *argv[] = { "sh", "-c", params->cmd, NULL };
+ const char *argv[] = { params->cmd, NULL };
memset(&child_process, 0, sizeof(child_process));
child_process.argv = argv;
+ child_process.use_shell = 1;
child_process.in = -1;
child_process.out = fd;
diff --git a/diff-lib.c b/diff-lib.c
index 349f612b6..1c7e652a8 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -159,7 +159,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
continue;
}
- if (ce_uptodate(ce))
+ if (ce_uptodate(ce) || ce_skip_worktree(ce))
continue;
/* If CE_VALID is set, don't look at workdir for file removal */
@@ -323,7 +323,8 @@ static void do_oneway_diff(struct unpack_trees_options *o,
int match_missing, cached;
/* if the entry is not checked out, don't examine work tree */
- cached = o->index_only || (idx && (idx->ce_flags & CE_VALID));
+ cached = o->index_only ||
+ (idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx)));
/*
* Backward compatibility wart - "diff-index -m" does
* not mean "do not ignore merges", but "match_missing".
diff --git a/diff.c b/diff.c
index aad4b3977..5d7131458 100644
--- a/diff.c
+++ b/diff.c
@@ -1997,7 +1997,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
* If ce is marked as "assume unchanged", there is no
* guarantee that work tree matches what we are looking for.
*/
- if (ce->ce_flags & CE_VALID)
+ if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))
return 0;
/*
@@ -2294,7 +2294,7 @@ static void run_external_diff(const char *pgm,
}
*arg = NULL;
fflush(NULL);
- retval = run_command_v_opt(spawn_arg, 0);
+ retval = run_command_v_opt(spawn_arg, RUN_USING_SHELL);
remove_tempfile();
if (retval) {
fprintf(stderr, "external diff died, stopping at %s.\n", name);
@@ -3818,6 +3818,7 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
*arg = NULL;
memset(&child, 0, sizeof(child));
+ child.use_shell = 1;
child.argv = argv;
child.out = -1;
if (start_command(&child) != 0 ||
diff --git a/dir.c b/dir.c
index d0999ba05..3a8d3e67a 100644
--- a/dir.c
+++ b/dir.c
@@ -200,11 +200,35 @@ void add_exclude(const char *string, const char *base,
which->excludes[which->nr++] = x;
}
-static int add_excludes_from_file_1(const char *fname,
- const char *base,
- int baselen,
- char **buf_p,
- struct exclude_list *which)
+static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
+{
+ int pos, len;
+ unsigned long sz;
+ enum object_type type;
+ void *data;
+ struct index_state *istate = &the_index;
+
+ len = strlen(path);
+ pos = index_name_pos(istate, path, len);
+ if (pos < 0)
+ return NULL;
+ if (!ce_skip_worktree(istate->cache[pos]))
+ return NULL;
+ data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
+ if (!data || type != OBJ_BLOB) {
+ free(data);
+ return NULL;
+ }
+ *size = xsize_t(sz);
+ return data;
+}
+
+int add_excludes_from_file_to_list(const char *fname,
+ const char *base,
+ int baselen,
+ char **buf_p,
+ struct exclude_list *which,
+ int check_index)
{
struct stat st;
int fd, i;
@@ -212,27 +236,32 @@ static int add_excludes_from_file_1(const char *fname,
char *buf, *entry;
fd = open(fname, O_RDONLY);
- if (fd < 0 || fstat(fd, &st) < 0)
- goto err;
- size = xsize_t(st.st_size);
- if (size == 0) {
- close(fd);
- return 0;
+ if (fd < 0 || fstat(fd, &st) < 0) {
+ if (0 <= fd)
+ close(fd);
+ if (!check_index ||
+ (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)
+ return -1;
}
- buf = xmalloc(size+1);
- if (read_in_full(fd, buf, size) != size)
- {
- free(buf);
- goto err;
+ else {
+ size = xsize_t(st.st_size);
+ if (size == 0) {
+ close(fd);
+ return 0;
+ }
+ buf = xmalloc(size);
+ if (read_in_full(fd, buf, size) != size) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
}
- close(fd);
if (buf_p)
*buf_p = buf;
- buf[size++] = '\n';
entry = buf;
- for (i = 0; i < size; i++) {
- if (buf[i] == '\n') {
+ for (i = 0; i <= size; i++) {
+ if (i == size || buf[i] == '\n') {
if (entry != buf + i && entry[0] != '#') {
buf[i - (i && buf[i-1] == '\r')] = 0;
add_exclude(entry, base, baselen, which);
@@ -241,17 +270,12 @@ static int add_excludes_from_file_1(const char *fname,
}
}
return 0;
-
- err:
- if (0 <= fd)
- close(fd);
- return -1;
}
void add_excludes_from_file(struct dir_struct *dir, const char *fname)
{
- if (add_excludes_from_file_1(fname, "", 0, NULL,
- &dir->exclude_list[EXC_FILE]) < 0)
+ if (add_excludes_from_file_to_list(fname, "", 0, NULL,
+ &dir->exclude_list[EXC_FILE], 0) < 0)
die("cannot use %s as an exclude file", fname);
}
@@ -300,9 +324,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
memcpy(dir->basebuf + current, base + current,
stk->baselen - current);
strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
- add_excludes_from_file_1(dir->basebuf,
- dir->basebuf, stk->baselen,
- &stk->filebuf, el);
+ add_excludes_from_file_to_list(dir->basebuf,
+ dir->basebuf, stk->baselen,
+ &stk->filebuf, el, 1);
dir->exclude_stack = stk;
current = stk->baselen;
}
@@ -312,9 +336,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
/* Scan the list and let the last match determine the fate.
* Return 1 for exclude, 0 for include and -1 for undecided.
*/
-static int excluded_1(const char *pathname,
- int pathlen, const char *basename, int *dtype,
- struct exclude_list *el)
+int excluded_from_list(const char *pathname,
+ int pathlen, const char *basename, int *dtype,
+ struct exclude_list *el)
{
int i;
@@ -325,6 +349,12 @@ static int excluded_1(const char *pathname,
int to_exclude = x->to_exclude;
if (x->flags & EXC_FLAG_MUSTBEDIR) {
+ if (!dtype) {
+ if (!prefixcmp(pathname, exclude))
+ return to_exclude;
+ else
+ continue;
+ }
if (*dtype == DT_UNKNOWN)
*dtype = get_dtype(NULL, pathname, pathlen);
if (*dtype != DT_DIR)
@@ -382,8 +412,8 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
prep_exclude(dir, pathname, basename-pathname);
for (st = EXC_CMDL; st <= EXC_FILE; st++) {
- switch (excluded_1(pathname, pathlen, basename,
- dtype_p, &dir->exclude_list[st])) {
+ switch (excluded_from_list(pathname, pathlen, basename,
+ dtype_p, &dir->exclude_list[st])) {
case 0:
return 0;
case 1:
diff --git a/dir.h b/dir.h
index 320b6a2f3..3bead5f0e 100644
--- a/dir.h
+++ b/dir.h
@@ -69,7 +69,11 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen,
extern int fill_directory(struct dir_struct *dir, const char **pathspec);
extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
+extern int excluded_from_list(const char *pathname, int pathlen, const char *basename,
+ int *dtype, struct exclude_list *el);
extern int excluded(struct dir_struct *, const char *, int *);
+extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
+ char **buf_p, struct exclude_list *which, int check_index);
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
extern void add_exclude(const char *string, const char *base,
int baselen, struct exclude_list *which);
diff --git a/editor.c b/editor.c
index 615f5754d..d8340031d 100644
--- a/editor.c
+++ b/editor.c
@@ -36,26 +36,9 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
return error("Terminal is dumb, but EDITOR unset");
if (strcmp(editor, ":")) {
- size_t len = strlen(editor);
- int i = 0;
- int failed;
- const char *args[6];
- struct strbuf arg0 = STRBUF_INIT;
+ const char *args[] = { editor, path, NULL };
- if (strcspn(editor, "|&;<>()$`\\\"' \t\n*?[#~=%") != len) {
- /* there are specials */
- strbuf_addf(&arg0, "%s \"$@\"", editor);
- args[i++] = "sh";
- args[i++] = "-c";
- args[i++] = arg0.buf;
- }
- args[i++] = editor;
- args[i++] = path;
- args[i] = NULL;
-
- failed = run_command_v_opt_cd_env(args, 0, NULL, env);
- strbuf_release(&arg0);
- if (failed)
+ if (run_command_v_opt_cd_env(args, RUN_USING_SHELL, NULL, env))
return error("There was a problem with the editor '%s'.",
editor);
}
diff --git a/entry.c b/entry.c
index 55b988e22..004182c99 100644
--- a/entry.c
+++ b/entry.c
@@ -206,7 +206,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
len += ce_namelen(ce);
if (!check_path(path, len, &st, state->base_dir_len)) {
- unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
+ unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
if (!changed)
return 0;
if (!state->force) {
diff --git a/environment.c b/environment.c
index 5171d9f9a..739ec2704 100644
--- a/environment.c
+++ b/environment.c
@@ -51,6 +51,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING;
enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
char *notes_ref_name;
int grafts_replace_parents = 1;
+int core_apply_sparse_checkout;
/* Parallel index stat data preload? */
int core_preload_index = 0;
diff --git a/fast-import.c b/fast-import.c
index cd8704987..25c358838 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -295,6 +295,9 @@ static unsigned long branch_count;
static unsigned long branch_load_count;
static int failure;
static FILE *pack_edges;
+static unsigned int show_stats = 1;
+static int global_argc;
+static const char **global_argv;
/* Memory pools */
static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool);
@@ -317,7 +320,10 @@ static unsigned int object_entry_alloc = 5000;
static struct object_entry_pool *blocks;
static struct object_entry *object_table[1 << 16];
static struct mark_set *marks;
-static const char *mark_file;
+static const char *export_marks_file;
+static const char *import_marks_file;
+static int import_marks_file_from_stream;
+static int relative_marks_paths;
/* Our last blob */
static struct last_object last_blob = { STRBUF_INIT, 0, 0, 0 };
@@ -351,6 +357,9 @@ static struct recent_command *rc_free;
static unsigned int cmd_save = 100;
static uintmax_t next_mark;
static struct strbuf new_data = STRBUF_INIT;
+static int seen_data_command;
+
+static void parse_argv(void);
static void write_branch_report(FILE *rpt, struct branch *b)
{
@@ -454,8 +463,8 @@ static void write_crash_report(const char *err)
fputc('\n', rpt);
fputs("Marks\n", rpt);
fputs("-----\n", rpt);
- if (mark_file)
- fprintf(rpt, " exported to %s\n", mark_file);
+ if (export_marks_file)
+ fprintf(rpt, " exported to %s\n", export_marks_file);
else
dump_marks_helper(rpt, 0, marks);
@@ -1602,13 +1611,13 @@ static void dump_marks(void)
int mark_fd;
FILE *f;
- if (!mark_file)
+ if (!export_marks_file)
return;
- mark_fd = hold_lock_file_for_update(&mark_lock, mark_file, 0);
+ mark_fd = hold_lock_file_for_update(&mark_lock, export_marks_file, 0);
if (mark_fd < 0) {
failure |= error("Unable to write marks file %s: %s",
- mark_file, strerror(errno));
+ export_marks_file, strerror(errno));
return;
}
@@ -1617,7 +1626,7 @@ static void dump_marks(void)
int saved_errno = errno;
rollback_lock_file(&mark_lock);
failure |= error("Unable to write marks file %s: %s",
- mark_file, strerror(saved_errno));
+ export_marks_file, strerror(saved_errno));
return;
}
@@ -1633,7 +1642,7 @@ static void dump_marks(void)
int saved_errno = errno;
rollback_lock_file(&mark_lock);
failure |= error("Unable to write marks file %s: %s",
- mark_file, strerror(saved_errno));
+ export_marks_file, strerror(saved_errno));
return;
}
@@ -1641,11 +1650,47 @@ static void dump_marks(void)
int saved_errno = errno;
rollback_lock_file(&mark_lock);
failure |= error("Unable to commit marks file %s: %s",
- mark_file, strerror(saved_errno));
+ export_marks_file, strerror(saved_errno));
return;
}
}
+static void read_marks(void)
+{
+ char line[512];
+ FILE *f = fopen(import_marks_file, "r");
+ if (!f)
+ die_errno("cannot read '%s'", import_marks_file);
+ while (fgets(line, sizeof(line), f)) {
+ uintmax_t mark;
+ char *end;
+ unsigned char sha1[20];
+ struct object_entry *e;
+
+ end = strchr(line, '\n');
+ if (line[0] != ':' || !end)
+ die("corrupt mark line: %s", line);
+ *end = 0;
+ mark = strtoumax(line + 1, &end, 10);
+ if (!mark || end == line + 1
+ || *end != ' ' || get_sha1(end + 1, sha1))
+ die("corrupt mark line: %s", line);
+ e = find_object(sha1);
+ if (!e) {
+ enum object_type type = sha1_object_info(sha1, NULL);
+ if (type < 0)
+ die("object not found: %s", sha1_to_hex(sha1));
+ e = insert_object(sha1);
+ e->type = type;
+ e->pack_id = MAX_PACK_ID;
+ e->offset = 1; /* just not zero! */
+ }
+ insert_mark(mark, e);
+ }
+ fclose(f);
+}
+
+
static int read_next_command(void)
{
static int stdin_eof = 0;
@@ -1666,6 +1711,12 @@ static int read_next_command(void)
if (stdin_eof)
return EOF;
+ if (!seen_data_command
+ && prefixcmp(command_buf.buf, "feature ")
+ && prefixcmp(command_buf.buf, "option ")) {
+ parse_argv();
+ }
+
rc = rc_free;
if (rc)
rc_free = rc->next;
@@ -2305,6 +2356,7 @@ static void parse_new_tag(void)
struct tag *t;
uintmax_t from_mark = 0;
unsigned char sha1[20];
+ enum object_type type;
/* Obtain the new tag name from the rest of our command */
sp = strchr(command_buf.buf, ' ') + 1;
@@ -2325,19 +2377,18 @@ static void parse_new_tag(void)
s = lookup_branch(from);
if (s) {
hashcpy(sha1, s->sha1);
+ type = OBJ_COMMIT;
} else if (*from == ':') {
struct object_entry *oe;
from_mark = strtoumax(from + 1, NULL, 10);
oe = find_mark(from_mark);
- if (oe->type != OBJ_COMMIT)
- die("Mark :%" PRIuMAX " not a commit", from_mark);
+ type = oe->type;
hashcpy(sha1, oe->sha1);
} else if (!get_sha1(from, sha1)) {
unsigned long size;
char *buf;
- buf = read_object_with_reference(sha1,
- commit_type, &size, sha1);
+ buf = read_sha1_file(sha1, &type, &size);
if (!buf || size < 46)
die("Not a valid commit: %s", from);
free(buf);
@@ -2362,7 +2413,7 @@ static void parse_new_tag(void)
"object %s\n"
"type %s\n"
"tag %s\n",
- sha1_to_hex(sha1), commit_type, t->name);
+ sha1_to_hex(sha1), typename(type), t->name);
if (tagger)
strbuf_addf(&new_data,
"tagger %s\n", tagger);
@@ -2420,39 +2471,140 @@ static void parse_progress(void)
skip_optional_lf();
}
-static void import_marks(const char *input_file)
+static char* make_fast_import_path(const char *path)
{
- char line[512];
- FILE *f = fopen(input_file, "r");
- if (!f)
- die_errno("cannot read '%s'", input_file);
- while (fgets(line, sizeof(line), f)) {
- uintmax_t mark;
- char *end;
- unsigned char sha1[20];
- struct object_entry *e;
+ struct strbuf abs_path = STRBUF_INIT;
- end = strchr(line, '\n');
- if (line[0] != ':' || !end)
- die("corrupt mark line: %s", line);
- *end = 0;
- mark = strtoumax(line + 1, &end, 10);
- if (!mark || end == line + 1
- || *end != ' ' || get_sha1(end + 1, sha1))
- die("corrupt mark line: %s", line);
- e = find_object(sha1);
- if (!e) {
- enum object_type type = sha1_object_info(sha1, NULL);
- if (type < 0)
- die("object not found: %s", sha1_to_hex(sha1));
- e = insert_object(sha1);
- e->type = type;
- e->pack_id = MAX_PACK_ID;
- e->offset = 1; /* just not zero! */
- }
- insert_mark(mark, e);
+ if (!relative_marks_paths || is_absolute_path(path))
+ return xstrdup(path);
+ strbuf_addf(&abs_path, "%s/info/fast-import/%s", get_git_dir(), path);
+ return strbuf_detach(&abs_path, NULL);
+}
+
+static void option_import_marks(const char *marks, int from_stream)
+{
+ if (import_marks_file) {
+ if (from_stream)
+ die("Only one import-marks command allowed per stream");
+
+ /* read previous mark file */
+ if(!import_marks_file_from_stream)
+ read_marks();
}
- fclose(f);
+
+ import_marks_file = make_fast_import_path(marks);
+ import_marks_file_from_stream = from_stream;
+}
+
+static void option_date_format(const char *fmt)
+{
+ if (!strcmp(fmt, "raw"))
+ whenspec = WHENSPEC_RAW;
+ else if (!strcmp(fmt, "rfc2822"))
+ whenspec = WHENSPEC_RFC2822;
+ else if (!strcmp(fmt, "now"))
+ whenspec = WHENSPEC_NOW;
+ else
+ die("unknown --date-format argument %s", fmt);
+}
+
+static void option_max_pack_size(const char *packsize)
+{
+ max_packsize = strtoumax(packsize, NULL, 0) * 1024 * 1024;
+}
+
+static void option_depth(const char *depth)
+{
+ max_depth = strtoul(depth, NULL, 0);
+ if (max_depth > MAX_DEPTH)
+ die("--depth cannot exceed %u", MAX_DEPTH);
+}
+
+static void option_active_branches(const char *branches)
+{
+ max_active_branches = strtoul(branches, NULL, 0);
+}
+
+static void option_export_marks(const char *marks)
+{
+ export_marks_file = make_fast_import_path(marks);
+}
+
+static void option_export_pack_edges(const char *edges)
+{
+ if (pack_edges)
+ fclose(pack_edges);
+ pack_edges = fopen(edges, "a");
+ if (!pack_edges)
+ die_errno("Cannot open '%s'", edges);
+}
+
+static int parse_one_option(const char *option)
+{
+ if (!prefixcmp(option, "max-pack-size=")) {
+ option_max_pack_size(option + 14);
+ } else if (!prefixcmp(option, "depth=")) {
+ option_depth(option + 6);
+ } else if (!prefixcmp(option, "active-branches=")) {
+ option_active_branches(option + 16);
+ } else if (!prefixcmp(option, "export-pack-edges=")) {
+ option_export_pack_edges(option + 18);
+ } else if (!prefixcmp(option, "quiet")) {
+ show_stats = 0;
+ } else if (!prefixcmp(option, "stats")) {
+ show_stats = 1;
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int parse_one_feature(const char *feature, int from_stream)
+{
+ if (!prefixcmp(feature, "date-format=")) {
+ option_date_format(feature + 12);
+ } else if (!prefixcmp(feature, "import-marks=")) {
+ option_import_marks(feature + 13, from_stream);
+ } else if (!prefixcmp(feature, "export-marks=")) {
+ option_export_marks(feature + 13);
+ } else if (!prefixcmp(feature, "relative-marks")) {
+ relative_marks_paths = 1;
+ } else if (!prefixcmp(feature, "no-relative-marks")) {
+ relative_marks_paths = 0;
+ } else if (!prefixcmp(feature, "force")) {
+ force_update = 1;
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+
+static void parse_feature(void)
+{
+ char *feature = command_buf.buf + 8;
+
+ if (seen_data_command)
+ die("Got feature command '%s' after data command", feature);
+
+ if (parse_one_feature(feature, 1))
+ return;
+
+ die("This version of fast-import does not support feature %s.", feature);
+}
+
+static void parse_option(void)
+{
+ char *option = command_buf.buf + 11;
+
+ if (seen_data_command)
+ die("Got option command '%s' after data command", option);
+
+ if (parse_one_option(option))
+ return;
+
+ die("This version of fast-import does not support option: %s", option);
}
static int git_pack_config(const char *k, const char *v, void *cb)
@@ -2479,9 +2631,35 @@ static int git_pack_config(const char *k, const char *v, void *cb)
static const char fast_import_usage[] =
"git fast-import [--date-format=f] [--max-pack-size=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";
+static void parse_argv(void)
+{
+ unsigned int i;
+
+ for (i = 1; i < global_argc; i++) {
+ const char *a = global_argv[i];
+
+ if (*a != '-' || !strcmp(a, "--"))
+ break;
+
+ if (parse_one_option(a + 2))
+ continue;
+
+ if (parse_one_feature(a + 2, 0))
+ continue;
+
+ die("unknown option %s", a);
+ }
+ if (i != global_argc)
+ usage(fast_import_usage);
+
+ seen_data_command = 1;
+ if (import_marks_file)
+ read_marks();
+}
+
int main(int argc, const char **argv)
{
- unsigned int i, show_stats = 1;
+ unsigned int i;
git_extract_argv0_path(argv[0]);
@@ -2500,52 +2678,8 @@ int main(int argc, const char **argv)
avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
marks = pool_calloc(1, sizeof(struct mark_set));
- for (i = 1; i < argc; i++) {
- const char *a = argv[i];
-
- if (*a != '-' || !strcmp(a, "--"))
- break;
- else if (!prefixcmp(a, "--date-format=")) {
- const char *fmt = a + 14;
- if (!strcmp(fmt, "raw"))
- whenspec = WHENSPEC_RAW;
- else if (!strcmp(fmt, "rfc2822"))
- whenspec = WHENSPEC_RFC2822;
- else if (!strcmp(fmt, "now"))
- whenspec = WHENSPEC_NOW;
- else
- die("unknown --date-format argument %s", fmt);
- }
- else if (!prefixcmp(a, "--max-pack-size="))
- max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024;
- else if (!prefixcmp(a, "--depth=")) {
- max_depth = strtoul(a + 8, NULL, 0);
- if (max_depth > MAX_DEPTH)
- die("--depth cannot exceed %u", MAX_DEPTH);
- }
- else if (!prefixcmp(a, "--active-branches="))
- max_active_branches = strtoul(a + 18, NULL, 0);
- else if (!prefixcmp(a, "--import-marks="))
- import_marks(a + 15);
- else if (!prefixcmp(a, "--export-marks="))
- mark_file = a + 15;
- else if (!prefixcmp(a, "--export-pack-edges=")) {
- if (pack_edges)
- fclose(pack_edges);
- pack_edges = fopen(a + 20, "a");
- if (!pack_edges)
- die_errno("Cannot open '%s'", a + 20);
- } else if (!strcmp(a, "--force"))
- force_update = 1;
- else if (!strcmp(a, "--quiet"))
- show_stats = 0;
- else if (!strcmp(a, "--stats"))
- show_stats = 1;
- else
- die("unknown option %s", a);
- }
- if (i != argc)
- usage(fast_import_usage);
+ global_argc = argc;
+ global_argv = argv;
rc_free = pool_alloc(cmd_save * sizeof(*rc_free));
for (i = 0; i < (cmd_save - 1); i++)
@@ -2568,9 +2702,20 @@ int main(int argc, const char **argv)
parse_checkpoint();
else if (!prefixcmp(command_buf.buf, "progress "))
parse_progress();
+ else if (!prefixcmp(command_buf.buf, "feature "))
+ parse_feature();
+ else if (!prefixcmp(command_buf.buf, "option git "))
+ parse_option();
+ else if (!prefixcmp(command_buf.buf, "option "))
+ /* ignore non-git options*/;
else
die("Unsupported command: %s", command_buf.buf);
}
+
+ /* argv hasn't been parsed yet, do so */
+ if (!seen_data_command)
+ parse_argv();
+
end_packfile();
dump_branches();
diff --git a/git-am.sh b/git-am.sh
index 4838cdb9e..2f46fda47 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -30,6 +30,7 @@ skip skip the current patch
abort restore the original branch and abort the patching operation.
committer-date-is-author-date lie about committer date
ignore-date use current timestamp for author date
+rerere-autoupdate update the index with reused conflict resolution if possible
rebasing* (internal use for git-rebase)"
. git-sh-setup
@@ -135,7 +136,7 @@ It does not apply to blobs recorded in its index."
export GIT_MERGE_VERBOSITY=0
fi
git-merge-recursive $orig_tree -- HEAD $his_tree || {
- git rerere
+ git rerere $allow_rerere_autoupdate
echo Failed to merge in the changes.
exit 1
}
@@ -293,6 +294,7 @@ resolvemsg= resume= scissors= no_inbody_headers=
git_apply_opt=
committer_date_is_author_date=
ignore_date=
+allow_rerere_autoupdate=
while test $# != 0
do
@@ -340,6 +342,8 @@ do
committer_date_is_author_date=t ;;
--ignore-date)
ignore_date=t ;;
+ --rerere-autoupdate|--no-rerere-autoupdate)
+ allow_rerere_autoupdate="$1" ;;
-q|--quiet)
GIT_QUIET=t ;;
--)
diff --git a/git-compat-util.h b/git-compat-util.h
index 85dea123a..60c8432f8 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -96,6 +96,7 @@
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
+#include <termios.h>
#ifndef NO_SYS_SELECT_H
#include <sys/select.h>
#endif
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index d52932878..1560e84bd 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -322,7 +322,7 @@ make_squash_message () {
}
peek_next_command () {
- sed -n "1s/ .*$//p" < "$TODO"
+ sed -n -e "/^#/d" -e "/^$/d" -e "s/ .*//p" -e "q" < "$TODO"
}
do_next () {
@@ -495,6 +495,25 @@ get_saved_options () {
test -f "$DOTEST"/rebase-root && REBASE_ROOT=t
}
+LF='
+'
+parse_onto () {
+ case "$1" in
+ *...*)
+ if left=${1%...*} right=${1#*...} &&
+ onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
+ then
+ case "$onto" in
+ ?*"$LF"?* | '')
+ exit 1 ;;
+ esac
+ echo "$onto"
+ exit 0
+ fi
+ esac
+ git rev-parse --verify "$1^0"
+}
+
while test $# != 0
do
case "$1" in
@@ -602,7 +621,7 @@ first and then run 'git rebase --continue' again."
;;
--onto)
shift
- ONTO=$(git rev-parse --verify "$1") ||
+ ONTO=$(parse_onto "$1") ||
die "Does not point to a valid commit: $1"
;;
--)
diff --git a/git-rebase.sh b/git-rebase.sh
index b121f4537..eddc02875 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -34,6 +34,8 @@ set_reflog_action rebase
require_work_tree
cd_to_toplevel
+LF='
+'
OK_TO_SKIP_PRE_REBASE=
RESOLVEMSG="
When you have resolved this problem run \"git rebase --continue\".
@@ -50,6 +52,7 @@ diffstat=$(git config --bool rebase.stat)
git_am_opt=
rebase_root=
force_rebase=
+allow_rerere_autoupdate=
continue_merge () {
test -n "$prev_head" || die "prev_head must be defined"
@@ -118,7 +121,7 @@ call_merge () {
return
;;
1)
- git rerere
+ git rerere $allow_rerere_autoupdate
die "$RESOLVEMSG"
;;
2)
@@ -349,6 +352,9 @@ do
-f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase)
force_rebase=t
;;
+ --rerere-autoupdate|--no-rerere-autoupdate)
+ allow_rerere_autoupdate="$1"
+ ;;
-*)
usage
;;
@@ -417,7 +423,27 @@ fi
# Make sure the branch to rebase onto is valid.
onto_name=${newbase-"$upstream_name"}
-onto=$(git rev-parse --verify "${onto_name}^0") || exit
+case "$onto_name" in
+*...*)
+ if left=${onto_name%...*} right=${onto_name#*...} &&
+ onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
+ then
+ case "$onto" in
+ ?*"$LF"?*)
+ die "$onto_name: there are more than one merge bases"
+ ;;
+ '')
+ die "$onto_name: there is no merge base"
+ ;;
+ esac
+ else
+ die "$onto_name: there is no merge base"
+ fi
+ ;;
+*)
+ onto=$(git rev-parse --verify "${onto_name}^0") || exit
+ ;;
+esac
# If a hook exists, give it a chance to interrupt
run_pre_rebase_hook "$upstream_arg" "$@"
diff --git a/http-backend.c b/http-backend.c
index f729488fc..345c12b79 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -648,6 +648,9 @@ int main(int argc, char **argv)
setup_path();
if (!enter_repo(dir, 0))
not_found("Not a git repository: '%s'", dir);
+ if (!getenv("GIT_HTTP_EXPORT_ALL") &&
+ access("git-daemon-export-ok", F_OK) )
+ not_found("Repository not exported: '%s'", dir);
git_config(http_config, NULL);
cmd->imp(cmd_arg);
diff --git a/imap-send.c b/imap-send.c
index de8114bac..51f371ba9 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -965,17 +965,13 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
/* open connection to IMAP server */
if (srvc->tunnel) {
- const char *argv[4];
+ const char *argv[] = { srvc->tunnel, NULL };
struct child_process tunnel = {0};
imap_info("Starting tunnel '%s'... ", srvc->tunnel);
- argv[0] = "sh";
- argv[1] = "-c";
- argv[2] = srvc->tunnel;
- argv[3] = NULL;
-
tunnel.argv = argv;
+ tunnel.use_shell = 1;
tunnel.in = -1;
tunnel.out = -1;
if (start_command(&tunnel))
diff --git a/ll-merge.c b/ll-merge.c
index 2d6b6d6cb..18511e281 100644
--- a/ll-merge.c
+++ b/ll-merge.c
@@ -175,7 +175,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
{ "B", temp[2] },
{ NULL }
};
- const char *args[] = { "sh", "-c", NULL, NULL };
+ const char *args[] = { NULL, NULL };
int status, fd, i;
struct stat st;
@@ -190,8 +190,8 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict);
- args[2] = cmd.buf;
- status = run_command_v_opt(args, 0);
+ args[0] = cmd.buf;
+ status = run_command_v_opt(args, RUN_USING_SHELL);
fd = open(temp[1], O_RDONLY);
if (fd < 0)
goto bad;
diff --git a/lockfile.c b/lockfile.c
index 6851fa55a..b0d74cddd 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -164,9 +164,10 @@ static char *unable_to_lock_message(const char *path, int err)
"If no other git process is currently running, this probably means a\n"
"git process crashed in this repository earlier. Make sure no other git\n"
"process is running and remove the file manually to continue.",
- path, strerror(err));
+ make_nonrelative_path(path), strerror(err));
} else
- strbuf_addf(&buf, "Unable to create '%s.lock': %s", path, strerror(err));
+ strbuf_addf(&buf, "Unable to create '%s.lock': %s",
+ make_nonrelative_path(path), strerror(err));
return strbuf_detach(&buf, NULL);
}
diff --git a/pager.c b/pager.c
index 92c03f654..2c7e8ecb3 100644
--- a/pager.c
+++ b/pager.c
@@ -28,7 +28,7 @@ static void pager_preexec(void)
}
#endif
-static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
+static const char *pager_argv[] = { NULL, NULL };
static struct child_process pager_process;
static void wait_for_pager(void)
@@ -81,7 +81,8 @@ void setup_pager(void)
spawned_pager = 1; /* means we are emitting to terminal */
/* spawn the pager */
- pager_argv[2] = pager;
+ pager_argv[0] = pager;
+ pager_process.use_shell = 1;
pager_process.argv = pager_argv;
pager_process.in = -1;
if (!getenv("LESS")) {
diff --git a/parse-options.c b/parse-options.c
index 7bbed5f3e..d218122af 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -636,3 +636,10 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
commit_list_insert(commit, opt->value);
return 0;
}
+
+int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
+{
+ int *target = opt->value;
+ *target = unset ? 2 : 1;
+ return 0;
+}
diff --git a/parse-options.h b/parse-options.h
index 72fa36011..0c996916b 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -123,6 +123,8 @@ struct option {
(h), PARSE_OPT_NOARG, NULL, (p) }
#define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), "n", (h) }
#define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) }
+#define OPT_UYN(s, l, v, h) { OPTION_CALLBACK, (s), (l), (v), NULL, \
+ (h), PARSE_OPT_NOARG, &parse_opt_tertiary }
#define OPT_DATE(s, l, v, h) \
{ OPTION_CALLBACK, (s), (l), (v), "time",(h), 0, \
parse_opt_approxidate_cb }
@@ -187,6 +189,7 @@ extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
extern int parse_opt_with_commit(const struct option *, const char *, int);
+extern int parse_opt_tertiary(const struct option *, const char *, int);
#define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose")
#define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet")
diff --git a/read-cache.c b/read-cache.c
index 9f4f44cb6..f4512967b 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -261,12 +261,17 @@ int ie_match_stat(const struct index_state *istate,
{
unsigned int changed;
int ignore_valid = options & CE_MATCH_IGNORE_VALID;
+ int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY;
/*
* If it's marked as always valid in the index, it's
* valid whatever the checked-out copy says.
+ *
+ * skip-worktree has the same effect with higher precedence
*/
+ if (!ignore_skip_worktree && ce_skip_worktree(ce))
+ return 0;
if (!ignore_valid && (ce->ce_flags & CE_VALID))
return 0;
@@ -566,7 +571,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
int size, namelen, was_same;
mode_t st_mode = st->st_mode;
struct cache_entry *ce, *alias;
- unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY;
+ unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE|CE_MATCH_RACY_IS_DIRTY;
int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND);
int pretend = flags & ADD_CACHE_PRETEND;
int intent_only = flags & ADD_CACHE_INTENT;
@@ -1002,14 +1007,20 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
struct cache_entry *updated;
int changed, size;
int ignore_valid = options & CE_MATCH_IGNORE_VALID;
+ int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
if (ce_uptodate(ce))
return ce;
/*
- * CE_VALID means the user promised us that the change to
- * the work tree does not matter and told us not to worry.
+ * CE_VALID or CE_SKIP_WORKTREE means the user promised us
+ * that the change to the work tree does not matter and told
+ * us not to worry.
*/
+ if (!ignore_skip_worktree && ce_skip_worktree(ce)) {
+ ce_mark_uptodate(ce);
+ return ce;
+ }
if (!ignore_valid && (ce->ce_flags & CE_VALID)) {
ce_mark_uptodate(ce);
return ce;
@@ -1608,9 +1619,8 @@ int read_index_unmerged(struct index_state *istate)
len = strlen(ce->name);
size = cache_entry_size(len);
new_ce = xcalloc(1, size);
- hashcpy(new_ce->sha1, ce->sha1);
memcpy(new_ce->name, ce->name, len);
- new_ce->ce_flags = create_ce_flags(len, 0);
+ new_ce->ce_flags = create_ce_flags(len, 0) | CE_CONFLICTED;
new_ce->ce_mode = ce->ce_mode;
if (add_index_entry(istate, new_ce, 0))
return error("%s: cannot drop to stage #0",
diff --git a/remote-curl.c b/remote-curl.c
index b76dcb2e8..136100695 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -510,7 +510,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc);
rpc->hdr_content_type = strbuf_detach(&buf, NULL);
- strbuf_addf(&buf, "Accept: application/x-%s-response", svc);
+ strbuf_addf(&buf, "Accept: application/x-%s-result", svc);
rpc->hdr_accept = strbuf_detach(&buf, NULL);
while (!err) {
diff --git a/rerere.c b/rerere.c
index 29f95f657..e0ac5bcae 100644
--- a/rerere.c
+++ b/rerere.c
@@ -367,7 +367,7 @@ static int is_rerere_enabled(void)
return 1;
}
-int setup_rerere(struct string_list *merge_rr)
+int setup_rerere(struct string_list *merge_rr, int flags)
{
int fd;
@@ -375,6 +375,8 @@ int setup_rerere(struct string_list *merge_rr)
if (!is_rerere_enabled())
return -1;
+ if (flags & (RERERE_AUTOUPDATE|RERERE_NOAUTOUPDATE))
+ rerere_autoupdate = !!(flags & RERERE_AUTOUPDATE);
merge_rr_path = git_pathdup("MERGE_RR");
fd = hold_lock_file_for_update(&write_lock, merge_rr_path,
LOCK_DIE_ON_ERROR);
@@ -382,12 +384,12 @@ int setup_rerere(struct string_list *merge_rr)
return fd;
}
-int rerere(void)
+int rerere(int flags)
{
struct string_list merge_rr = { NULL, 0, 0, 1 };
int fd;
- fd = setup_rerere(&merge_rr);
+ fd = setup_rerere(&merge_rr, flags);
if (fd < 0)
return 0;
return do_plain_rerere(&merge_rr, fd);
diff --git a/rerere.h b/rerere.h
index 13313f3f2..10a94a4ea 100644
--- a/rerere.h
+++ b/rerere.h
@@ -3,9 +3,15 @@
#include "string-list.h"
-extern int setup_rerere(struct string_list *);
-extern int rerere(void);
+#define RERERE_AUTOUPDATE 01
+#define RERERE_NOAUTOUPDATE 02
+
+extern int setup_rerere(struct string_list *, int);
+extern int rerere(int);
extern const char *rerere_path(const char *hex, const char *file);
extern int has_rerere_resolution(const char *hex);
+#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \
+ "update the index with reused conflict resolution if possible")
+
#endif
diff --git a/run-command.c b/run-command.c
index cf2d8f7fa..a90984576 100644
--- a/run-command.c
+++ b/run-command.c
@@ -8,12 +8,58 @@ static inline void close_pair(int fd[2])
close(fd[1]);
}
+#ifndef WIN32
static inline void dup_devnull(int to)
{
int fd = open("/dev/null", O_RDWR);
dup2(fd, to);
close(fd);
}
+#endif
+
+static const char **prepare_shell_cmd(const char **argv)
+{
+ int argc, nargc = 0;
+ const char **nargv;
+
+ for (argc = 0; argv[argc]; argc++)
+ ; /* just counting */
+ /* +1 for NULL, +3 for "sh -c" plus extra $0 */
+ nargv = xmalloc(sizeof(*nargv) * (argc + 1 + 3));
+
+ if (argc < 1)
+ die("BUG: shell command is empty");
+
+ if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) {
+ nargv[nargc++] = "sh";
+ nargv[nargc++] = "-c";
+
+ if (argc < 2)
+ nargv[nargc++] = argv[0];
+ else {
+ struct strbuf arg0 = STRBUF_INIT;
+ strbuf_addf(&arg0, "%s \"$@\"", argv[0]);
+ nargv[nargc++] = strbuf_detach(&arg0, NULL);
+ }
+ }
+
+ for (argc = 0; argv[argc]; argc++)
+ nargv[nargc++] = argv[argc];
+ nargv[nargc] = NULL;
+
+ return nargv;
+}
+
+#ifndef WIN32
+static int execv_shell_cmd(const char **argv)
+{
+ const char **nargv = prepare_shell_cmd(argv);
+ trace_argv_printf(nargv, "trace: exec:");
+ execvp(nargv[0], (char **)nargv);
+ free(nargv);
+ return -1;
+}
+#endif
int start_command(struct child_process *cmd)
{
@@ -123,6 +169,8 @@ fail_pipe:
cmd->preexec_cb();
if (cmd->git_cmd) {
execv_git_cmd(cmd->argv);
+ } else if (cmd->use_shell) {
+ execv_shell_cmd(cmd->argv);
} else {
execvp(cmd->argv[0], (char *const*) cmd->argv);
}
@@ -135,42 +183,30 @@ fail_pipe:
strerror(failed_errno = errno));
#else
{
- int s0 = -1, s1 = -1, s2 = -1; /* backups of stdin, stdout, stderr */
+ int fhin = 0, fhout = 1, fherr = 2;
const char **sargv = cmd->argv;
char **env = environ;
- if (cmd->no_stdin) {
- s0 = dup(0);
- dup_devnull(0);
- } else if (need_in) {
- s0 = dup(0);
- dup2(fdin[0], 0);
- } else if (cmd->in) {
- s0 = dup(0);
- dup2(cmd->in, 0);
- }
-
- if (cmd->no_stderr) {
- s2 = dup(2);
- dup_devnull(2);
- } else if (need_err) {
- s2 = dup(2);
- dup2(fderr[1], 2);
- }
-
- if (cmd->no_stdout) {
- s1 = dup(1);
- dup_devnull(1);
- } else if (cmd->stdout_to_stderr) {
- s1 = dup(1);
- dup2(2, 1);
- } else if (need_out) {
- s1 = dup(1);
- dup2(fdout[1], 1);
- } else if (cmd->out > 1) {
- s1 = dup(1);
- dup2(cmd->out, 1);
- }
+ if (cmd->no_stdin)
+ fhin = open("/dev/null", O_RDWR);
+ else if (need_in)
+ fhin = dup(fdin[0]);
+ else if (cmd->in)
+ fhin = dup(cmd->in);
+
+ if (cmd->no_stderr)
+ fherr = open("/dev/null", O_RDWR);
+ else if (need_err)
+ fherr = dup(fderr[1]);
+
+ if (cmd->no_stdout)
+ fhout = open("/dev/null", O_RDWR);
+ else if (cmd->stdout_to_stderr)
+ fhout = dup(fherr);
+ else if (need_out)
+ fhout = dup(fdout[1]);
+ else if (cmd->out > 1)
+ fhout = dup(cmd->out);
if (cmd->dir)
die("chdir in start_command() not implemented");
@@ -179,9 +215,12 @@ fail_pipe:
if (cmd->git_cmd) {
cmd->argv = prepare_git_cmd(cmd->argv);
+ } else if (cmd->use_shell) {
+ cmd->argv = prepare_shell_cmd(cmd->argv);
}
- cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env);
+ cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env,
+ fhin, fhout, fherr);
failed_errno = errno;
if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))
error("cannot spawn %s: %s", cmd->argv[0], strerror(errno));
@@ -192,12 +231,12 @@ fail_pipe:
free(cmd->argv);
cmd->argv = sargv;
- if (s0 >= 0)
- dup2(s0, 0), close(s0);
- if (s1 >= 0)
- dup2(s1, 1), close(s1);
- if (s2 >= 0)
- dup2(s2, 2), close(s2);
+ if (fhin != 0)
+ close(fhin);
+ if (fhout != 1)
+ close(fhout);
+ if (fherr != 2)
+ close(fherr);
}
#endif
@@ -297,6 +336,7 @@ static void prepare_run_command_v_opt(struct child_process *cmd,
cmd->git_cmd = opt & RUN_GIT_CMD ? 1 : 0;
cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
cmd->silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0;
+ cmd->use_shell = opt & RUN_USING_SHELL ? 1 : 0;
}
int run_command_v_opt(const char **argv, int opt)
diff --git a/run-command.h b/run-command.h
index fb342090e..967ba8cc0 100644
--- a/run-command.h
+++ b/run-command.h
@@ -33,6 +33,7 @@ struct child_process {
unsigned git_cmd:1; /* if this is to be git sub-command */
unsigned silent_exec_failure:1;
unsigned stdout_to_stderr:1;
+ unsigned use_shell:1;
void (*preexec_cb)(void);
};
@@ -46,6 +47,7 @@ extern int run_hook(const char *index_file, const char *name, ...);
#define RUN_GIT_CMD 2 /*If this is to be git sub-command */
#define RUN_COMMAND_STDOUT_TO_STDERR 4
#define RUN_SILENT_EXEC_FAILURE 8
+#define RUN_USING_SHELL 16
int run_command_v_opt(const char **argv, int opt);
/*
diff --git a/sha1_name.c b/sha1_name.c
index ca8f9dba9..1739e9e61 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -794,6 +794,48 @@ release_return:
return retval;
}
+int get_sha1_mb(const char *name, unsigned char *sha1)
+{
+ struct commit *one, *two;
+ struct commit_list *mbs;
+ unsigned char sha1_tmp[20];
+ const char *dots;
+ int st;
+
+ dots = strstr(name, "...");
+ if (!dots)
+ return get_sha1(name, sha1);
+ if (dots == name)
+ st = get_sha1("HEAD", sha1_tmp);
+ else {
+ struct strbuf sb;
+ strbuf_init(&sb, dots - name);
+ strbuf_add(&sb, name, dots - name);
+ st = get_sha1(sb.buf, sha1_tmp);
+ strbuf_release(&sb);
+ }
+ if (st)
+ return st;
+ one = lookup_commit_reference_gently(sha1_tmp, 0);
+ if (!one)
+ return -1;
+
+ if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp))
+ return -1;
+ two = lookup_commit_reference_gently(sha1_tmp, 0);
+ if (!two)
+ return -1;
+ mbs = get_merge_bases(one, two, 1);
+ if (!mbs || mbs->next)
+ st = -1;
+ else {
+ st = 0;
+ hashcpy(sha1, mbs->item->object.sha1);
+ }
+ free_commit_list(mbs);
+ return st;
+}
+
/*
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
* notably "xyz^" for "parent of xyz"
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
index 6765b0806..28aff887b 100644
--- a/t/lib-httpd.sh
+++ b/t/lib-httpd.sh
@@ -12,16 +12,29 @@ fi
HTTPD_PARA=""
+for DEFAULT_HTTPD_PATH in '/usr/sbin/httpd' '/usr/sbin/apache2'
+do
+ if test -x "$DEFAULT_HTTPD_PATH"
+ then
+ break
+ fi
+done
+
+for DEFAULT_HTTPD_MODULE_PATH in '/usr/libexec/apache2' \
+ '/usr/lib/apache2/modules' \
+ '/usr/lib64/httpd/modules' \
+ '/usr/lib/httpd/modules'
+do
+ if test -d "$DEFAULT_HTTPD_MODULE_PATH"
+ then
+ break
+ fi
+done
+
case $(uname) in
Darwin)
- DEFAULT_HTTPD_PATH='/usr/sbin/httpd'
- DEFAULT_HTTPD_MODULE_PATH='/usr/libexec/apache2'
HTTPD_PARA="$HTTPD_PARA -DDarwin"
;;
- *)
- DEFAULT_HTTPD_PATH='/usr/sbin/apache2'
- DEFAULT_HTTPD_MODULE_PATH='/usr/lib/apache2/modules'
- ;;
esac
LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"}
@@ -49,6 +62,11 @@ then
say "skipping test, at least Apache version 2 is required"
test_done
fi
+ if ! test -d "$DEFAULT_HTTPD_MODULE_PATH"
+ then
+ say "Apache module directory not found. Skipping tests."
+ test_done
+ fi
LIB_HTTPD_MODULE_PATH="$DEFAULT_HTTPD_MODULE_PATH"
fi
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index 0fe3fd0d0..4961505d1 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -22,8 +22,13 @@ Alias /dumb/ www/
<Location /smart/>
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
+ SetEnv GIT_HTTP_EXPORT_ALL
+</Location>
+<Location /smart_noexport/>
+ SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
</Location>
ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/
+ScriptAlias /smart_noexport/ ${GIT_EXEC_PATH}/git-http-backend/
<Directory ${GIT_EXEC_PATH}>
Options None
</Directory>
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index 8fc39d77c..6cb8d60ea 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -4,7 +4,8 @@ test_description='blob conversion via gitattributes'
. ./test-lib.sh
-cat <<\EOF >rot13.sh
+cat <<EOF >rot13.sh
+#!$SHELL_PATH
tr \
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \
'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
new file mode 100755
index 000000000..62246dbf9
--- /dev/null
+++ b/t/t1011-read-tree-sparse-checkout.sh
@@ -0,0 +1,150 @@
+#!/bin/sh
+
+test_description='sparse checkout tests'
+
+. ./test-lib.sh
+
+cat >expected <<EOF
+100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t
+100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added
+EOF
+test_expect_success 'setup' '
+ test_commit init &&
+ echo modified >> init.t &&
+ mkdir sub &&
+ touch sub/added &&
+ git add init.t sub/added &&
+ git commit -m "modified and added" &&
+ git tag top &&
+ git rm sub/added &&
+ git commit -m removed &&
+ git tag removed &&
+ git checkout top &&
+ git ls-files --stage > result &&
+ test_cmp expected result
+'
+
+cat >expected.swt <<EOF
+H init.t
+H sub/added
+EOF
+test_expect_success 'read-tree without .git/info/sparse-checkout' '
+ git read-tree -m -u HEAD &&
+ git ls-files --stage > result &&
+ test_cmp expected result &&
+ git ls-files -t > result &&
+ test_cmp expected.swt result
+'
+
+test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' '
+ echo > .git/info/sparse-checkout
+ git read-tree -m -u HEAD &&
+ git ls-files -t > result &&
+ test_cmp expected.swt result &&
+ test -f init.t &&
+ test -f sub/added
+'
+
+test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' '
+ git config core.sparsecheckout true &&
+ echo > .git/info/sparse-checkout &&
+ git read-tree --no-sparse-checkout -m -u HEAD &&
+ git ls-files -t > result &&
+ test_cmp expected.swt result &&
+ test -f init.t &&
+ test -f sub/added
+'
+
+test_expect_success 'read-tree with empty .git/info/sparse-checkout' '
+ git config core.sparsecheckout true &&
+ echo > .git/info/sparse-checkout &&
+ test_must_fail git read-tree -m -u HEAD &&
+ git ls-files --stage > result &&
+ test_cmp expected result &&
+ git ls-files -t > result &&
+ test_cmp expected.swt result &&
+ test -f init.t &&
+ test -f sub/added
+'
+
+cat >expected.swt <<EOF
+S init.t
+H sub/added
+EOF
+test_expect_success 'match directories with trailing slash' '
+ echo sub/ > .git/info/sparse-checkout &&
+ git read-tree -m -u HEAD &&
+ git ls-files -t > result &&
+ test_cmp expected.swt result &&
+ test ! -f init.t &&
+ test -f sub/added
+'
+
+cat >expected.swt <<EOF
+H init.t
+H sub/added
+EOF
+test_expect_failure 'match directories without trailing slash' '
+ echo init.t > .git/info/sparse-checkout &&
+ echo sub >> .git/info/sparse-checkout &&
+ git read-tree -m -u HEAD &&
+ git ls-files -t > result &&
+ test_cmp expected.swt result &&
+ test ! -f init.t &&
+ test -f sub/added
+'
+
+cat >expected.swt <<EOF
+H init.t
+S sub/added
+EOF
+test_expect_success 'checkout area changes' '
+ echo init.t > .git/info/sparse-checkout &&
+ git read-tree -m -u HEAD &&
+ git ls-files -t > result &&
+ test_cmp expected.swt result &&
+ test -f init.t &&
+ test ! -f sub/added
+'
+
+test_expect_success 'read-tree updates worktree, absent case' '
+ echo sub/added > .git/info/sparse-checkout &&
+ git checkout -f top &&
+ git read-tree -m -u HEAD^ &&
+ test ! -f init.t
+'
+
+test_expect_success 'read-tree updates worktree, dirty case' '
+ echo sub/added > .git/info/sparse-checkout &&
+ git checkout -f top &&
+ echo dirty > init.t &&
+ git read-tree -m -u HEAD^ &&
+ grep -q dirty init.t &&
+ rm init.t
+'
+
+test_expect_success 'read-tree removes worktree, dirty case' '
+ echo init.t > .git/info/sparse-checkout &&
+ git checkout -f top &&
+ echo dirty > added &&
+ git read-tree -m -u HEAD^ &&
+ grep -q dirty added
+'
+
+test_expect_success 'read-tree adds to worktree, absent case' '
+ echo init.t > .git/info/sparse-checkout &&
+ git checkout -f removed &&
+ git read-tree -u -m HEAD^ &&
+ test ! -f sub/added
+'
+
+test_expect_success 'read-tree adds to worktree, dirty case' '
+ echo init.t > .git/info/sparse-checkout &&
+ git checkout -f removed &&
+ mkdir sub &&
+ echo dirty > sub/added &&
+ git read-tree -u -m HEAD^ &&
+ grep -q dirty sub/added
+'
+
+test_done
diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
index 87b30a268..b44de9dc6 100755
--- a/t/t2012-checkout-last.sh
+++ b/t/t2012-checkout-last.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-test_description='checkout can switch to last branch'
+test_description='checkout can switch to last branch and merge base'
. ./test-lib.sh
@@ -91,4 +91,29 @@ test_expect_success 'switch to twelfth from the last' '
test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13"
'
+test_expect_success 'merge base test setup' '
+ git checkout -b another other &&
+ echo "hello again" >>world &&
+ git add world &&
+ git commit -m third
+'
+
+test_expect_success 'another...master' '
+ git checkout another &&
+ git checkout another...master &&
+ test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
+'
+
+test_expect_success '...master' '
+ git checkout another &&
+ git checkout ...master &&
+ test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
+'
+
+test_expect_success 'master...' '
+ git checkout another &&
+ git checkout master... &&
+ test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
+'
+
test_done
diff --git a/t/t2104-update-index-skip-worktree.sh b/t/t2104-update-index-skip-worktree.sh
new file mode 100755
index 000000000..1d0879be0
--- /dev/null
+++ b/t/t2104-update-index-skip-worktree.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
+#
+
+test_description='skip-worktree bit test'
+
+. ./test-lib.sh
+
+cat >expect.full <<EOF
+H 1
+H 2
+H sub/1
+H sub/2
+EOF
+
+cat >expect.skip <<EOF
+S 1
+H 2
+S sub/1
+H sub/2
+EOF
+
+test_expect_success 'setup' '
+ mkdir sub &&
+ touch ./1 ./2 sub/1 sub/2 &&
+ git add 1 2 sub/1 sub/2 &&
+ git ls-files -t | test_cmp expect.full -
+'
+
+test_expect_success 'index is at version 2' '
+ test "$(test-index-version < .git/index)" = 2
+'
+
+test_expect_success 'update-index --skip-worktree' '
+ git update-index --skip-worktree 1 sub/1 &&
+ git ls-files -t | test_cmp expect.skip -
+'
+
+test_expect_success 'index is at version 3 after having some skip-worktree entries' '
+ test "$(test-index-version < .git/index)" = 3
+'
+
+test_expect_success 'ls-files -t' '
+ git ls-files -t | test_cmp expect.skip -
+'
+
+test_expect_success 'update-index --no-skip-worktree' '
+ git update-index --no-skip-worktree 1 sub/1 &&
+ git ls-files -t | test_cmp expect.full -
+'
+
+test_expect_success 'index version is back to 2 when there is no skip-worktree entry' '
+ test "$(test-index-version < .git/index)" = 2
+'
+
+test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index c65bca838..132c4765c 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -64,6 +64,8 @@ two/*.4
echo '!*.2
!*.8' >one/two/.gitignore
+allignores='.gitignore one/.gitignore one/two/.gitignore'
+
test_expect_success \
'git ls-files --others with various exclude options.' \
'git ls-files --others \
@@ -85,6 +87,26 @@ test_expect_success \
>output &&
test_cmp expect output'
+test_expect_success 'setup skip-worktree gitignore' '
+ git add $allignores &&
+ git update-index --skip-worktree $allignores &&
+ rm $allignores
+'
+
+test_expect_success \
+ 'git ls-files --others with various exclude options.' \
+ 'git ls-files --others \
+ --exclude=\*.6 \
+ --exclude-per-directory=.gitignore \
+ --exclude-from=.git/ignore \
+ >output &&
+ test_cmp expect output'
+
+test_expect_success 'restore gitignore' '
+ git checkout $allignores &&
+ rm .git/index
+'
+
cat > excludes-file <<\EOF
*.[1-8]
e*
diff --git a/t/t3415-rebase-onto-threedots.sh b/t/t3415-rebase-onto-threedots.sh
new file mode 100755
index 000000000..ddf2f6485
--- /dev/null
+++ b/t/t3415-rebase-onto-threedots.sh
@@ -0,0 +1,105 @@
+#!/bin/sh
+
+test_description='git rebase --onto A...B'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-rebase.sh"
+
+# Rebase only the tip commit of "topic" on merge base between "master"
+# and "topic". Cannot do this for "side" with "master" because there
+# is no single merge base.
+#
+#
+# F---G topic G'
+# / /
+# A---B---C---D---E master --> A---B---C---D---E
+# \ \ /
+# \ x
+# \ / \
+# H---I---J---K side
+
+test_expect_success setup '
+ test_commit A &&
+ test_commit B &&
+ git branch side &&
+ test_commit C &&
+ git branch topic &&
+ git checkout side &&
+ test_commit H &&
+ git checkout master &&
+ test_tick &&
+ git merge H &&
+ git tag D &&
+ test_commit E &&
+ git checkout topic &&
+ test_commit F &&
+ test_commit G &&
+ git checkout side &&
+ test_tick &&
+ git merge C &&
+ git tag I &&
+ test_commit J &&
+ test_commit K
+'
+
+test_expect_success 'rebase --onto master...topic' '
+ git reset --hard &&
+ git checkout topic &&
+ git reset --hard G &&
+
+ git rebase --onto master...topic F &&
+ git rev-parse HEAD^1 >actual &&
+ git rev-parse C^0 >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rebase --onto master...' '
+ git reset --hard &&
+ git checkout topic &&
+ git reset --hard G &&
+
+ git rebase --onto master... F &&
+ git rev-parse HEAD^1 >actual &&
+ git rev-parse C^0 >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rebase --onto master...side' '
+ git reset --hard &&
+ git checkout side &&
+ git reset --hard K &&
+
+ test_must_fail git rebase --onto master...side J
+'
+
+test_expect_success 'rebase -i --onto master...topic' '
+ git reset --hard &&
+ git checkout topic &&
+ git reset --hard G &&
+ set_fake_editor &&
+ EXPECT_COUNT=1 git rebase -i --onto master...topic F &&
+ git rev-parse HEAD^1 >actual &&
+ git rev-parse C^0 >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rebase -i --onto master...' '
+ git reset --hard &&
+ git checkout topic &&
+ git reset --hard G &&
+ set_fake_editor &&
+ EXPECT_COUNT=1 git rebase -i --onto master... F &&
+ git rev-parse HEAD^1 >actual &&
+ git rev-parse C^0 >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rebase -i --onto master...side' '
+ git reset --hard &&
+ git checkout side &&
+ git reset --hard K &&
+
+ test_must_fail git rebase -i --onto master...side J
+'
+
+test_done
diff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh
index a3f0897a5..88c5619ae 100755
--- a/t/t4030-diff-textconv.sh
+++ b/t/t4030-diff-textconv.sh
@@ -48,7 +48,7 @@ test_expect_success 'file is considered binary by plumbing' '
test_expect_success 'setup textconv filters' '
echo file diff=foo >.gitattributes &&
- git config diff.foo.textconv "$PWD"/hexdump &&
+ git config diff.foo.textconv "\"$(pwd)\""/hexdump &&
git config diff.fail.textconv false
'
diff --git a/t/t4031-diff-rewrite-binary.sh b/t/t4031-diff-rewrite-binary.sh
index a894c6062..7e7b307a2 100755
--- a/t/t4031-diff-rewrite-binary.sh
+++ b/t/t4031-diff-rewrite-binary.sh
@@ -54,7 +54,7 @@ chmod +x dump
test_expect_success 'setup textconv' '
echo file diff=foo >.gitattributes &&
- git config diff.foo.textconv "$PWD"/dump
+ git config diff.foo.textconv "\"$(pwd)\""/dump
'
test_expect_success 'rewrite diff respects textconv' '
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index a6bc028a5..bb402c378 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -217,7 +217,22 @@ test_expect_success 'rerere.autoupdate' '
git checkout version2 &&
test_must_fail git merge fifth &&
test 0 = $(git ls-files -u | wc -l)
+'
+test_expect_success 'merge --rerere-autoupdate' '
+ git config --unset rerere.autoupdate
+ git reset --hard &&
+ git checkout version2 &&
+ test_must_fail git merge --rerere-autoupdate fifth &&
+ test 0 = $(git ls-files -u | wc -l)
+'
+
+test_expect_success 'merge --no-rerere-autoupdate' '
+ git config rerere.autoupdate true
+ git reset --hard &&
+ git checkout version2 &&
+ test_must_fail git merge --no-rerere-autoupdate fifth &&
+ test 2 = $(git ls-files -u | wc -l)
'
test_done
diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh
index c0505ecd7..7faa31a29 100755
--- a/t/t5551-http-fetch.sh
+++ b/t/t5551-http-fetch.sh
@@ -38,7 +38,7 @@ cat >exp <<EOF
> POST /smart/repo.git/git-upload-pack HTTP/1.1
> Accept-Encoding: deflate, gzip
> Content-Type: application/x-git-upload-pack-request
-> Accept: application/x-git-upload-pack-response
+> Accept: application/x-git-upload-pack-result
> Content-Length: xxx
< HTTP/1.1 200 OK
< Pragma: no-cache
diff --git a/t/t5560-http-backend-noserver.sh b/t/t5560-http-backend-noserver.sh
new file mode 100755
index 000000000..44885b850
--- /dev/null
+++ b/t/t5560-http-backend-noserver.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+test_description='test git-http-backend-noserver'
+. ./test-lib.sh
+
+HTTPD_DOCUMENT_ROOT_PATH="$TRASH_DIRECTORY"
+
+run_backend() {
+ echo "$2" |
+ QUERY_STRING="${1#*\?}" \
+ GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \
+ PATH_INFO="${1%%\?*}" \
+ git http-backend >act.out 2>act.err
+}
+
+GET() {
+ export REQUEST_METHOD="GET" &&
+ run_backend "/repo.git/$1" &&
+ unset REQUEST_METHOD &&
+ if ! grep "Status" act.out >act
+ then
+ printf "Status: 200 OK\r\n" >act
+ fi
+ printf "Status: $2\r\n" >exp &&
+ test_cmp exp act
+}
+
+POST() {
+ export REQUEST_METHOD="POST" &&
+ export CONTENT_TYPE="application/x-$1-request" &&
+ run_backend "/repo.git/$1" "$2" &&
+ unset REQUEST_METHOD &&
+ unset CONTENT_TYPE &&
+ if ! grep "Status" act.out >act
+ then
+ printf "Status: 200 OK\r\n" >act
+ fi
+ printf "Status: $3\r\n" >exp &&
+ test_cmp exp act
+}
+
+log_div() {
+ return 0
+}
+
+. "$TEST_DIRECTORY"/t556x_common
+
+expect_aliased() {
+ export REQUEST_METHOD="GET" &&
+ if test $1 = 0; then
+ run_backend "$2"
+ else
+ run_backend "$2" &&
+ echo "fatal: '$2': aliased" >exp.err &&
+ test_cmp exp.err act.err
+ fi
+ unset REQUEST_METHOD
+}
+
+test_expect_success 'http-backend blocks bad PATH_INFO' '
+ config http.getanyfile true &&
+
+ expect_aliased 0 /repo.git/HEAD &&
+
+ expect_aliased 1 /repo.git/../HEAD &&
+ expect_aliased 1 /../etc/passwd &&
+ expect_aliased 1 ../etc/passwd &&
+ expect_aliased 1 /etc//passwd &&
+ expect_aliased 1 /etc/./passwd &&
+ expect_aliased 1 //domain/data.txt
+'
+
+test_done
diff --git a/t/t5560-http-backend.sh b/t/t5560-http-backend.sh
deleted file mode 100755
index ed034bc98..000000000
--- a/t/t5560-http-backend.sh
+++ /dev/null
@@ -1,260 +0,0 @@
-#!/bin/sh
-
-test_description='test git-http-backend'
-. ./test-lib.sh
-
-if test -n "$NO_CURL"; then
- say 'skipping test, git built without http support'
- test_done
-fi
-
-LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5560'}
-. "$TEST_DIRECTORY"/lib-httpd.sh
-start_httpd
-
-find_file() {
- cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- find $1 -type f |
- sed -e 1q
-}
-
-config() {
- git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" config $1 $2
-}
-
-GET() {
- curl --include "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null &&
- tr '\015' Q <out |
- sed '
- s/Q$//
- 1q
- ' >act &&
- echo "HTTP/1.1 $2" >exp &&
- test_cmp exp act
-}
-
-POST() {
- curl --include --data "$2" \
- --header "Content-Type: application/x-$1-request" \
- "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null &&
- tr '\015' Q <out |
- sed '
- s/Q$//
- 1q
- ' >act &&
- echo "HTTP/1.1 $3" >exp &&
- test_cmp exp act
-}
-
-log_div() {
- echo >>"$HTTPD_ROOT_PATH"/access.log
- echo "### $1" >>"$HTTPD_ROOT_PATH"/access.log
- echo "###" >>"$HTTPD_ROOT_PATH"/access.log
-}
-
-test_expect_success 'setup repository' '
- echo content >file &&
- git add file &&
- git commit -m one &&
-
- mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- git --bare init &&
- : >objects/info/alternates &&
- : >objects/info/http-alternates
- ) &&
- git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- git push public master:master &&
-
- (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- git repack -a -d
- ) &&
-
- echo other >file &&
- git add file &&
- git commit -m two &&
- git push public master:master &&
-
- LOOSE_URL=$(find_file objects/??) &&
- PACK_URL=$(find_file objects/pack/*.pack) &&
- IDX_URL=$(find_file objects/pack/*.idx)
-'
-
-get_static_files() {
- GET HEAD "$1" &&
- GET info/refs "$1" &&
- GET objects/info/packs "$1" &&
- GET objects/info/alternates "$1" &&
- GET objects/info/http-alternates "$1" &&
- GET $LOOSE_URL "$1" &&
- GET $PACK_URL "$1" &&
- GET $IDX_URL "$1"
-}
-
-test_expect_success 'direct refs/heads/master not found' '
- log_div "refs/heads/master"
- GET refs/heads/master "404 Not Found"
-'
-test_expect_success 'static file is ok' '
- log_div "getanyfile default"
- get_static_files "200 OK"
-'
-test_expect_success 'static file if http.getanyfile true is ok' '
- log_div "getanyfile true"
- config http.getanyfile true &&
- get_static_files "200 OK"
-'
-test_expect_success 'static file if http.getanyfile false fails' '
- log_div "getanyfile false"
- config http.getanyfile false &&
- get_static_files "403 Forbidden"
-'
-
-test_expect_success 'http.uploadpack default enabled' '
- log_div "uploadpack default"
- GET info/refs?service=git-upload-pack "200 OK" &&
- POST git-upload-pack 0000 "200 OK"
-'
-test_expect_success 'http.uploadpack true' '
- log_div "uploadpack true"
- config http.uploadpack true &&
- GET info/refs?service=git-upload-pack "200 OK" &&
- POST git-upload-pack 0000 "200 OK"
-'
-test_expect_success 'http.uploadpack false' '
- log_div "uploadpack false"
- config http.uploadpack false &&
- GET info/refs?service=git-upload-pack "403 Forbidden" &&
- POST git-upload-pack 0000 "403 Forbidden"
-'
-
-test_expect_success 'http.receivepack default disabled' '
- log_div "receivepack default"
- GET info/refs?service=git-receive-pack "403 Forbidden" &&
- POST git-receive-pack 0000 "403 Forbidden"
-'
-test_expect_success 'http.receivepack true' '
- log_div "receivepack true"
- config http.receivepack true &&
- GET info/refs?service=git-receive-pack "200 OK" &&
- POST git-receive-pack 0000 "200 OK"
-'
-test_expect_success 'http.receivepack false' '
- log_div "receivepack false"
- config http.receivepack false &&
- GET info/refs?service=git-receive-pack "403 Forbidden" &&
- POST git-receive-pack 0000 "403 Forbidden"
-'
-
-run_backend() {
- REQUEST_METHOD=GET \
- GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \
- PATH_INFO="$2" \
- git http-backend >act.out 2>act.err
-}
-
-path_info() {
- if test $1 = 0; then
- run_backend "$2"
- else
- test_must_fail run_backend "$2" &&
- echo "fatal: '$2': aliased" >exp.err &&
- test_cmp exp.err act.err
- fi
-}
-
-test_expect_success 'http-backend blocks bad PATH_INFO' '
- config http.getanyfile true &&
-
- run_backend 0 /repo.git/HEAD &&
-
- run_backend 1 /repo.git/../HEAD &&
- run_backend 1 /../etc/passwd &&
- run_backend 1 ../etc/passwd &&
- run_backend 1 /etc//passwd &&
- run_backend 1 /etc/./passwd &&
- run_backend 1 /etc/.../passwd &&
- run_backend 1 //domain/data.txt
-'
-
-cat >exp <<EOF
-
-### refs/heads/master
-###
-GET /smart/repo.git/refs/heads/master HTTP/1.1 404 -
-
-### getanyfile default
-###
-GET /smart/repo.git/HEAD HTTP/1.1 200
-GET /smart/repo.git/info/refs HTTP/1.1 200
-GET /smart/repo.git/objects/info/packs HTTP/1.1 200
-GET /smart/repo.git/objects/info/alternates HTTP/1.1 200 -
-GET /smart/repo.git/objects/info/http-alternates HTTP/1.1 200 -
-GET /smart/repo.git/$LOOSE_URL HTTP/1.1 200
-GET /smart/repo.git/$PACK_URL HTTP/1.1 200
-GET /smart/repo.git/$IDX_URL HTTP/1.1 200
-
-### getanyfile true
-###
-GET /smart/repo.git/HEAD HTTP/1.1 200
-GET /smart/repo.git/info/refs HTTP/1.1 200
-GET /smart/repo.git/objects/info/packs HTTP/1.1 200
-GET /smart/repo.git/objects/info/alternates HTTP/1.1 200 -
-GET /smart/repo.git/objects/info/http-alternates HTTP/1.1 200 -
-GET /smart/repo.git/$LOOSE_URL HTTP/1.1 200
-GET /smart/repo.git/$PACK_URL HTTP/1.1 200
-GET /smart/repo.git/$IDX_URL HTTP/1.1 200
-
-### getanyfile false
-###
-GET /smart/repo.git/HEAD HTTP/1.1 403 -
-GET /smart/repo.git/info/refs HTTP/1.1 403 -
-GET /smart/repo.git/objects/info/packs HTTP/1.1 403 -
-GET /smart/repo.git/objects/info/alternates HTTP/1.1 403 -
-GET /smart/repo.git/objects/info/http-alternates HTTP/1.1 403 -
-GET /smart/repo.git/$LOOSE_URL HTTP/1.1 403 -
-GET /smart/repo.git/$PACK_URL HTTP/1.1 403 -
-GET /smart/repo.git/$IDX_URL HTTP/1.1 403 -
-
-### uploadpack default
-###
-GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
-POST /smart/repo.git/git-upload-pack HTTP/1.1 200 -
-
-### uploadpack true
-###
-GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
-POST /smart/repo.git/git-upload-pack HTTP/1.1 200 -
-
-### uploadpack false
-###
-GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 403 -
-POST /smart/repo.git/git-upload-pack HTTP/1.1 403 -
-
-### receivepack default
-###
-GET /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 -
-POST /smart/repo.git/git-receive-pack HTTP/1.1 403 -
-
-### receivepack true
-###
-GET /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
-POST /smart/repo.git/git-receive-pack HTTP/1.1 200 -
-
-### receivepack false
-###
-GET /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 -
-POST /smart/repo.git/git-receive-pack HTTP/1.1 403 -
-EOF
-test_expect_success 'server request log matches test results' '
- sed -e "
- s/^.* \"//
- s/\"//
- s/ [1-9][0-9]*\$//
- s/^GET /GET /
- " >act <"$HTTPD_ROOT_PATH"/access.log &&
- test_cmp exp act
-'
-
-stop_httpd
-test_done
diff --git a/t/t5561-http-backend.sh b/t/t5561-http-backend.sh
new file mode 100755
index 000000000..8c6d0b2f2
--- /dev/null
+++ b/t/t5561-http-backend.sh
@@ -0,0 +1,149 @@
+#!/bin/sh
+
+test_description='test git-http-backend'
+. ./test-lib.sh
+
+if test -n "$NO_CURL"; then
+ say 'skipping test, git built without http support'
+ test_done
+fi
+
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5561'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+GET() {
+ curl --include "$HTTPD_URL/$SMART/repo.git/$1" >out 2>/dev/null &&
+ tr '\015' Q <out |
+ sed '
+ s/Q$//
+ 1q
+ ' >act &&
+ echo "HTTP/1.1 $2" >exp &&
+ test_cmp exp act
+}
+
+POST() {
+ curl --include --data "$2" \
+ --header "Content-Type: application/x-$1-request" \
+ "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null &&
+ tr '\015' Q <out |
+ sed '
+ s/Q$//
+ 1q
+ ' >act &&
+ echo "HTTP/1.1 $3" >exp &&
+ test_cmp exp act
+}
+
+log_div() {
+ echo >>"$HTTPD_ROOT_PATH"/access.log
+ echo "### $1" >>"$HTTPD_ROOT_PATH"/access.log
+ echo "###" >>"$HTTPD_ROOT_PATH"/access.log
+}
+
+. "$TEST_DIRECTORY"/t556x_common
+
+cat >exp <<EOF
+
+### refs/heads/master
+###
+GET /smart/repo.git/refs/heads/master HTTP/1.1 404 -
+
+### getanyfile default
+###
+GET /smart/repo.git/HEAD HTTP/1.1 200
+GET /smart/repo.git/info/refs HTTP/1.1 200
+GET /smart/repo.git/objects/info/packs HTTP/1.1 200
+GET /smart/repo.git/objects/info/alternates HTTP/1.1 200 -
+GET /smart/repo.git/objects/info/http-alternates HTTP/1.1 200 -
+GET /smart/repo.git/$LOOSE_URL HTTP/1.1 200
+GET /smart/repo.git/$PACK_URL HTTP/1.1 200
+GET /smart/repo.git/$IDX_URL HTTP/1.1 200
+
+### no git-daemon-export-ok
+###
+GET /smart_noexport/repo.git/HEAD HTTP/1.1 404 -
+GET /smart_noexport/repo.git/info/refs HTTP/1.1 404 -
+GET /smart_noexport/repo.git/objects/info/packs HTTP/1.1 404 -
+GET /smart_noexport/repo.git/objects/info/alternates HTTP/1.1 404 -
+GET /smart_noexport/repo.git/objects/info/http-alternates HTTP/1.1 404 -
+GET /smart_noexport/repo.git/$LOOSE_URL HTTP/1.1 404 -
+GET /smart_noexport/repo.git/$PACK_URL HTTP/1.1 404 -
+GET /smart_noexport/repo.git/$IDX_URL HTTP/1.1 404 -
+
+### git-daemon-export-ok
+###
+GET /smart_noexport/repo.git/HEAD HTTP/1.1 200
+GET /smart_noexport/repo.git/info/refs HTTP/1.1 200
+GET /smart_noexport/repo.git/objects/info/packs HTTP/1.1 200
+GET /smart_noexport/repo.git/objects/info/alternates HTTP/1.1 200 -
+GET /smart_noexport/repo.git/objects/info/http-alternates HTTP/1.1 200 -
+GET /smart_noexport/repo.git/$LOOSE_URL HTTP/1.1 200
+GET /smart_noexport/repo.git/$PACK_URL HTTP/1.1 200
+GET /smart_noexport/repo.git/$IDX_URL HTTP/1.1 200
+
+### getanyfile true
+###
+GET /smart/repo.git/HEAD HTTP/1.1 200
+GET /smart/repo.git/info/refs HTTP/1.1 200
+GET /smart/repo.git/objects/info/packs HTTP/1.1 200
+GET /smart/repo.git/objects/info/alternates HTTP/1.1 200 -
+GET /smart/repo.git/objects/info/http-alternates HTTP/1.1 200 -
+GET /smart/repo.git/$LOOSE_URL HTTP/1.1 200
+GET /smart/repo.git/$PACK_URL HTTP/1.1 200
+GET /smart/repo.git/$IDX_URL HTTP/1.1 200
+
+### getanyfile false
+###
+GET /smart/repo.git/HEAD HTTP/1.1 403 -
+GET /smart/repo.git/info/refs HTTP/1.1 403 -
+GET /smart/repo.git/objects/info/packs HTTP/1.1 403 -
+GET /smart/repo.git/objects/info/alternates HTTP/1.1 403 -
+GET /smart/repo.git/objects/info/http-alternates HTTP/1.1 403 -
+GET /smart/repo.git/$LOOSE_URL HTTP/1.1 403 -
+GET /smart/repo.git/$PACK_URL HTTP/1.1 403 -
+GET /smart/repo.git/$IDX_URL HTTP/1.1 403 -
+
+### uploadpack default
+###
+GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/repo.git/git-upload-pack HTTP/1.1 200 -
+
+### uploadpack true
+###
+GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/repo.git/git-upload-pack HTTP/1.1 200 -
+
+### uploadpack false
+###
+GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 403 -
+POST /smart/repo.git/git-upload-pack HTTP/1.1 403 -
+
+### receivepack default
+###
+GET /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 -
+POST /smart/repo.git/git-receive-pack HTTP/1.1 403 -
+
+### receivepack true
+###
+GET /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/repo.git/git-receive-pack HTTP/1.1 200 -
+
+### receivepack false
+###
+GET /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 -
+POST /smart/repo.git/git-receive-pack HTTP/1.1 403 -
+EOF
+test_expect_success 'server request log matches test results' '
+ sed -e "
+ s/^.* \"//
+ s/\"//
+ s/ [1-9][0-9]*\$//
+ s/^GET /GET /
+ " >act <"$HTTPD_ROOT_PATH"/access.log &&
+ test_cmp exp act
+'
+
+stop_httpd
+test_done
diff --git a/t/t556x_common b/t/t556x_common
new file mode 100755
index 000000000..be024e551
--- /dev/null
+++ b/t/t556x_common
@@ -0,0 +1,122 @@
+#!/bin/sh
+
+find_file() {
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ find $1 -type f |
+ sed -e 1q
+}
+
+config() {
+ git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" config $1 $2
+}
+
+test_expect_success 'setup repository' '
+ echo content >file &&
+ git add file &&
+ git commit -m one &&
+
+ mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git --bare init &&
+ : >objects/info/alternates &&
+ : >objects/info/http-alternates
+ ) &&
+ git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git push public master:master &&
+
+ (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git repack -a -d
+ ) &&
+
+ echo other >file &&
+ git add file &&
+ git commit -m two &&
+ git push public master:master &&
+
+ LOOSE_URL=$(find_file objects/??) &&
+ PACK_URL=$(find_file objects/pack/*.pack) &&
+ IDX_URL=$(find_file objects/pack/*.idx)
+'
+
+get_static_files() {
+ GET HEAD "$1" &&
+ GET info/refs "$1" &&
+ GET objects/info/packs "$1" &&
+ GET objects/info/alternates "$1" &&
+ GET objects/info/http-alternates "$1" &&
+ GET $LOOSE_URL "$1" &&
+ GET $PACK_URL "$1" &&
+ GET $IDX_URL "$1"
+}
+
+SMART=smart
+export GIT_HTTP_EXPORT_ALL=1
+test_expect_success 'direct refs/heads/master not found' '
+ log_div "refs/heads/master"
+ GET refs/heads/master "404 Not Found"
+'
+test_expect_success 'static file is ok' '
+ log_div "getanyfile default"
+ get_static_files "200 OK"
+'
+SMART=smart_noexport
+unset GIT_HTTP_EXPORT_ALL
+test_expect_success 'no export by default' '
+ log_div "no git-daemon-export-ok"
+ get_static_files "404 Not Found"
+'
+test_expect_success 'export if git-daemon-export-ok' '
+ log_div "git-daemon-export-ok"
+ (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ touch git-daemon-export-ok
+ ) &&
+ get_static_files "200 OK"
+'
+SMART=smart
+export GIT_HTTP_EXPORT_ALL=1
+test_expect_success 'static file if http.getanyfile true is ok' '
+ log_div "getanyfile true"
+ config http.getanyfile true &&
+ get_static_files "200 OK"
+'
+test_expect_success 'static file if http.getanyfile false fails' '
+ log_div "getanyfile false"
+ config http.getanyfile false &&
+ get_static_files "403 Forbidden"
+'
+
+test_expect_success 'http.uploadpack default enabled' '
+ log_div "uploadpack default"
+ GET info/refs?service=git-upload-pack "200 OK" &&
+ POST git-upload-pack 0000 "200 OK"
+'
+test_expect_success 'http.uploadpack true' '
+ log_div "uploadpack true"
+ config http.uploadpack true &&
+ GET info/refs?service=git-upload-pack "200 OK" &&
+ POST git-upload-pack 0000 "200 OK"
+'
+test_expect_success 'http.uploadpack false' '
+ log_div "uploadpack false"
+ config http.uploadpack false &&
+ GET info/refs?service=git-upload-pack "403 Forbidden" &&
+ POST git-upload-pack 0000 "403 Forbidden"
+'
+
+test_expect_success 'http.receivepack default disabled' '
+ log_div "receivepack default"
+ GET info/refs?service=git-receive-pack "403 Forbidden" &&
+ POST git-receive-pack 0000 "403 Forbidden"
+'
+test_expect_success 'http.receivepack true' '
+ log_div "receivepack true"
+ config http.receivepack true &&
+ GET info/refs?service=git-receive-pack "200 OK" &&
+ POST git-receive-pack 0000 "200 OK"
+'
+test_expect_success 'http.receivepack false' '
+ log_div "receivepack false"
+ config http.receivepack false &&
+ GET info/refs?service=git-receive-pack "403 Forbidden" &&
+ POST git-receive-pack 0000 "403 Forbidden"
+'
diff --git a/t/t5702-clone-options.sh b/t/t5702-clone-options.sh
index 27825f5f3..02cb02472 100755
--- a/t/t5702-clone-options.sh
+++ b/t/t5702-clone-options.sh
@@ -27,7 +27,8 @@ test_expect_success 'redirected clone' '
'
test_expect_success 'redirected clone -v' '
- git clone -v "file://$(pwd)/parent" clone-redirected-v >out 2>err &&
+ git clone --progress "file://$(pwd)/parent" clone-redirected-progress \
+ >out 2>err &&
test -s err
'
diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh
index abd14bf81..76c5e091b 100755
--- a/t/t7002-grep.sh
+++ b/t/t7002-grep.sh
@@ -8,6 +8,18 @@ test_description='git grep various.
. ./test-lib.sh
+test_expect_success 'Check for external grep support' '
+ case "$(git grep -h 2>&1|grep ext-grep)" in
+ *"(default)"*)
+ test_set_prereq EXTGREP
+ true;;
+ *"(ignored by this build)"*)
+ true;;
+ *)
+ false;;
+ esac
+'
+
cat >hello.c <<EOF
#include <stdio.h>
int main(int argc, const char **argv)
@@ -426,4 +438,16 @@ test_expect_success 'grep -Fi' '
test_cmp expected actual
'
+test_expect_success EXTGREP 'external grep is called' '
+ GIT_TRACE=2 git grep foo >/dev/null 2>actual &&
+ grep "trace: grep:.*foo" actual >/dev/null
+'
+
+test_expect_success EXTGREP 'no external grep when skip-worktree entries exist' '
+ git update-index --skip-worktree file &&
+ GIT_TRACE=2 git grep foo >/dev/null 2>actual &&
+ ! grep "trace: grep:" actual >/dev/null &&
+ git update-index --no-skip-worktree file
+'
+
test_done
diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh
new file mode 100755
index 000000000..bb4066f76
--- /dev/null
+++ b/t/t7011-skip-worktree-reading.sh
@@ -0,0 +1,163 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
+#
+
+test_description='skip-worktree bit test'
+
+. ./test-lib.sh
+
+cat >expect.full <<EOF
+H 1
+H 2
+H init.t
+H sub/1
+H sub/2
+EOF
+
+cat >expect.skip <<EOF
+S 1
+H 2
+H init.t
+S sub/1
+H sub/2
+EOF
+
+NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+ZERO_SHA0=0000000000000000000000000000000000000000
+setup_absent() {
+ test -f 1 && rm 1
+ git update-index --remove 1 &&
+ git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+ git update-index --skip-worktree 1
+}
+
+test_absent() {
+ echo "100644 $NULL_SHA1 0 1" > expected &&
+ git ls-files --stage 1 > result &&
+ test_cmp expected result &&
+ test ! -f 1
+}
+
+setup_dirty() {
+ git update-index --force-remove 1 &&
+ echo dirty > 1 &&
+ git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+ git update-index --skip-worktree 1
+}
+
+test_dirty() {
+ echo "100644 $NULL_SHA1 0 1" > expected &&
+ git ls-files --stage 1 > result &&
+ test_cmp expected result &&
+ echo dirty > expected
+ test_cmp expected 1
+}
+
+test_expect_success 'setup' '
+ test_commit init &&
+ mkdir sub &&
+ touch ./1 ./2 sub/1 sub/2 &&
+ git add 1 2 sub/1 sub/2 &&
+ git update-index --skip-worktree 1 sub/1 &&
+ git ls-files -t > result &&
+ test_cmp expect.skip result
+'
+
+test_expect_success 'update-index' '
+ setup_absent &&
+ git update-index 1 &&
+ test_absent
+'
+
+test_expect_success 'update-index' '
+ setup_dirty &&
+ git update-index 1 &&
+ test_dirty
+'
+
+test_expect_success 'update-index --remove' '
+ setup_absent &&
+ git update-index --remove 1 &&
+ test -z "$(git ls-files 1)" &&
+ test ! -f 1
+'
+
+test_expect_success 'update-index --remove' '
+ setup_dirty &&
+ git update-index --remove 1 &&
+ test -z "$(git ls-files 1)" &&
+ echo dirty > expected &&
+ test_cmp expected 1
+'
+
+test_expect_success 'ls-files --delete' '
+ setup_absent &&
+ test -z "$(git ls-files -d)"
+'
+
+test_expect_success 'ls-files --delete' '
+ setup_dirty &&
+ test -z "$(git ls-files -d)"
+'
+
+test_expect_success 'ls-files --modified' '
+ setup_absent &&
+ test -z "$(git ls-files -m)"
+'
+
+test_expect_success 'ls-files --modified' '
+ setup_dirty &&
+ test -z "$(git ls-files -m)"
+'
+
+test_expect_success 'grep with skip-worktree file' '
+ git update-index --no-skip-worktree 1 &&
+ echo test > 1 &&
+ git update-index 1 &&
+ git update-index --skip-worktree 1 &&
+ rm 1 &&
+ test "$(git grep --no-ext-grep test)" = "1:test"
+'
+
+echo ":000000 100644 $ZERO_SHA0 $NULL_SHA1 A 1" > expected
+test_expect_success 'diff-index does not examine skip-worktree absent entries' '
+ setup_absent &&
+ git diff-index HEAD -- 1 > result &&
+ test_cmp expected result
+'
+
+test_expect_success 'diff-index does not examine skip-worktree dirty entries' '
+ setup_dirty &&
+ git diff-index HEAD -- 1 > result &&
+ test_cmp expected result
+'
+
+test_expect_success 'diff-files does not examine skip-worktree absent entries' '
+ setup_absent &&
+ test -z "$(git diff-files -- one)"
+'
+
+test_expect_success 'diff-files does not examine skip-worktree dirty entries' '
+ setup_dirty &&
+ test -z "$(git diff-files -- one)"
+'
+
+test_expect_success 'git-rm succeeds on skip-worktree absent entries' '
+ setup_absent &&
+ git rm 1
+'
+
+test_expect_success 'commit on skip-worktree absent entries' '
+ git reset &&
+ setup_absent &&
+ test_must_fail git commit -m null 1
+'
+
+test_expect_success 'commit on skip-worktree dirty entries' '
+ git reset &&
+ setup_dirty &&
+ test_must_fail git commit -m null 1
+'
+
+test_done
diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh
new file mode 100755
index 000000000..8d8b1c0e2
--- /dev/null
+++ b/t/t7012-skip-worktree-writing.sh
@@ -0,0 +1,146 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
+#
+
+test_description='test worktree writing operations when skip-worktree is used'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit init &&
+ echo modified >> init.t &&
+ touch added &&
+ git add init.t added &&
+ git commit -m "modified and added" &&
+ git tag top
+'
+
+test_expect_success 'read-tree updates worktree, absent case' '
+ git checkout -f top &&
+ git update-index --skip-worktree init.t &&
+ rm init.t &&
+ git read-tree -m -u HEAD^ &&
+ echo init > expected &&
+ test_cmp expected init.t
+'
+
+test_expect_success 'read-tree updates worktree, dirty case' '
+ git checkout -f top &&
+ git update-index --skip-worktree init.t &&
+ echo dirty >> init.t &&
+ test_must_fail git read-tree -m -u HEAD^ &&
+ grep -q dirty init.t &&
+ test "$(git ls-files -t init.t)" = "S init.t" &&
+ git update-index --no-skip-worktree init.t
+'
+
+test_expect_success 'read-tree removes worktree, absent case' '
+ git checkout -f top &&
+ git update-index --skip-worktree added &&
+ rm added &&
+ git read-tree -m -u HEAD^ &&
+ test ! -f added
+'
+
+test_expect_success 'read-tree removes worktree, dirty case' '
+ git checkout -f top &&
+ git update-index --skip-worktree added &&
+ echo dirty >> added &&
+ test_must_fail git read-tree -m -u HEAD^ &&
+ grep -q dirty added &&
+ test "$(git ls-files -t added)" = "S added" &&
+ git update-index --no-skip-worktree added
+'
+
+NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+ZERO_SHA0=0000000000000000000000000000000000000000
+setup_absent() {
+ test -f 1 && rm 1
+ git update-index --remove 1 &&
+ git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+ git update-index --skip-worktree 1
+}
+
+test_absent() {
+ echo "100644 $NULL_SHA1 0 1" > expected &&
+ git ls-files --stage 1 > result &&
+ test_cmp expected result &&
+ test ! -f 1
+}
+
+setup_dirty() {
+ git update-index --force-remove 1 &&
+ echo dirty > 1 &&
+ git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+ git update-index --skip-worktree 1
+}
+
+test_dirty() {
+ echo "100644 $NULL_SHA1 0 1" > expected &&
+ git ls-files --stage 1 > result &&
+ test_cmp expected result &&
+ echo dirty > expected
+ test_cmp expected 1
+}
+
+cat >expected <<EOF
+S 1
+H 2
+H init.t
+S sub/1
+H sub/2
+EOF
+
+test_expect_success 'index setup' '
+ git checkout -f init &&
+ mkdir sub &&
+ touch ./1 ./2 sub/1 sub/2 &&
+ git add 1 2 sub/1 sub/2 &&
+ git update-index --skip-worktree 1 sub/1 &&
+ git ls-files -t > result &&
+ test_cmp expected result
+'
+
+test_expect_success 'git-add ignores worktree content' '
+ setup_absent &&
+ git add 1 &&
+ test_absent
+'
+
+test_expect_success 'git-add ignores worktree content' '
+ setup_dirty &&
+ git add 1 &&
+ test_dirty
+'
+
+test_expect_success 'git-rm fails if worktree is dirty' '
+ setup_dirty &&
+ test_must_fail git rm 1 &&
+ test_dirty
+'
+
+cat >expected <<EOF
+Would remove expected
+Would remove result
+EOF
+test_expect_success 'git-clean, absent case' '
+ setup_absent &&
+ git clean -n > result &&
+ test_cmp expected result
+'
+
+test_expect_success 'git-clean, dirty case' '
+ setup_dirty &&
+ git clean -n > result &&
+ test_cmp expected result
+'
+
+test_expect_failure 'git-apply adds file' false
+test_expect_failure 'git-apply updates file' false
+test_expect_failure 'git-apply removes file' false
+test_expect_failure 'git-mv to skip-worktree' false
+test_expect_failure 'git-mv from skip-worktree' false
+test_expect_failure 'git-checkout' false
+
+test_done
diff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh
new file mode 100755
index 000000000..8704d0019
--- /dev/null
+++ b/t/t7110-reset-merge.sh
@@ -0,0 +1,183 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Christian Couder
+#
+
+test_description='Tests for "git reset --merge"'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ for i in 1 2 3; do echo line $i; done >file1 &&
+ cat file1 >file2 &&
+ git add file1 file2 &&
+ test_tick &&
+ git commit -m "Initial commit" &&
+ git tag initial &&
+ echo line 4 >>file1 &&
+ cat file1 >file2 &&
+ test_tick &&
+ git commit -m "add line 4 to file1" file1 &&
+ git tag second
+'
+
+# The next test will test the following:
+#
+# working index HEAD target working index HEAD
+# ----------------------------------------------------
+# file1: C C C D --merge D D D
+# file2: C D D D --merge C D D
+test_expect_success 'reset --merge is ok with changes in file it does not touch' '
+ git reset --merge HEAD^ &&
+ ! grep 4 file1 &&
+ grep 4 file2 &&
+ test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+ test -z "$(git diff --cached)"
+'
+
+test_expect_success 'reset --merge is ok when switching back' '
+ git reset --merge second &&
+ grep 4 file1 &&
+ grep 4 file2 &&
+ test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+ test -z "$(git diff --cached)"
+'
+
+# The next test will test the following:
+#
+# working index HEAD target working index HEAD
+# ----------------------------------------------------
+# file1: B B C D --merge D D D
+# file2: C D D D --merge C D D
+test_expect_success 'reset --merge discards changes added to index (1)' '
+ git reset --hard second &&
+ cat file1 >file2 &&
+ echo "line 5" >> file1 &&
+ git add file1 &&
+ git reset --merge HEAD^ &&
+ ! grep 4 file1 &&
+ ! grep 5 file1 &&
+ grep 4 file2 &&
+ test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+ test -z "$(git diff --cached)"
+'
+
+test_expect_success 'reset --merge is ok again when switching back (1)' '
+ git reset --hard initial &&
+ echo "line 5" >> file2 &&
+ git add file2 &&
+ git reset --merge second &&
+ ! grep 4 file2 &&
+ ! grep 5 file1 &&
+ grep 4 file1 &&
+ test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+ test -z "$(git diff --cached)"
+'
+
+# The next test will test the following:
+#
+# working index HEAD target working index HEAD
+# ----------------------------------------------------
+# file1: C C C D --merge D D D
+# file2: C C D D --merge D D D
+test_expect_success 'reset --merge discards changes added to index (2)' '
+ git reset --hard second &&
+ echo "line 4" >> file2 &&
+ git add file2 &&
+ git reset --merge HEAD^ &&
+ ! grep 4 file2 &&
+ test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+ test -z "$(git diff)" &&
+ test -z "$(git diff --cached)"
+'
+
+test_expect_success 'reset --merge is ok again when switching back (2)' '
+ git reset --hard initial &&
+ git reset --merge second &&
+ ! grep 4 file2 &&
+ grep 4 file1 &&
+ test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+ test -z "$(git diff --cached)"
+'
+
+# The next test will test the following:
+#
+# working index HEAD target working index HEAD
+# ----------------------------------------------------
+# file1: A B B C --merge (disallowed)
+test_expect_success 'reset --merge fails with changes in file it touches' '
+ git reset --hard second &&
+ echo "line 5" >> file1 &&
+ test_tick &&
+ git commit -m "add line 5" file1 &&
+ sed -e "s/line 1/changed line 1/" <file1 >file3 &&
+ mv file3 file1 &&
+ test_must_fail git reset --merge HEAD^ 2>err.log &&
+ grep file1 err.log | grep "not uptodate"
+'
+
+test_expect_success 'setup 3 different branches' '
+ git reset --hard second &&
+ git branch branch1 &&
+ git branch branch2 &&
+ git branch branch3 &&
+ git checkout branch1 &&
+ echo "line 5 in branch1" >> file1 &&
+ test_tick &&
+ git commit -a -m "change in branch1" &&
+ git checkout branch2 &&
+ echo "line 5 in branch2" >> file1 &&
+ test_tick &&
+ git commit -a -m "change in branch2" &&
+ git tag third &&
+ git checkout branch3 &&
+ echo a new file >file3 &&
+ rm -f file1 &&
+ git add file3 &&
+ test_tick &&
+ git commit -a -m "change in branch3"
+'
+
+# The next test will test the following:
+#
+# working index HEAD target working index HEAD
+# ----------------------------------------------------
+# file1: X U B C --merge C C C
+test_expect_success '"reset --merge HEAD^" is ok with pending merge' '
+ git checkout third &&
+ test_must_fail git merge branch1 &&
+ git reset --merge HEAD^ &&
+ test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+ test -z "$(git diff --cached)" &&
+ test -z "$(git diff)"
+'
+
+# The next test will test the following:
+#
+# working index HEAD target working index HEAD
+# ----------------------------------------------------
+# file1: X U B B --merge B B B
+test_expect_success '"reset --merge HEAD" is ok with pending merge' '
+ git reset --hard third &&
+ test_must_fail git merge branch1 &&
+ git reset --merge HEAD &&
+ test "$(git rev-parse HEAD)" = "$(git rev-parse third)" &&
+ test -z "$(git diff --cached)" &&
+ test -z "$(git diff)"
+'
+
+test_expect_success '--merge with added/deleted' '
+ git reset --hard third &&
+ rm -f file2 &&
+ test_must_fail git merge branch3 &&
+ ! test -f file2 &&
+ test -f file3 &&
+ git diff --exit-code file3 &&
+ git diff --exit-code branch3 file3 &&
+ git reset --merge HEAD &&
+ ! test -f file3 &&
+ ! test -f file2 &&
+ git diff --exit-code --cached
+'
+
+test_done
diff --git a/t/t7111-reset-table.sh b/t/t7111-reset-table.sh
new file mode 100755
index 000000000..de896c948
--- /dev/null
+++ b/t/t7111-reset-table.sh
@@ -0,0 +1,113 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Christian Couder
+#
+
+test_description='Tests to check that "reset" options follow a known table'
+
+. ./test-lib.sh
+
+
+test_expect_success 'creating initial commits' '
+ test_commit E file1 &&
+ test_commit D file1 &&
+ test_commit C file1
+'
+
+while read W1 I1 H1 T opt W2 I2 H2
+do
+ test_expect_success "check: $W1 $I1 $H1 $T --$opt $W2 $I2 $H2" '
+ git reset --hard C &&
+ if test "$I1" != "$H1"
+ then
+ echo "$I1" >file1 &&
+ git add file1
+ fi &&
+ if test "$W1" != "$I1"
+ then
+ echo "$W1" >file1
+ fi &&
+ if test "$W2" != "XXXXX"
+ then
+ git reset --$opt $T &&
+ test "$(cat file1)" = "$W2" &&
+ git checkout-index -f -- file1 &&
+ test "$(cat file1)" = "$I2" &&
+ git checkout -f HEAD -- file1 &&
+ test "$(cat file1)" = "$H2"
+ else
+ test_must_fail git reset --$opt $T
+ fi
+ '
+done <<\EOF
+A B C D soft A B D
+A B C D mixed A D D
+A B C D hard D D D
+A B C D merge XXXXX
+A B C C soft A B C
+A B C C mixed A C C
+A B C C hard C C C
+A B C C merge XXXXX
+B B C D soft B B D
+B B C D mixed B D D
+B B C D hard D D D
+B B C D merge D D D
+B B C C soft B B C
+B B C C mixed B C C
+B B C C hard C C C
+B B C C merge C C C
+B C C D soft B C D
+B C C D mixed B D D
+B C C D hard D D D
+B C C D merge XXXXX
+B C C C soft B C C
+B C C C mixed B C C
+B C C C hard C C C
+B C C C merge B C C
+EOF
+
+test_expect_success 'setting up branches to test with unmerged entries' '
+ git reset --hard C &&
+ git branch branch1 &&
+ git branch branch2 &&
+ git checkout branch1 &&
+ test_commit B1 file1 &&
+ git checkout branch2 &&
+ test_commit B file1
+'
+
+while read W1 I1 H1 T opt W2 I2 H2
+do
+ test_expect_success "check: $W1 $I1 $H1 $T --$opt $W2 $I2 $H2" '
+ git reset --hard B &&
+ test_must_fail git merge branch1 &&
+ cat file1 >X_file1 &&
+ if test "$W2" != "XXXXX"
+ then
+ git reset --$opt $T &&
+ if test "$W2" = "X"
+ then
+ test_cmp file1 X_file1
+ else
+ test "$(cat file1)" = "$W2"
+ fi &&
+ git checkout-index -f -- file1 &&
+ test "$(cat file1)" = "$I2" &&
+ git checkout -f HEAD -- file1 &&
+ test "$(cat file1)" = "$H2"
+ else
+ test_must_fail git reset --$opt $T
+ fi
+ '
+done <<\EOF
+X U B C soft XXXXX
+X U B C mixed X C C
+X U B C hard C C C
+X U B C merge C C C
+X U B B soft XXXXX
+X U B B mixed X B B
+X U B B hard B B B
+X U B B merge B B B
+EOF
+
+test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index ebfd34df3..6442f710b 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -542,4 +542,61 @@ test_expect_success 'switch out of non-branch' '
! grep "^Previous HEAD" error.log
'
+(
+ echo "#!$SHELL_PATH"
+ cat <<\EOF
+O=$1 A=$2 B=$3
+cat "$A" >.tmp
+exec >"$A"
+echo '<<<<<<< filfre-theirs'
+cat "$B"
+echo '||||||| filfre-common'
+cat "$O"
+echo '======='
+cat ".tmp"
+echo '>>>>>>> filfre-ours'
+rm -f .tmp
+exit 1
+EOF
+) >filfre.sh
+chmod +x filfre.sh
+
+test_expect_success 'custom merge driver with checkout -m' '
+ git reset --hard &&
+
+ git config merge.filfre.driver "./filfre.sh %O %A %B" &&
+ git config merge.filfre.name "Feel-free merge driver" &&
+ git config merge.filfre.recursive binary &&
+ echo "arm merge=filfre" >.gitattributes &&
+
+ git checkout -b left &&
+ echo neutral >arm &&
+ git add arm .gitattributes &&
+ test_tick &&
+ git commit -m neutral &&
+ git branch right &&
+
+ echo left >arm &&
+ test_tick &&
+ git commit -a -m left &&
+ git checkout right &&
+
+ echo right >arm &&
+ test_tick &&
+ git commit -a -m right &&
+
+ test_must_fail git merge left &&
+ (
+ for t in filfre-common left right
+ do
+ grep $t arm || exit 1
+ done
+ exit 0
+ ) &&
+
+ mv arm expect &&
+ git checkout -m arm &&
+ test_cmp expect arm
+'
+
test_done
diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh
index 118c6ebb1..7d8ed68be 100755
--- a/t/t7300-clean.sh
+++ b/t/t7300-clean.sh
@@ -22,6 +22,25 @@ test_expect_success 'setup' '
'
+test_expect_success 'git clean with skip-worktree .gitignore' '
+ git update-index --skip-worktree .gitignore &&
+ rm .gitignore &&
+ mkdir -p build docs &&
+ touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+ git clean &&
+ test -f Makefile &&
+ test -f README &&
+ test -f src/part1.c &&
+ test -f src/part2.c &&
+ test ! -f a.out &&
+ test ! -f src/part3.c &&
+ test -f docs/manual.txt &&
+ test -f obj.o &&
+ test -f build/lib.so &&
+ git update-index --no-skip-worktree .gitignore &&
+ git checkout .gitignore
+'
+
test_expect_success 'git clean' '
mkdir -p build docs &&
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index a0cc99ab9..1a4dc5f89 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -299,6 +299,15 @@ test_expect_success 'ls-files gracefully handles trailing slash' '
'
+test_expect_success 'moving to a commit without submodule does not leave empty dir' '
+ rm -rf init &&
+ mkdir init &&
+ git reset --hard &&
+ git checkout initial &&
+ test ! -d init &&
+ git checkout second
+'
+
test_expect_success 'submodule <invalid-path> warns' '
git submodule no-such-submodule 2> output.err &&
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
index fe9455229..844fb43c6 100755
--- a/t/t7502-commit.sh
+++ b/t/t7502-commit.sh
@@ -267,4 +267,113 @@ test_expect_success 'A single-liner subject with a token plus colon is not a foo
'
+cat >.git/FAKE_EDITOR <<EOF
+#!$SHELL_PATH
+mv "\$1" "\$1.orig"
+(
+ echo message
+ cat "\$1.orig"
+) >"\$1"
+EOF
+
+echo '## Custom template' >template
+
+clear_config () {
+ (
+ git config --unset-all "$1"
+ case $? in
+ 0|5) exit 0 ;;
+ *) exit 1 ;;
+ esac
+ )
+}
+
+try_commit () {
+ git reset --hard &&
+ echo >>negative &&
+ GIT_EDITOR=.git/FAKE_EDITOR git commit -a $* $use_template &&
+ case "$use_template" in
+ '')
+ ! grep "^## Custom template" .git/COMMIT_EDITMSG ;;
+ *)
+ grep "^## Custom template" .git/COMMIT_EDITMSG ;;
+ esac
+}
+
+try_commit_status_combo () {
+
+ test_expect_success 'commit' '
+ clear_config commit.status &&
+ try_commit "" &&
+ grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit' '
+ clear_config commit.status &&
+ try_commit "" &&
+ grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit --status' '
+ clear_config commit.status &&
+ try_commit --status &&
+ grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit --no-status' '
+ clear_config commit.status &&
+ try_commit --no-status
+ ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit with commit.status = yes' '
+ clear_config commit.status &&
+ git config commit.status yes &&
+ try_commit "" &&
+ grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit with commit.status = no' '
+ clear_config commit.status &&
+ git config commit.status no &&
+ try_commit "" &&
+ ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit --status with commit.status = yes' '
+ clear_config commit.status &&
+ git config commit.status yes &&
+ try_commit --status &&
+ grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit --no-status with commit.status = yes' '
+ clear_config commit.status &&
+ git config commit.status yes &&
+ try_commit --no-status &&
+ ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit --status with commit.status = no' '
+ clear_config commit.status &&
+ git config commit.status no &&
+ try_commit --status &&
+ grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+ test_expect_success 'commit --no-status with commit.status = no' '
+ clear_config commit.status &&
+ git config commit.status no &&
+ try_commit --no-status &&
+ ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+ '
+
+}
+
+try_commit_status_combo
+
+use_template="-t template"
+
+try_commit_status_combo
+
test_done
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
index b49815d10..a1b8c2bb9 100755
--- a/t/t9300-fast-import.sh
+++ b/t/t9300-fast-import.sh
@@ -1254,4 +1254,156 @@ test_expect_success \
'Q: verify note for third commit' \
'git cat-file blob refs/notes/foobar:$commit3 >actual && test_cmp expect actual'
+###
+### series R (feature and option)
+###
+
+cat >input <<EOF
+feature no-such-feature-exists
+EOF
+
+test_expect_success 'R: abort on unsupported feature' '
+ test_must_fail git fast-import <input
+'
+
+cat >input <<EOF
+feature date-format=now
+EOF
+
+test_expect_success 'R: supported feature is accepted' '
+ git fast-import <input
+'
+
+cat >input << EOF
+blob
+data 3
+hi
+feature date-format=now
+EOF
+
+test_expect_success 'R: abort on receiving feature after data command' '
+ test_must_fail git fast-import <input
+'
+
+cat >input << EOF
+feature import-marks=git.marks
+feature import-marks=git2.marks
+EOF
+
+test_expect_success 'R: only one import-marks feature allowed per stream' '
+ test_must_fail git fast-import <input
+'
+
+cat >input << EOF
+feature export-marks=git.marks
+blob
+mark :1
+data 3
+hi
+
+EOF
+
+test_expect_success \
+ 'R: export-marks feature results in a marks file being created' \
+ 'cat input | git fast-import &&
+ grep :1 git.marks'
+
+test_expect_success \
+ 'R: export-marks options can be overriden by commandline options' \
+ 'cat input | git fast-import --export-marks=other.marks &&
+ grep :1 other.marks'
+
+cat >input << EOF
+feature import-marks=marks.out
+feature export-marks=marks.new
+EOF
+
+test_expect_success \
+ 'R: import to output marks works without any content' \
+ 'cat input | git fast-import &&
+ test_cmp marks.out marks.new'
+
+cat >input <<EOF
+feature import-marks=nonexistant.marks
+feature export-marks=marks.new
+EOF
+
+test_expect_success \
+ 'R: import marks prefers commandline marks file over the stream' \
+ 'cat input | git fast-import --import-marks=marks.out &&
+ test_cmp marks.out marks.new'
+
+
+cat >input <<EOF
+feature import-marks=nonexistant.marks
+feature export-marks=combined.marks
+EOF
+
+test_expect_success 'R: multiple --import-marks= should be honoured' '
+ head -n2 marks.out > one.marks &&
+ tail -n +3 marks.out > two.marks &&
+ git fast-import --import-marks=one.marks --import-marks=two.marks <input &&
+ test_cmp marks.out combined.marks
+'
+
+cat >input <<EOF
+feature relative-marks
+feature import-marks=relative.in
+feature export-marks=relative.out
+EOF
+
+test_expect_success 'R: feature relative-marks should be honoured' '
+ mkdir -p .git/info/fast-import/ &&
+ cp marks.new .git/info/fast-import/relative.in &&
+ git fast-import <input &&
+ test_cmp marks.new .git/info/fast-import/relative.out
+'
+
+cat >input <<EOF
+feature relative-marks
+feature import-marks=relative.in
+feature no-relative-marks
+feature export-marks=non-relative.out
+EOF
+
+test_expect_success 'R: feature no-relative-marks should be honoured' '
+ git fast-import <input &&
+ test_cmp marks.new non-relative.out
+'
+
+cat >input << EOF
+option git quiet
+blob
+data 3
+hi
+
+EOF
+
+touch empty
+
+test_expect_success 'R: quiet option results in no stats being output' '
+ cat input | git fast-import 2> output &&
+ test_cmp empty output
+'
+
+cat >input <<EOF
+option git non-existing-option
+EOF
+
+test_expect_success 'R: die on unknown option' '
+ test_must_fail git fast-import <input
+'
+
+test_expect_success 'R: unknown commandline options are rejected' '\
+ test_must_fail git fast-import --non-existing-option < /dev/null
+'
+
+cat >input <<EOF
+option non-existing-vcs non-existing-option
+EOF
+
+test_expect_success 'R: ignore non-git options' '
+ git fast-import <input
+'
+
test_done
diff --git a/test-index-version.c b/test-index-version.c
new file mode 100644
index 000000000..bfaad9e09
--- /dev/null
+++ b/test-index-version.c
@@ -0,0 +1,14 @@
+#include "cache.h"
+
+int main(int argc, const char **argv)
+{
+ struct cache_header hdr;
+ int version;
+
+ memset(&hdr,0,sizeof(hdr));
+ if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr))
+ return 0;
+ version = ntohl(hdr.hdr_version);
+ printf("%d\n", version);
+ return 0;
+}
diff --git a/transport-helper.c b/transport-helper.c
index 11f3d7ec5..ca8fa92e6 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -8,6 +8,8 @@
#include "quote.h"
#include "remote.h"
+static int debug;
+
struct helper_data
{
const char *name;
@@ -16,12 +18,81 @@ struct helper_data
unsigned fetch : 1,
import : 1,
option : 1,
- push : 1;
+ push : 1,
+ connect : 1,
+ no_disconnect_req : 1;
/* These go from remote name (as in "list") to private name */
struct refspec *refspecs;
int refspec_nr;
+ /* Transport options for fetch-pack/send-pack (should one of
+ * those be invoked).
+ */
+ struct git_transport_options transport_options;
};
+static void sendline(struct helper_data *helper, struct strbuf *buffer)
+{
+ if (debug)
+ fprintf(stderr, "Debug: Remote helper: -> %s", buffer->buf);
+ if (write_in_full(helper->helper->in, buffer->buf, buffer->len)
+ != buffer->len)
+ die_errno("Full write to remote helper failed");
+}
+
+static int recvline_fh(FILE *helper, struct strbuf *buffer)
+{
+ strbuf_reset(buffer);
+ if (debug)
+ fprintf(stderr, "Debug: Remote helper: Waiting...\n");
+ if (strbuf_getline(buffer, helper, '\n') == EOF) {
+ if (debug)
+ fprintf(stderr, "Debug: Remote helper quit.\n");
+ exit(128);
+ }
+
+ if (debug)
+ fprintf(stderr, "Debug: Remote helper: <- %s\n", buffer->buf);
+ return 0;
+}
+
+static int recvline(struct helper_data *helper, struct strbuf *buffer)
+{
+ return recvline_fh(helper->out, buffer);
+}
+
+static void xchgline(struct helper_data *helper, struct strbuf *buffer)
+{
+ sendline(helper, buffer);
+ recvline(helper, buffer);
+}
+
+static void write_constant(int fd, const char *str)
+{
+ if (debug)
+ fprintf(stderr, "Debug: Remote helper: -> %s", str);
+ if (write_in_full(fd, str, strlen(str)) != strlen(str))
+ die_errno("Full write to remote helper failed");
+}
+
+const char *remove_ext_force(const char *url)
+{
+ if (url) {
+ const char *colon = strchr(url, ':');
+ if (colon && colon[1] == ':')
+ return colon + 2;
+ }
+ return url;
+}
+
+static void do_take_over(struct transport *transport)
+{
+ struct helper_data *data;
+ data = (struct helper_data *)transport->data;
+ transport_take_over(transport, data->helper);
+ fclose(data->out);
+ free(data);
+}
+
static struct child_process *get_helper(struct transport *transport)
{
struct helper_data *data = transport->data;
@@ -30,6 +101,7 @@ static struct child_process *get_helper(struct transport *transport)
const char **refspecs = NULL;
int refspec_nr = 0;
int refspec_alloc = 0;
+ int duped;
if (data->helper)
return data->helper;
@@ -42,34 +114,60 @@ static struct child_process *get_helper(struct transport *transport)
strbuf_addf(&buf, "remote-%s", data->name);
helper->argv[0] = strbuf_detach(&buf, NULL);
helper->argv[1] = transport->remote->name;
- helper->argv[2] = transport->url;
+ helper->argv[2] = remove_ext_force(transport->url);
helper->git_cmd = 1;
if (start_command(helper))
die("Unable to run helper: git %s", helper->argv[0]);
data->helper = helper;
+ data->no_disconnect_req = 0;
+
+ /*
+ * Open the output as FILE* so strbuf_getline() can be used.
+ * Do this with duped fd because fclose() will close the fd,
+ * and stuff like taking over will require the fd to remain.
+ */
+ duped = dup(helper->out);
+ if (duped < 0)
+ die_errno("Can't dup helper output fd");
+ data->out = xfdopen(duped, "r");
- write_str_in_full(helper->in, "capabilities\n");
+ write_constant(helper->in, "capabilities\n");
- data->out = xfdopen(helper->out, "r");
while (1) {
- if (strbuf_getline(&buf, data->out, '\n') == EOF)
- exit(128); /* child died, message supplied already */
+ const char *capname;
+ int mandatory = 0;
+ recvline(data, &buf);
if (!*buf.buf)
break;
- if (!strcmp(buf.buf, "fetch"))
+
+ if (*buf.buf == '*') {
+ capname = buf.buf + 1;
+ mandatory = 1;
+ } else
+ capname = buf.buf;
+
+ if (debug)
+ fprintf(stderr, "Debug: Got cap %s\n", capname);
+ if (!strcmp(capname, "fetch"))
data->fetch = 1;
- if (!strcmp(buf.buf, "option"))
+ else if (!strcmp(capname, "option"))
data->option = 1;
- if (!strcmp(buf.buf, "push"))
+ else if (!strcmp(capname, "push"))
data->push = 1;
- if (!strcmp(buf.buf, "import"))
+ else if (!strcmp(capname, "import"))
data->import = 1;
- if (!data->refspecs && !prefixcmp(buf.buf, "refspec ")) {
+ else if (!data->refspecs && !prefixcmp(capname, "refspec ")) {
ALLOC_GROW(refspecs,
refspec_nr + 1,
refspec_alloc);
refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec "));
+ } else if (!strcmp(capname, "connect")) {
+ data->connect = 1;
+ } else if (mandatory) {
+ die("Unknown madatory capability %s. This remote "
+ "helper probably needs newer version of Git.\n",
+ capname);
}
}
if (refspecs) {
@@ -82,15 +180,25 @@ static struct child_process *get_helper(struct transport *transport)
free(refspecs);
}
strbuf_release(&buf);
+ if (debug)
+ fprintf(stderr, "Debug: Capabilities complete.\n");
return data->helper;
}
static int disconnect_helper(struct transport *transport)
{
struct helper_data *data = transport->data;
+ struct strbuf buf = STRBUF_INIT;
+
if (data->helper) {
- write_str_in_full(data->helper->in, "\n");
+ if (debug)
+ fprintf(stderr, "Debug: Disconnecting.\n");
+ if (!data->no_disconnect_req) {
+ strbuf_addf(&buf, "\n");
+ sendline(data, &buf);
+ }
close(data->helper->in);
+ close(data->helper->out);
fclose(data->out);
finish_command(data->helper);
free((char *)data->helper->argv[0]);
@@ -117,10 +225,11 @@ static int set_helper_option(struct transport *transport,
const char *name, const char *value)
{
struct helper_data *data = transport->data;
- struct child_process *helper = get_helper(transport);
struct strbuf buf = STRBUF_INIT;
int i, ret, is_bool = 0;
+ get_helper(transport);
+
if (!data->option)
return 1;
@@ -143,12 +252,7 @@ static int set_helper_option(struct transport *transport,
quote_c_style(value, &buf, NULL, 0);
strbuf_addch(&buf, '\n');
- if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
- die_errno("cannot send option to %s", data->name);
-
- strbuf_reset(&buf);
- if (strbuf_getline(&buf, data->out, '\n') == EOF)
- exit(128); /* child died, message supplied already */
+ xchgline(data, &buf);
if (!strcmp(buf.buf, "ok"))
ret = 0;
@@ -169,7 +273,7 @@ static void standard_options(struct transport *t)
char buf[16];
int n;
int v = t->verbose;
- int no_progress = v < 0 || (!t->progress && !isatty(1));
+ int no_progress = v < 0 || (!t->progress && !isatty(2));
set_helper_option(t, "progress", !no_progress ? "true" : "false");
@@ -208,13 +312,10 @@ static int fetch_with_fetch(struct transport *transport,
}
strbuf_addch(&buf, '\n');
- if (write_in_full(data->helper->in, buf.buf, buf.len) != buf.len)
- die_errno("cannot send fetch to %s", data->name);
+ sendline(data, &buf);
while (1) {
- strbuf_reset(&buf);
- if (strbuf_getline(&buf, data->out, '\n') == EOF)
- exit(128); /* child died, message supplied already */
+ recvline(data, &buf);
if (!prefixcmp(buf.buf, "lock ")) {
const char *name = buf.buf + 5;
@@ -249,12 +350,13 @@ static int fetch_with_import(struct transport *transport,
int nr_heads, struct ref **to_fetch)
{
struct child_process fastimport;
- struct child_process *helper = get_helper(transport);
struct helper_data *data = transport->data;
int i;
struct ref *posn;
struct strbuf buf = STRBUF_INIT;
+ get_helper(transport);
+
if (get_importer(transport, &fastimport))
die("Couldn't run fast-import");
@@ -264,7 +366,7 @@ static int fetch_with_import(struct transport *transport,
continue;
strbuf_addf(&buf, "import %s\n", posn->name);
- write_in_full(helper->in, buf.buf, buf.len);
+ sendline(data, &buf);
strbuf_reset(&buf);
}
disconnect_helper(transport);
@@ -288,12 +390,112 @@ static int fetch_with_import(struct transport *transport,
return 0;
}
+static int process_connect_service(struct transport *transport,
+ const char *name, const char *exec)
+{
+ struct helper_data *data = transport->data;
+ struct strbuf cmdbuf = STRBUF_INIT;
+ struct child_process *helper;
+ int r, duped, ret = 0;
+ FILE *input;
+
+ helper = get_helper(transport);
+
+ /*
+ * Yes, dup the pipe another time, as we need unbuffered version
+ * of input pipe as FILE*. fclose() closes the underlying fd and
+ * stream buffering only can be changed before first I/O operation
+ * on it.
+ */
+ duped = dup(helper->out);
+ if (duped < 0)
+ die_errno("Can't dup helper output fd");
+ input = xfdopen(duped, "r");
+ setvbuf(input, NULL, _IONBF, 0);
+
+ /*
+ * Handle --upload-pack and friends. This is fire and forget...
+ * just warn if it fails.
+ */
+ if (strcmp(name, exec)) {
+ r = set_helper_option(transport, "servpath", exec);
+ if (r > 0)
+ warning("Setting remote service path not supported by protocol.");
+ else if (r < 0)
+ warning("Invalid remote service path.");
+ }
+
+ if (data->connect)
+ strbuf_addf(&cmdbuf, "connect %s\n", name);
+ else
+ goto exit;
+
+ sendline(data, &cmdbuf);
+ recvline_fh(input, &cmdbuf);
+ if (!strcmp(cmdbuf.buf, "")) {
+ data->no_disconnect_req = 1;
+ if (debug)
+ fprintf(stderr, "Debug: Smart transport connection "
+ "ready.\n");
+ ret = 1;
+ } else if (!strcmp(cmdbuf.buf, "fallback")) {
+ if (debug)
+ fprintf(stderr, "Debug: Falling back to dumb "
+ "transport.\n");
+ } else
+ die("Unknown response to connect: %s",
+ cmdbuf.buf);
+
+exit:
+ fclose(input);
+ return ret;
+}
+
+static int process_connect(struct transport *transport,
+ int for_push)
+{
+ struct helper_data *data = transport->data;
+ const char *name;
+ const char *exec;
+
+ name = for_push ? "git-receive-pack" : "git-upload-pack";
+ if (for_push)
+ exec = data->transport_options.receivepack;
+ else
+ exec = data->transport_options.uploadpack;
+
+ return process_connect_service(transport, name, exec);
+}
+
+static int connect_helper(struct transport *transport, const char *name,
+ const char *exec, int fd[2])
+{
+ struct helper_data *data = transport->data;
+
+ /* Get_helper so connect is inited. */
+ get_helper(transport);
+ if (!data->connect)
+ die("Operation not supported by protocol.");
+
+ if (!process_connect_service(transport, name, exec))
+ die("Can't connect to subservice %s.", name);
+
+ fd[0] = data->helper->out;
+ fd[1] = data->helper->in;
+ return 0;
+}
+
static int fetch(struct transport *transport,
int nr_heads, struct ref **to_fetch)
{
struct helper_data *data = transport->data;
int i, count;
+ if (process_connect(transport, 0)) {
+ do_take_over(transport);
+ return transport->fetch(transport, nr_heads, to_fetch);
+ }
+
count = 0;
for (i = 0; i < nr_heads; i++)
if (!(to_fetch[i]->status & REF_STATUS_UPTODATE))
@@ -321,6 +523,11 @@ static int push_refs(struct transport *transport,
struct child_process *helper;
struct ref *ref;
+ if (process_connect(transport, 1)) {
+ do_take_over(transport);
+ return transport->push_refs(transport, remote_refs, flags);
+ }
+
if (!remote_refs)
return 0;
@@ -369,17 +576,14 @@ static int push_refs(struct transport *transport,
}
strbuf_addch(&buf, '\n');
- if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
- exit(128);
+ sendline(data, &buf);
ref = remote_refs;
while (1) {
char *refname, *msg;
int status;
- strbuf_reset(&buf);
- if (strbuf_getline(&buf, data->out, '\n') == EOF)
- exit(128); /* child died, message supplied already */
+ recvline(data, &buf);
if (!buf.len)
break;
@@ -464,6 +668,11 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
helper = get_helper(transport);
+ if (process_connect(transport, for_push)) {
+ do_take_over(transport);
+ return transport->get_refs_list(transport, for_push);
+ }
+
if (data->push && for_push)
write_str_in_full(helper->in, "list for-push\n");
else
@@ -471,8 +680,7 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
while (1) {
char *eov, *eon;
- if (strbuf_getline(&buf, data->out, '\n') == EOF)
- exit(128); /* child died, message supplied already */
+ recvline(data, &buf);
if (!*buf.buf)
break;
@@ -497,6 +705,8 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
}
tail = &((*tail)->next);
}
+ if (debug)
+ fprintf(stderr, "Debug: Read ref listing.\n");
strbuf_release(&buf);
for (posn = ret; posn; posn = posn->next)
@@ -510,11 +720,16 @@ int transport_helper_init(struct transport *transport, const char *name)
struct helper_data *data = xcalloc(sizeof(*data), 1);
data->name = name;
+ if (getenv("GIT_TRANSPORT_HELPER_DEBUG"))
+ debug = 1;
+
transport->data = data;
transport->set_option = set_helper_option;
transport->get_refs_list = get_refs_list;
transport->fetch = fetch;
transport->push_refs = push_refs;
transport->disconnect = release_helper;
+ transport->connect = connect_helper;
+ transport->smart_options = &(data->transport_options);
return 0;
}
diff --git a/transport.c b/transport.c
index 3eea836a3..c3f156ea0 100644
--- a/transport.c
+++ b/transport.c
@@ -143,7 +143,7 @@ static const char *rsync_url(const char *url)
static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
{
struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;
- struct ref dummy, *tail = &dummy;
+ struct ref dummy = {0}, *tail = &dummy;
struct child_process rsync;
const char *args[5];
int temp_dir_len;
@@ -395,41 +395,36 @@ static int close_bundle(struct transport *transport)
}
struct git_transport_data {
- unsigned thin : 1;
- unsigned keep : 1;
- unsigned followtags : 1;
- int depth;
+ struct git_transport_options options;
struct child_process *conn;
int fd[2];
- const char *uploadpack;
- const char *receivepack;
+ unsigned got_remote_heads : 1;
struct extra_have_objects extra_have;
};
-static int set_git_option(struct transport *connection,
+static int set_git_option(struct git_transport_options *opts,
const char *name, const char *value)
{
- struct git_transport_data *data = connection->data;
if (!strcmp(name, TRANS_OPT_UPLOADPACK)) {
- data->uploadpack = value;
+ opts->uploadpack = value;
return 0;
} else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) {
- data->receivepack = value;
+ opts->receivepack = value;
return 0;
} else if (!strcmp(name, TRANS_OPT_THIN)) {
- data->thin = !!value;
+ opts->thin = !!value;
return 0;
} else if (!strcmp(name, TRANS_OPT_FOLLOWTAGS)) {
- data->followtags = !!value;
+ opts->followtags = !!value;
return 0;
} else if (!strcmp(name, TRANS_OPT_KEEP)) {
- data->keep = !!value;
+ opts->keep = !!value;
return 0;
} else if (!strcmp(name, TRANS_OPT_DEPTH)) {
if (!value)
- data->depth = 0;
+ opts->depth = 0;
else
- data->depth = atoi(value);
+ opts->depth = atoi(value);
return 0;
}
return 1;
@@ -438,9 +433,15 @@ static int set_git_option(struct transport *connection,
static int connect_setup(struct transport *transport, int for_push, int verbose)
{
struct git_transport_data *data = transport->data;
+
+ if (data->conn)
+ return 0;
+
data->conn = git_connect(data->fd, transport->url,
- for_push ? data->receivepack : data->uploadpack,
+ for_push ? data->options.receivepack :
+ data->options.uploadpack,
verbose ? CONNECT_VERBOSE : 0);
+
return 0;
}
@@ -452,6 +453,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
connect_setup(transport, for_push, 0);
get_remote_heads(data->fd[0], &refs, 0, NULL,
for_push ? REF_NORMAL : 0, &data->extra_have);
+ data->got_remote_heads = 1;
return refs;
}
@@ -469,22 +471,23 @@ static int fetch_refs_via_pack(struct transport *transport,
struct ref *refs_tmp = NULL;
memset(&args, 0, sizeof(args));
- args.uploadpack = data->uploadpack;
- args.keep_pack = data->keep;
+ args.uploadpack = data->options.uploadpack;
+ args.keep_pack = data->options.keep;
args.lock_pack = 1;
- args.use_thin_pack = data->thin;
- args.include_tag = data->followtags;
+ args.use_thin_pack = data->options.thin;
+ args.include_tag = data->options.followtags;
args.verbose = (transport->verbose > 0);
args.quiet = (transport->verbose < 0);
- args.no_progress = args.quiet || (!transport->progress && !isatty(1));
- args.depth = data->depth;
+ args.no_progress = args.quiet || (!transport->progress && !isatty(2));
+ args.depth = data->options.depth;
for (i = 0; i < nr_heads; i++)
origh[i] = heads[i] = xstrdup(to_fetch[i]->name);
- if (!data->conn) {
+ if (!data->got_remote_heads) {
connect_setup(transport, 0, 0);
get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);
+ data->got_remote_heads = 1;
}
refs = fetch_pack(&args, data->fd, data->conn,
@@ -495,6 +498,7 @@ static int fetch_refs_via_pack(struct transport *transport,
if (finish_connect(data->conn))
refs = NULL;
data->conn = NULL;
+ data->got_remote_heads = 0;
free_refs(refs_tmp);
@@ -723,18 +727,19 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
struct send_pack_args args;
int ret;
- if (!data->conn) {
+ if (!data->got_remote_heads) {
struct ref *tmp_refs;
connect_setup(transport, 1, 0);
get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL,
NULL);
+ data->got_remote_heads = 1;
}
memset(&args, 0, sizeof(args));
args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
- args.use_thin_pack = data->thin;
+ args.use_thin_pack = data->options.thin;
args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
args.quiet = !!(flags & TRANSPORT_PUSH_QUIET);
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
@@ -746,15 +751,28 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
close(data->fd[0]);
ret |= finish_connect(data->conn);
data->conn = NULL;
+ data->got_remote_heads = 0;
return ret;
}
+static int connect_git(struct transport *transport, const char *name,
+ const char *executable, int fd[2])
+{
+ struct git_transport_data *data = transport->data;
+ data->conn = git_connect(data->fd, transport->url,
+ executable, 0);
+ fd[0] = data->fd[0];
+ fd[1] = data->fd[1];
+ return 0;
+}
+
static int disconnect_git(struct transport *transport)
{
struct git_transport_data *data = transport->data;
if (data->conn) {
- packet_flush(data->fd[1]);
+ if (data->got_remote_heads)
+ packet_flush(data->fd[1]);
close(data->fd[0]);
close(data->fd[1]);
finish_connect(data->conn);
@@ -764,6 +782,32 @@ static int disconnect_git(struct transport *transport)
return 0;
}
+void transport_take_over(struct transport *transport,
+ struct child_process *child)
+{
+ struct git_transport_data *data;
+
+ if (!transport->smart_options)
+ die("Bug detected: Taking over transport requires non-NULL "
+ "smart_options field.");
+
+ data = xcalloc(1, sizeof(*data));
+ data->options = *transport->smart_options;
+ data->conn = child;
+ data->fd[0] = data->conn->out;
+ data->fd[1] = data->conn->in;
+ data->got_remote_heads = 0;
+ transport->data = data;
+
+ transport->set_option = NULL;
+ transport->get_refs_list = get_refs_via_connect;
+ transport->fetch = fetch_refs_via_pack;
+ transport->push = NULL;
+ transport->push_refs = git_transport_push;
+ transport->disconnect = disconnect_git;
+ transport->smart_options = &(data->options);
+}
+
static int is_local(const char *url)
{
const char *colon = strchr(url, ':');
@@ -780,6 +824,44 @@ static int is_file(const char *url)
return S_ISREG(buf.st_mode);
}
+static int is_url(const char *url)
+{
+ const char *url2, *first_slash;
+
+ if (!url)
+ return 0;
+ url2 = url;
+ first_slash = strchr(url, '/');
+
+ /* Input with no slash at all or slash first can't be URL. */
+ if (!first_slash || first_slash == url)
+ return 0;
+ /* Character before must be : and next must be /. */
+ if (first_slash[-1] != ':' || first_slash[1] != '/')
+ return 0;
+ /* There must be something before the :// */
+ if (first_slash == url + 1)
+ return 0;
+ /*
+ * Check all characters up to first slash - 1. Only alphanum
+ * is allowed.
+ */
+ url2 = url;
+ while (url2 < first_slash - 1) {
+ if (!isalnum((unsigned char)*url2))
+ return 0;
+ url2++;
+ }
+
+ /* Valid enough. */
+ return 1;
+}
+
+static int external_specification_len(const char *url)
+{
+ return strchr(url, ':') - url;
+}
+
struct transport *transport_get(struct remote *remote, const char *url)
{
struct transport *ret = xcalloc(1, sizeof(*ret));
@@ -793,6 +875,9 @@ struct transport *transport_get(struct remote *remote, const char *url)
url = remote->url[0];
ret->url = url;
+ /* In case previous URL had helper forced, reset it. */
+ remote->foreign_vcs = NULL;
+
/* maybe it is a foreign URL? */
if (url) {
const char *p = url;
@@ -805,46 +890,54 @@ struct transport *transport_get(struct remote *remote, const char *url)
if (remote && remote->foreign_vcs) {
transport_helper_init(ret, remote->foreign_vcs);
- return ret;
- }
-
- if (!prefixcmp(url, "rsync:")) {
+ } else if (!prefixcmp(url, "rsync:")) {
ret->get_refs_list = get_refs_via_rsync;
ret->fetch = fetch_objs_via_rsync;
ret->push = rsync_transport_push;
-
- } else if (!prefixcmp(url, "http://")
- || !prefixcmp(url, "https://")
- || !prefixcmp(url, "ftp://")) {
- transport_helper_init(ret, "curl");
-#ifdef NO_CURL
- error("git was compiled without libcurl support.");
-#endif
-
+ ret->smart_options = NULL;
} else if (is_local(url) && is_file(url)) {
struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
ret->data = data;
ret->get_refs_list = get_refs_from_bundle;
ret->fetch = fetch_refs_from_bundle;
ret->disconnect = close_bundle;
-
- } else {
+ ret->smart_options = NULL;
+ } else if (!is_url(url)
+ || !prefixcmp(url, "file://")
+ || !prefixcmp(url, "git://")
+ || !prefixcmp(url, "ssh://")
+ || !prefixcmp(url, "git+ssh://")
+ || !prefixcmp(url, "ssh+git://")) {
+ /* These are builtin smart transports. */
struct git_transport_data *data = xcalloc(1, sizeof(*data));
ret->data = data;
- ret->set_option = set_git_option;
+ ret->set_option = NULL;
ret->get_refs_list = get_refs_via_connect;
ret->fetch = fetch_refs_via_pack;
ret->push_refs = git_transport_push;
+ ret->connect = connect_git;
ret->disconnect = disconnect_git;
+ ret->smart_options = &(data->options);
- data->thin = 1;
data->conn = NULL;
- data->uploadpack = "git-upload-pack";
+ data->got_remote_heads = 0;
+ } else {
+ /* Unknown protocol in URL. Pass to external handler. */
+ int len = external_specification_len(url);
+ char *handler = xmalloc(len + 1);
+ handler[len] = 0;
+ strncpy(handler, url, len);
+ transport_helper_init(ret, handler);
+ }
+
+ if (ret->smart_options) {
+ ret->smart_options->thin = 1;
+ ret->smart_options->uploadpack = "git-upload-pack";
if (remote->uploadpack)
- data->uploadpack = remote->uploadpack;
- data->receivepack = "git-receive-pack";
+ ret->smart_options->uploadpack = remote->uploadpack;
+ ret->smart_options->receivepack = "git-receive-pack";
if (remote->receivepack)
- data->receivepack = remote->receivepack;
+ ret->smart_options->receivepack = remote->receivepack;
}
return ret;
@@ -853,8 +946,23 @@ struct transport *transport_get(struct remote *remote, const char *url)
int transport_set_option(struct transport *transport,
const char *name, const char *value)
{
+ int git_reports = 1, protocol_reports = 1;
+
+ if (transport->smart_options)
+ git_reports = set_git_option(transport->smart_options,
+ name, value);
+
if (transport->set_option)
- return transport->set_option(transport, name, value);
+ protocol_reports = transport->set_option(transport, name,
+ value);
+
+ /* If either report is 0, report 0 (success). */
+ if (!git_reports || !protocol_reports)
+ return 0;
+ /* If either reports -1 (invalid value), report -1. */
+ if ((git_reports == -1) || (protocol_reports == -1))
+ return -1;
+ /* Otherwise if both report unknown, report unknown. */
return 1;
}
@@ -865,9 +973,9 @@ int transport_push(struct transport *transport,
*nonfastforward = 0;
verify_remote_names(refspec_nr, refspec);
- if (transport->push)
+ if (transport->push) {
return transport->push(transport, refspec_nr, refspec, flags);
- if (transport->push_refs) {
+ } else if (transport->push_refs) {
struct ref *remote_refs =
transport->get_refs_list(transport, 1);
struct ref *local_refs = get_local_heads();
@@ -911,6 +1019,7 @@ const struct ref *transport_get_remote_refs(struct transport *transport)
{
if (!transport->remote_refs)
transport->remote_refs = transport->get_refs_list(transport, 0);
+
return transport->remote_refs;
}
@@ -945,6 +1054,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)
}
rc = transport->fetch(transport, nr_heads, heads);
+
free(heads);
return rc;
}
@@ -958,6 +1068,15 @@ void transport_unlock_pack(struct transport *transport)
}
}
+int transport_connect(struct transport *transport, const char *name,
+ const char *exec, int fd[2])
+{
+ if (transport->connect)
+ return transport->connect(transport, name, exec, fd);
+ else
+ die("Operation not supported by protocol");
+}
+
int transport_disconnect(struct transport *transport)
{
int ret = 0;
diff --git a/transport.h b/transport.h
index 9e74406fa..7a242fe3b 100644
--- a/transport.h
+++ b/transport.h
@@ -4,6 +4,15 @@
#include "cache.h"
#include "remote.h"
+struct git_transport_options {
+ unsigned thin : 1;
+ unsigned keep : 1;
+ unsigned followtags : 1;
+ int depth;
+ const char *uploadpack;
+ const char *receivepack;
+};
+
struct transport {
struct remote *remote;
const char *url;
@@ -55,6 +64,8 @@ struct transport {
**/
int (*push_refs)(struct transport *transport, struct ref *refs, int flags);
int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
+ int (*connect)(struct transport *connection, const char *name,
+ const char *executable, int fd[2]);
/** get_refs_list(), fetch(), and push_refs() can keep
* resources (such as a connection) reserved for futher
@@ -63,8 +74,14 @@ struct transport {
int (*disconnect)(struct transport *connection);
char *pack_lockfile;
signed verbose : 3;
- /* Force progress even if the output is not a tty */
+ /* Force progress even if stderr is not a tty */
unsigned progress : 1;
+ /*
+ * If transport is at least potentially smart, this points to
+ * git_transport_options structure to use in case transport
+ * actually turns out to be smart.
+ */
+ struct git_transport_options *smart_options;
};
#define TRANSPORT_PUSH_ALL 1
@@ -115,6 +132,11 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs);
void transport_unlock_pack(struct transport *transport);
int transport_disconnect(struct transport *transport);
char *transport_anonymize_url(const char *url);
+void transport_take_over(struct transport *transport,
+ struct child_process *child);
+
+int transport_connect(struct transport *transport, const char *name,
+ const char *exec, int fd[2]);
/* Transport methods defined outside transport.c */
int transport_helper_init(struct transport *transport, const char *name);
diff --git a/unpack-trees.c b/unpack-trees.c
index dd5999c35..0ddbef3e6 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -32,6 +32,12 @@ static struct unpack_trees_error_msgs unpack_plumbing_errors = {
/* bind_overlap */
"Entry '%s' overlaps with '%s'. Cannot bind.",
+
+ /* sparse_not_uptodate_file */
+ "Entry '%s' not uptodate. Cannot update sparse checkout.",
+
+ /* would_lose_orphaned */
+ "Working tree file '%s' would be %s by sparse checkout update.",
};
#define ERRORMSG(o,fld) \
@@ -61,8 +67,16 @@ static void unlink_entry(struct cache_entry *ce)
{
if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce)))
return;
- if (unlink_or_warn(ce->name))
- return;
+ if (S_ISGITLINK(ce->ce_mode)) {
+ if (rmdir(ce->name)) {
+ warning("unable to rmdir %s: %s",
+ ce->name, strerror(errno));
+ return;
+ }
+ }
+ else
+ if (unlink_or_warn(ce->name))
+ return;
schedule_dir_for_removal(ce->name, ce_namelen(ce));
}
@@ -78,7 +92,7 @@ static int check_updates(struct unpack_trees_options *o)
if (o->update && o->verbose_update) {
for (total = cnt = 0; cnt < index->cache_nr; cnt++) {
struct cache_entry *ce = index->cache[cnt];
- if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
+ if (ce->ce_flags & (CE_UPDATE | CE_REMOVE | CE_WT_REMOVE))
total++;
}
@@ -92,6 +106,13 @@ static int check_updates(struct unpack_trees_options *o)
for (i = 0; i < index->cache_nr; i++) {
struct cache_entry *ce = index->cache[i];
+ if (ce->ce_flags & CE_WT_REMOVE) {
+ display_progress(progress, ++cnt);
+ if (o->update)
+ unlink_entry(ce);
+ continue;
+ }
+
if (ce->ce_flags & CE_REMOVE) {
display_progress(progress, ++cnt);
if (o->update)
@@ -118,6 +139,57 @@ static int check_updates(struct unpack_trees_options *o)
return errs != 0;
}
+static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o);
+static int verify_absent_sparse(struct cache_entry *ce, const char *action, struct unpack_trees_options *o);
+
+static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o)
+{
+ const char *basename;
+
+ if (ce_stage(ce))
+ return 0;
+
+ basename = strrchr(ce->name, '/');
+ basename = basename ? basename+1 : ce->name;
+ return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0;
+}
+
+static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o)
+{
+ int was_skip_worktree = ce_skip_worktree(ce);
+
+ if (will_have_skip_worktree(ce, o))
+ ce->ce_flags |= CE_SKIP_WORKTREE;
+ else
+ ce->ce_flags &= ~CE_SKIP_WORKTREE;
+
+ /*
+ * We only care about files getting into the checkout area
+ * If merge strategies want to remove some, go ahead, this
+ * flag will be removed eventually in unpack_trees() if it's
+ * outside checkout area.
+ */
+ if (ce->ce_flags & CE_REMOVE)
+ return 0;
+
+ if (!was_skip_worktree && ce_skip_worktree(ce)) {
+ /*
+ * If CE_UPDATE is set, verify_uptodate() must be called already
+ * also stat info may have lost after merged_entry() so calling
+ * verify_uptodate() again may fail
+ */
+ if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o))
+ return -1;
+ ce->ce_flags |= CE_WT_REMOVE;
+ }
+ if (was_skip_worktree && !ce_skip_worktree(ce)) {
+ if (verify_absent_sparse(ce, "overwritten", o))
+ return -1;
+ ce->ce_flags |= CE_UPDATE;
+ }
+ return 0;
+}
+
static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o)
{
int ret = o->fn(src, o);
@@ -369,8 +441,9 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
*/
int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
{
- int ret;
+ int i, ret;
static struct cache_entry *dfc;
+ struct exclude_list el;
if (len > MAX_UNPACK_TREES)
die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
@@ -380,6 +453,16 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
state.quiet = 1;
state.refresh_cache = 1;
+ memset(&el, 0, sizeof(el));
+ if (!core_apply_sparse_checkout || !o->update)
+ o->skip_sparse_checkout = 1;
+ if (!o->skip_sparse_checkout) {
+ if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, NULL, &el, 0) < 0)
+ o->skip_sparse_checkout = 1;
+ else
+ o->el = &el;
+ }
+
memset(&o->result, 0, sizeof(o->result));
o->result.initialized = 1;
if (o->src_index) {
@@ -400,26 +483,65 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
info.fn = unpack_callback;
info.data = o;
- if (traverse_trees(len, t, &info) < 0)
- return unpack_failed(o, NULL);
+ if (traverse_trees(len, t, &info) < 0) {
+ ret = unpack_failed(o, NULL);
+ goto done;
+ }
}
/* Any left-over entries in the index? */
if (o->merge) {
while (o->pos < o->src_index->cache_nr) {
struct cache_entry *ce = o->src_index->cache[o->pos];
- if (unpack_index_entry(ce, o) < 0)
- return unpack_failed(o, NULL);
+ if (unpack_index_entry(ce, o) < 0) {
+ ret = unpack_failed(o, NULL);
+ goto done;
+ }
}
}
- if (o->trivial_merges_only && o->nontrivial_merge)
- return unpack_failed(o, "Merge requires file-level merging");
+ if (o->trivial_merges_only && o->nontrivial_merge) {
+ ret = unpack_failed(o, "Merge requires file-level merging");
+ goto done;
+ }
+
+ if (!o->skip_sparse_checkout) {
+ int empty_worktree = 1;
+ for (i = 0;i < o->result.cache_nr;i++) {
+ struct cache_entry *ce = o->result.cache[i];
+
+ if (apply_sparse_checkout(ce, o)) {
+ ret = -1;
+ goto done;
+ }
+ /*
+ * Merge strategies may set CE_UPDATE|CE_REMOVE outside checkout
+ * area as a result of ce_skip_worktree() shortcuts in
+ * verify_absent() and verify_uptodate(). Clear them.
+ */
+ if (ce_skip_worktree(ce))
+ ce->ce_flags &= ~(CE_UPDATE | CE_REMOVE);
+ else
+ empty_worktree = 0;
+
+ }
+ if (o->result.cache_nr && empty_worktree) {
+ ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory");
+ goto done;
+ }
+ }
o->src_index = NULL;
ret = check_updates(o) ? (-2) : 0;
if (o->dst_index)
*o->dst_index = o->result;
+
+done:
+ for (i = 0;i < el.nr;i++)
+ free(el.excludes[i]);
+ if (el.excludes)
+ free(el.excludes);
+
return ret;
}
@@ -436,6 +558,8 @@ static int same(struct cache_entry *a, struct cache_entry *b)
return 0;
if (!a && !b)
return 1;
+ if ((a->ce_flags | b->ce_flags) & CE_CONFLICTED)
+ return 0;
return a->ce_mode == b->ce_mode &&
!hashcmp(a->sha1, b->sha1);
}
@@ -445,16 +569,17 @@ static int same(struct cache_entry *a, struct cache_entry *b)
* When a CE gets turned into an unmerged entry, we
* want it to be up-to-date
*/
-static int verify_uptodate(struct cache_entry *ce,
- struct unpack_trees_options *o)
+static int verify_uptodate_1(struct cache_entry *ce,
+ struct unpack_trees_options *o,
+ const char *error_msg)
{
struct stat st;
- if (o->index_only || o->reset || ce_uptodate(ce))
+ if (o->index_only || (!ce_skip_worktree(ce) && (o->reset || ce_uptodate(ce))))
return 0;
if (!lstat(ce->name, &st)) {
- unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID);
+ unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
if (!changed)
return 0;
/*
@@ -471,7 +596,21 @@ static int verify_uptodate(struct cache_entry *ce,
if (errno == ENOENT)
return 0;
return o->gently ? -1 :
- error(ERRORMSG(o, not_uptodate_file), ce->name);
+ error(error_msg, ce->name);
+}
+
+static int verify_uptodate(struct cache_entry *ce,
+ struct unpack_trees_options *o)
+{
+ if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
+ return 0;
+ return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file));
+}
+
+static int verify_uptodate_sparse(struct cache_entry *ce,
+ struct unpack_trees_options *o)
+{
+ return verify_uptodate_1(ce, o, ERRORMSG(o, sparse_not_uptodate_file));
}
static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o)
@@ -572,15 +711,16 @@ static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst,
struct cache_entry *src;
src = index_name_exists(o->src_index, dst->name, ce_namelen(dst), 1);
- return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID);
+ return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
}
/*
* We do not want to remove or overwrite a working tree file that
* is not tracked, unless it is ignored.
*/
-static int verify_absent(struct cache_entry *ce, const char *action,
- struct unpack_trees_options *o)
+static int verify_absent_1(struct cache_entry *ce, const char *action,
+ struct unpack_trees_options *o,
+ const char *error_msg)
{
struct stat st;
@@ -660,13 +800,30 @@ static int verify_absent(struct cache_entry *ce, const char *action,
}
return 0;
}
+static int verify_absent(struct cache_entry *ce, const char *action,
+ struct unpack_trees_options *o)
+{
+ if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
+ return 0;
+ return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked));
+}
+
+static int verify_absent_sparse(struct cache_entry *ce, const char *action,
+ struct unpack_trees_options *o)
+{
+ return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_orphaned));
+}
static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
struct unpack_trees_options *o)
{
int update = CE_UPDATE;
- if (old) {
+ if (!old) {
+ if (verify_absent(merge, "overwritten", o))
+ return -1;
+ invalidate_ce_path(merge, o);
+ } else if (!(old->ce_flags & CE_CONFLICTED)) {
/*
* See if we can re-use the old CE directly?
* That way we get the uptodate stat info.
@@ -680,13 +837,16 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
} else {
if (verify_uptodate(old, o))
return -1;
+ if (ce_skip_worktree(old))
+ update |= CE_SKIP_WORKTREE;
invalidate_ce_path(old, o);
}
- }
- else {
- if (verify_absent(merge, "overwritten", o))
- return -1;
- invalidate_ce_path(merge, o);
+ } else {
+ /*
+ * Previously unmerged entry left as an existence
+ * marker by read_index_unmerged();
+ */
+ invalidate_ce_path(old, o);
}
add_entry(o, merge, update, CE_STAGEMASK);
@@ -702,7 +862,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
return -1;
return 0;
}
- if (verify_uptodate(old, o))
+ if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o))
return -1;
add_entry(o, ce, CE_REMOVE, 0);
invalidate_ce_path(ce, o);
@@ -1004,10 +1164,10 @@ int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o)
if (old && same(old, a)) {
int update = 0;
- if (o->reset && !ce_uptodate(old)) {
+ if (o->reset && !ce_uptodate(old) && !ce_skip_worktree(old)) {
struct stat st;
if (lstat(old->name, &st) ||
- ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID))
+ ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE))
update |= CE_UPDATE;
}
add_entry(o, old, update, 0);
diff --git a/unpack-trees.h b/unpack-trees.h
index d19df44f4..95ff36c82 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -4,6 +4,7 @@
#define MAX_UNPACK_TREES 8
struct unpack_trees_options;
+struct exclude_list;
typedef int (*merge_fn_t)(struct cache_entry **src,
struct unpack_trees_options *options);
@@ -14,6 +15,8 @@ struct unpack_trees_error_msgs {
const char *not_uptodate_dir;
const char *would_lose_untracked;
const char *bind_overlap;
+ const char *sparse_not_uptodate_file;
+ const char *would_lose_orphaned;
};
struct unpack_trees_options {
@@ -28,6 +31,7 @@ struct unpack_trees_options {
skip_unmerged,
initial_checkout,
diff_index_cached,
+ skip_sparse_checkout,
gently;
const char *prefix;
int pos;
@@ -44,6 +48,8 @@ struct unpack_trees_options {
struct index_state *dst_index;
struct index_state *src_index;
struct index_state result;
+
+ struct exclude_list *el; /* for internal use */
};
extern int unpack_trees(unsigned n, struct tree_desc *t,