summaryrefslogtreecommitdiff
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.txt123
-rw-r--r--Documentation/blame-options.txt6
-rw-r--r--Documentation/config.txt33
-rw-r--r--Documentation/git-blame.txt2
-rw-r--r--Documentation/git-branch.txt8
-rw-r--r--Documentation/git-clone.txt12
-rw-r--r--Documentation/git-commit.txt14
-rw-r--r--Documentation/git-config.txt14
-rw-r--r--Documentation/git-cvsserver.txt15
-rw-r--r--Documentation/git-diff.txt4
-rw-r--r--Documentation/git-difftool.txt17
-rw-r--r--Documentation/git-fast-import.txt79
-rw-r--r--Documentation/git-http-backend.txt10
-rw-r--r--Documentation/git-log.txt11
-rw-r--r--Documentation/git-ls-files.txt1
-rw-r--r--Documentation/git-merge-file.txt12
-rw-r--r--Documentation/git-merge.txt7
-rw-r--r--Documentation/git-push.txt13
-rw-r--r--Documentation/git-read-tree.txt52
-rw-r--r--Documentation/git-rebase.txt23
-rw-r--r--Documentation/git-remote-helpers.txt30
-rw-r--r--Documentation/git-remote.txt17
-rw-r--r--Documentation/git-reset.txt90
-rw-r--r--Documentation/git-rev-list.txt7
-rw-r--r--Documentation/git-rev-parse.txt29
-rw-r--r--Documentation/git-svn.txt44
-rw-r--r--Documentation/git-update-index.txt29
-rw-r--r--Documentation/git.txt6
-rw-r--r--Documentation/gitattributes.txt17
-rw-r--r--Documentation/gitcore-tutorial.txt8
-rw-r--r--Documentation/merge-options.txt5
-rw-r--r--Documentation/merge-strategies.txt29
-rw-r--r--Documentation/pretty-formats.txt1
-rw-r--r--Documentation/pretty-options.txt8
-rw-r--r--Documentation/rev-list-options.txt25
-rw-r--r--Documentation/technical/api-directory-listing.txt3
-rw-r--r--Documentation/technical/api-strbuf.txt11
-rw-r--r--Makefile127
-rw-r--r--advice.c18
-rw-r--r--advice.h6
-rw-r--r--archive.c29
-rw-r--r--attr.c11
-rw-r--r--attr.h2
-rw-r--r--base85.c14
-rw-r--r--bisect.c10
-rw-r--r--bisect.h2
-rw-r--r--blob.c21
-rw-r--r--blob.h9
-rw-r--r--branch.c45
-rw-r--r--builtin-add.c76
-rw-r--r--builtin-apply.c15
-rw-r--r--builtin-archive.c17
-rw-r--r--builtin-branch.c66
-rw-r--r--builtin-check-attr.c2
-rw-r--r--builtin-checkout.c31
-rw-r--r--builtin-clean.c4
-rw-r--r--builtin-clone.c6
-rw-r--r--builtin-commit.c81
-rw-r--r--builtin-config.c20
-rw-r--r--builtin-gc.c2
-rw-r--r--builtin-grep.c345
-rw-r--r--builtin-hash-object.c (renamed from hash-object.c)5
-rw-r--r--builtin-help.c9
-rw-r--r--builtin-index-pack.c (renamed from index-pack.c)16
-rw-r--r--builtin-log.c3
-rw-r--r--builtin-ls-files.c54
-rw-r--r--builtin-merge-file.c17
-rw-r--r--builtin-merge-index.c (renamed from merge-index.c)7
-rw-r--r--builtin-merge-recursive.c27
-rw-r--r--builtin-merge-tree.c (renamed from merge-tree.c)10
-rw-r--r--builtin-merge.c73
-rw-r--r--builtin-mktag.c (renamed from mktag.c)6
-rw-r--r--builtin-mv.c11
-rw-r--r--builtin-pack-objects.c38
-rw-r--r--builtin-pack-redundant.c (renamed from pack-redundant.c)8
-rw-r--r--builtin-patch-id.c (renamed from patch-id.c)4
-rw-r--r--builtin-push.c32
-rw-r--r--builtin-read-tree.c6
-rw-r--r--builtin-remote.c108
-rw-r--r--builtin-rerere.c26
-rw-r--r--builtin-reset.c52
-rw-r--r--builtin-rev-list.c2
-rw-r--r--builtin-rev-parse.c29
-rw-r--r--builtin-revert.c19
-rw-r--r--builtin-rm.c3
-rw-r--r--builtin-send-pack.c51
-rw-r--r--builtin-unpack-file.c (renamed from unpack-file.c)5
-rw-r--r--builtin-update-index.c96
-rw-r--r--builtin-var.c (renamed from var.c)4
-rw-r--r--builtin.h9
-rw-r--r--cache.h38
-rw-r--r--commit.h2
-rw-r--r--compat/mingw.c116
-rw-r--r--compat/mingw.h12
-rw-r--r--compat/msvc.h40
-rw-r--r--compat/regex/regex.c2
-rw-r--r--compat/vcbuild/scripts/clink.pl1
-rw-r--r--compat/win32/pthread.c110
-rw-r--r--compat/win32/pthread.h67
-rw-r--r--config.c11
-rw-r--r--contrib/buildsystems/engine.pl9
-rwxr-xr-xcontrib/completion/git-completion.bash6
-rwxr-xr-xcontrib/examples/git-merge.sh3
-rwxr-xr-xcontrib/fast-import/git-p42
-rwxr-xr-xcontrib/hg-to-git/hg-to-git.py8
-rw-r--r--convert.c9
-rw-r--r--daemon.c29
-rw-r--r--date.c2
-rw-r--r--diff-lib.c47
-rw-r--r--diff.c27
-rw-r--r--diff.h10
-rw-r--r--diffcore.h1
-rw-r--r--dir.c305
-rw-r--r--dir.h4
-rw-r--r--editor.c21
-rw-r--r--entry.c4
-rw-r--r--environment.c1
-rw-r--r--fast-import.c461
-rwxr-xr-xgit-am.sh6
-rw-r--r--git-compat-util.h6
-rwxr-xr-xgit-cvsimport.perl70
-rwxr-xr-xgit-cvsserver.perl22
-rwxr-xr-xgit-difftool--helper.sh38
-rwxr-xr-xgit-difftool.perl26
-rwxr-xr-xgit-pull.sh55
-rwxr-xr-xgit-rebase--interactive.sh307
-rwxr-xr-xgit-rebase.sh30
-rwxr-xr-xgit-sh-setup.sh19
-rwxr-xr-xgit-submodule.sh6
-rwxr-xr-xgit-svn.perl160
-rw-r--r--git.c13
-rw-r--r--grep.c84
-rw-r--r--grep.h1
-rw-r--r--hex.c67
-rw-r--r--http-backend.c3
-rw-r--r--http.c36
-rw-r--r--http.h9
-rw-r--r--ident.c25
-rw-r--r--imap-send.c8
-rw-r--r--ll-merge.c94
-rw-r--r--ll-merge.h4
-rw-r--r--lockfile.c5
-rw-r--r--log-tree.c1
-rw-r--r--mailmap.c5
-rw-r--r--mailmap.h1
-rw-r--r--match-trees.c69
-rw-r--r--merge-file.c16
-rw-r--r--merge-recursive.c47
-rw-r--r--merge-recursive.h7
-rw-r--r--object.c21
-rw-r--r--object.h5
-rw-r--r--pack-write.c4
-rw-r--r--pack.h2
-rw-r--r--pager.c5
-rw-r--r--parse-options.c14
-rw-r--r--parse-options.h6
-rw-r--r--path.c39
-rw-r--r--pretty.c4
-rw-r--r--quote.c2
-rw-r--r--quote.h1
-rw-r--r--read-cache.c122
-rw-r--r--refs.c53
-rw-r--r--refs.h2
-rw-r--r--remote-curl.c50
-rw-r--r--remote.c50
-rw-r--r--remote.h2
-rw-r--r--rerere.c307
-rw-r--r--rerere.h11
-rw-r--r--resolve-undo.c176
-rw-r--r--resolve-undo.h16
-rw-r--r--revision.c48
-rw-r--r--revision.h3
-rw-r--r--run-command.c297
-rw-r--r--run-command.h2
-rw-r--r--setup.c37
-rw-r--r--sha1_file.c74
-rw-r--r--sha1_name.c248
-rw-r--r--show-index.c2
-rw-r--r--strbuf.c33
-rw-r--r--strbuf.h3
-rw-r--r--string-list.h6
-rw-r--r--submodule.c51
-rw-r--r--submodule.h1
-rw-r--r--symlinks.c31
-rw-r--r--t/lib-httpd.sh30
-rw-r--r--t/lib-httpd/apache.conf5
-rw-r--r--t/lib-rebase.sh33
-rwxr-xr-xt/t0021-conversion.sh3
-rwxr-xr-xt/t0061-run-command.sh14
-rwxr-xr-xt/t1011-read-tree-sparse-checkout.sh150
-rwxr-xr-xt/t1300-repo-config.sh28
-rwxr-xr-xt/t1501-worktree.sh6
-rwxr-xr-xt/t1506-rev-parse-diagnosis.sh69
-rwxr-xr-xt/t1506-rev-parse-upstream.sh110
-rwxr-xr-xt/t2012-checkout-last.sh27
-rwxr-xr-xt/t2030-unresolve-info.sh170
-rwxr-xr-xt/t2104-update-index-skip-worktree.sh57
-rwxr-xr-xt/t2105-update-index-gitfile.sh38
-rwxr-xr-xt/t3001-ls-files-others-exclude.sh61
-rwxr-xr-xt/t3030-merge-recursive.sh6
-rwxr-xr-xt/t3200-branch.sh26
-rwxr-xr-xt/t3301-notes.sh59
-rwxr-xr-xt/t3404-rebase-interactive.sh205
-rwxr-xr-xt/t3415-rebase-autosquash.sh73
-rwxr-xr-xt/t3416-rebase-onto-threedots.sh105
-rwxr-xr-xt/t3501-revert-cherry-pick.sh2
-rwxr-xr-xt/t4027-diff-submodule.sh84
-rwxr-xr-xt/t4030-diff-textconv.sh2
-rwxr-xr-xt/t4031-diff-rewrite-binary.sh2
-rwxr-xr-xt/t4120-apply-popt.sh5
-rwxr-xr-xt/t4133-apply-filenames.sh38
-rwxr-xr-xt/t4200-rerere.sh15
-rwxr-xr-xt/t5505-remote.sh230
-rwxr-xr-xt/t5516-fetch-push.sh26
-rwxr-xr-xt/t5523-push-upstream.sh69
-rwxr-xr-xt/t5524-pull-msg.sh35
-rwxr-xr-xt/t5541-http-push.sh44
-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/t6006-rev-list-format.sh7
-rwxr-xr-xt/t6018-rev-list-glob.sh195
-rwxr-xr-xt/t6026-merge-attr.sh12
-rwxr-xr-xt/t6029-merge-subtree.sh47
-rwxr-xr-xt/t6037-merge-ours-theirs.sh64
-rwxr-xr-xt/t6040-tracking-info.sh21
-rwxr-xr-xt/t7001-mv.sh12
-rwxr-xr-xt/t7002-grep.sh58
-rwxr-xr-xt/t7011-skip-worktree-reading.sh163
-rwxr-xr-xt/t7012-skip-worktree-writing.sh146
-rwxr-xr-xt/t7103-reset-bare.sh6
-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/t7401-submodule-summary.sh2
-rwxr-xr-xt/t7501-commit.sh6
-rwxr-xr-xt/t7502-commit.sh109
-rwxr-xr-xt/t7506-status-submodule.sh83
-rwxr-xr-xt/t7508-status.sh4
-rwxr-xr-xt/t7800-difftool.sh75
-rwxr-xr-xt/t9151-svn-mergeinfo.sh2
-rw-r--r--t/t9151/make-svnmerge-dump65
-rw-r--r--t/t9151/svn-mergeinfo.dump545
-rwxr-xr-xt/t9153-git-svn-rewrite-uuid.sh25
-rw-r--r--t/t9153/svn.dump75
-rwxr-xr-xt/t9154-git-svn-fancy-glob.sh42
-rw-r--r--t/t9154/svn.dump222
-rwxr-xr-xt/t9300-fast-import.sh308
-rwxr-xr-xt/t9301-fast-import-notes.sh623
-rwxr-xr-xt/t9350-fast-export.sh (renamed from t/t9301-fast-export.sh)0
-rw-r--r--test-index-version.c14
-rw-r--r--test-run-command.c35
-rw-r--r--transport-helper.c325
-rw-r--r--transport.c288
-rw-r--r--transport.h25
-rw-r--r--tree-diff.c8
-rw-r--r--unpack-trees.c214
-rw-r--r--unpack-trees.h6
-rw-r--r--userdiff.c2
-rw-r--r--utf8.c2
-rw-r--r--utf8.h1
-rw-r--r--ws.c2
-rw-r--r--wt-status.c14
-rw-r--r--xdiff/xdiff.h15
-rw-r--r--xdiff/xmerge.c33
275 files changed, 11138 insertions, 2496 deletions
diff --git a/.gitignore b/.gitignore
index 5d32289664..83cf1b7532 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 6ff87c4664..536e55524d 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 4797b2dc35..8a8a3954dc 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 0000000000..8b24bebb96
--- /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 4c88bebb90..f1d0a4ae2d 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 d66a9732c3..098e38bc94 100644
--- a/Documentation/RelNotes-1.7.0.txt
+++ b/Documentation/RelNotes-1.7.0.txt
@@ -44,18 +44,129 @@ 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).
+
+ * A new attribute conflict-marker-size can be used to change the size of
+ the conflict markers from the default 7; this is useful when tracked
+ contents (e.g. git-merge documentation) have strings that resemble the
+ conflict markers.
+
+ * A new syntax "<branch>@{upstream}" can be used on the command line to
+ substitute the name of the "upstream" of the branch. Missing branch
+ defaults to the current branch, so "git fetch && git merge @{upstream}"
+ will be equivalent to "git pull".
+
+ * "git branch --set-upstream" can be used to update the (surprise!) upstream
+ i.e. where the branch is supposed to pull and merge from (or rebase onto).
+
+ * "git checkout A...B" is a way to detach HEAD at the merge base between
+ A and B.
+
+ * "git checkout -m path" to reset the work tree file back into the
+ conflicted state works even when you already ran "git add path" and
+ resolved the conflicts.
+
* "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 commit" warns a bit more aggressively until you configure user.email,
+ whose default value almost always is not (and fundamentally cannot be)
+ what you want.
+
+ * "git difftool" has been extended to make it easier to integrate it
+ with gitk.
+
+ * "git fetch --all" can now be used in place of "git remote update".
+
+ * "git grep" does not rely on external grep anymore.
+
+ * "git grep" learned "--no-index" option, to search inside contents that
+ are not managed by git.
+
+ * "git log" and friends learned "--glob=heads/*" syntax that is a more
+ flexible way to complement "--branches/--tags/--remotes".
+
+ * "git merge" learned to pass options specific to strategy-backends. E.g.
+
+ - "git merge -Xsubtree=path/to/directory" can be used to tell the subtree
+ strategy how much to shift the trees explicitly.
+
+ - "git merge -Xtheirs" can be used to auto-merge as much as possible,
+ while discarding your own changes and taking merged version in
+ conflicted regions.
+
+ * "git push" learned "git push origin --delete branch", a syntactic sugar
+ for "git push origin :branch".
+
+ * "git push" learned "git push --set-upstream origin forker:forkee" that
+ lets you configure your "forker" branch to later pull from "forkee"
+ branch at "origin".
+
+ * "git rebase --onto A...B" means the history is replayed on top of the
+ merge base between A and B.
+
+ * "git rebase -i" learned new action "fixup", that squashes the change
+ but does not affect existing log message.
+
+ * "git rebase -i" also learned --autosquash option, that is useful
+ together with the new "fixup" action.
+
+ * "git remote" learned set-url subcommand, to update (surprise!) url
+ for an existing remote nickname.
+
+ * "git rerere" learned "forget path" subcommand. Together with "git
+ checkout -m path" it will be useful when you recorded a wrong
+ resolution.
+
+ * 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.
+
+ * Many more commands are now built-in.
Fixes since v1.6.6
------------------
@@ -63,8 +174,18 @@ Fixes since v1.6.6
All of the fixes in v1.6.6.X maintenance series are included in this
release, unless otherwise noted.
+ * "git branch -d branch" used to refuse deleting the branch even when
+ the branch is fully merged to its upstream branch if it is not merged
+ to the current branch. It now deletes it in such a case.
+
+ * When "git diff" is asked to compare the work tree with something,
+ it used to consider that a checked-out submodule with uncommitted
+ changes is not modified; this could cause people to forget committing
+ these changes in the submodule before committing in the superproject.
+ It now considers such a change as a modification.
+
--
exec >/var/tmp/1
-O=v1.6.6-101-gf012d27
+O=v1.6.6.1-434-g3521c1b
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 1625ffce6a..4833cac4b9 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 8d3397c69b..8dcb191566 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -130,6 +130,14 @@ advice.*::
Advice shown when linkgit:git-merge[1] refuses to
merge to avoid overwritting local changes.
Default: true.
+ resolveConflict::
+ Advices shown by various commands when conflicts
+ prevent the operation from being performed.
+ Default: true.
+ implicitIdentity::
+ Advice on how to set your identity configuration when
+ your information is guessed from the system username and
+ domain name. Default: true.
--
core.fileMode::
@@ -502,6 +510,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'
@@ -652,14 +664,6 @@ color.grep::
`never`), never. When set to `true` or `auto`, use color only
when the output is written to the terminal. Defaults to `false`.
-color.grep.external::
- The string value of this variable is passed to an external 'grep'
- command as a command line option if match highlighting is turned
- on. If set to an empty string, no option is passed at all,
- turning off coloring for external 'grep' calls; this is the default.
- For GNU grep, set it to `--color=always` to highlight matches even
- when a pager is used.
-
color.grep.match::
Use customized color for matches. The value of this variable
may be specified as in color.branch.<slot>. It is passed using
@@ -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
@@ -878,7 +887,7 @@ gc.packrefs::
Running `git pack-refs` in a repository renders it
unclonable by Git versions prior to 1.5.1.2 over dumb
transports such as HTTP. This variable determines whether
- 'git gc' runs `git pack-refs`. This can be set to "nobare"
+ 'git gc' runs `git pack-refs`. This can be set to `nobare`
to enable it within all non-bare repos or it can be set to a
boolean value. The default is `true`.
@@ -1136,6 +1145,12 @@ http.maxRequests::
How many HTTP requests to launch in parallel. Can be overridden
by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
+http.minSessions::
+ The number of curl sessions (counted across slots) to be kept across
+ requests. They will not be ended with curl_easy_cleanup() until
+ http_cleanup() is invoked. If USE_CURL_MULTI is not defined, this
+ value will be capped at 1. Defaults to 1.
+
http.postBuffer::
Maximum size in bytes of the buffer used by smart HTTP
transports when POSTing data to the remote system.
diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt
index 158a0187cb..a27f43950f 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-branch.txt b/Documentation/git-branch.txt
index 44144d5a0d..6b6c3da2d9 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -11,7 +11,7 @@ SYNOPSIS
'git branch' [--color | --no-color] [-r | -a]
[-v [--abbrev=<length> | --no-abbrev]]
[(--merged | --no-merged | --contains) [<commit>]]
-'git branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
+'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
'git branch' (-m | -M) [<oldbranch>] <newbranch>
'git branch' (-d | -D) [-r] <branchname>...
@@ -129,6 +129,12 @@ start-point is either a local or remote branch.
Do not set up "upstream" configuration, even if the
branch.autosetupmerge configuration variable is true.
+--set-upstream::
+ If specified branch does not exist yet or if '--force' has been
+ given, acts exactly like '--track'. Otherwise sets up configuration
+ like '--track' would when creating the branch, except that where
+ branch points to is not changed.
+
--contains <commit>::
Only list branches which contain the specified commit.
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 7ccd742a87..f43c8b2c08 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 c322c759fd..e99bb14754 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-config.txt b/Documentation/git-config.txt
index 3c8d895ddb..543dd64a46 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -37,11 +37,12 @@ existing values that match the regexp are updated or unset. If
you want to handle the lines that do *not* match the regex, just
prepend a single exclamation mark in front (see also <<EXAMPLES>>).
-The type specifier can be either '--int' or '--bool', which will make
+The type specifier can be either '--int' or '--bool', to make
'git config' ensure that the variable(s) are of the given type and
convert the value to the canonical form (simple decimal number for int,
-a "true" or "false" string for bool). If no type specifier is passed,
-no checks or transformations are performed on the value.
+a "true" or "false" string for bool), or '--path', which does some
+path expansion (see '--path' below). If no type specifier is passed, no
+checks or transformations are performed on the value.
The file-option can be one of '--system', '--global' or '--file'
which specify where the values will be read from or written to.
@@ -136,6 +137,13 @@ See also <<FILES>>.
'git config' will ensure that the output matches the format of
either --bool or --int, as described above.
+--path::
+ 'git-config' will expand leading '{tilde}' to the value of
+ '$HOME', and '{tilde}user' to the home directory for the
+ specified user. This option has no effect when setting the
+ value (but you can use 'git config bla {tilde}/' from the
+ command line to let your shell do the expansion).
+
-z::
--null::
For all options that output values and/or keys, always
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
index d4de2e8138..dbb053ee17 100644
--- a/Documentation/git-cvsserver.txt
+++ b/Documentation/git-cvsserver.txt
@@ -277,6 +277,21 @@ In `dbdriver` and `dbuser` you can use the following variables:
If no name can be determined, the
numeric uid is used.
+ENVIRONMENT
+-----------
+
+These variables obviate the need for command-line options in some
+circumstances, allowing easier restricted usage through git-shell.
+
+GIT_CVSSERVER_BASE_PATH takes the place of the argument to --base-path.
+
+GIT_CVSSERVER_ROOT specifies a single-directory whitelist. The
+repository must still be configured to allow access through
+git-cvsserver, as described above.
+
+When these environment variables are set, the corresponding
+command-line arguments may not be used.
+
Eclipse CVS Client Notes
------------------------
diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt
index 0ac711230e..723a64872f 100644
--- a/Documentation/git-diff.txt
+++ b/Documentation/git-diff.txt
@@ -157,6 +157,10 @@ $ git diff -R <2>
rewrites (very expensive).
<2> Output diff in reverse.
+SEE ALSO
+--------
+linkgit:git-difftool[1]::
+ Show changes using common diff tools
Author
------
diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt
index 4c9422d223..8250bad2ce 100644
--- a/Documentation/git-difftool.txt
+++ b/Documentation/git-difftool.txt
@@ -7,7 +7,7 @@ git-difftool - Show changes using common diff tools
SYNOPSIS
--------
-'git difftool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<'git diff' options>]
+'git difftool' [<options>] <commit>{0,2} [--] [<path>...]
DESCRIPTION
-----------
@@ -58,6 +58,18 @@ is set to the name of the temporary file containing the contents
of the diff post-image. `$BASE` is provided for compatibility
with custom merge tool commands and has the same value as `$LOCAL`.
+-x <command>::
+--extcmd=<command>::
+ Specify a custom command for viewing diffs.
+ 'git-difftool' ignores the configured defaults and runs
+ `$command $LOCAL $REMOTE` when this option is specified.
+
+-g::
+--gui::
+ When 'git-difftool' is invoked with the `-g` or `--gui` option
+ the default diff tool will be read from the configured
+ `diff.guitool` variable instead of `diff.tool`.
+
See linkgit:git-diff[1] for the full list of supported options.
CONFIG VARIABLES
@@ -68,6 +80,9 @@ difftool equivalents have not been defined.
diff.tool::
The default diff tool to use.
+diff.guitool::
+ The default diff tool to use when `--gui` is specified.
+
difftool.<tool>.path::
Override the path for the given tool. This is useful in case
your tool is not in the PATH.
diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
index 91ca302315..ff4022c15f 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 4b2edd3f78..07931c6874 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-log.txt b/Documentation/git-log.txt
index ff4063b020..0e39bb61ee 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -107,6 +107,17 @@ git log --follow builtin-rev-list.c::
those commits that occurred before the file was given its
present name.
+git log --branches --not --remotes=origin::
+
+ Shows all commits that are in any of local branches but not in
+ any of remote tracking branches for 'origin' (what you have that
+ origin doesn't).
+
+git log master --not --remotes=*/master::
+
+ Shows all commits that are in local master but not in any remote
+ repository master branches.
+
Discussion
----------
diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt
index a84cc3701b..3521637b58 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-file.txt b/Documentation/git-merge-file.txt
index fa723d0513..234269ae59 100644
--- a/Documentation/git-merge-file.txt
+++ b/Documentation/git-merge-file.txt
@@ -10,7 +10,8 @@ SYNOPSIS
--------
[verse]
'git merge-file' [-L <current-name> [-L <base-name> [-L <other-name>]]]
- [-p|--stdout] [-q|--quiet] <current-file> <base-file> <other-file>
+ [--ours|--theirs] [-p|--stdout] [-q|--quiet]
+ <current-file> <base-file> <other-file>
DESCRIPTION
@@ -34,7 +35,9 @@ normally outputs a warning and brackets the conflict with lines containing
>>>>>>> B
If there are conflicts, the user should edit the result and delete one of
-the alternatives.
+the alternatives. When `--ours` or `--theirs` option is in effect, however,
+these conflicts are resolved favouring lines from `<current-file>` or
+lines from `<other-file>` respectively.
The exit value of this program is negative on error, and the number of
conflicts otherwise. If the merge was clean, the exit value is 0.
@@ -62,6 +65,11 @@ OPTIONS
-q::
Quiet; do not warn about conflicts.
+--ours::
+--theirs::
+ Instead of leaving conflicts in the file, resolve conflicts
+ favouring our (or their) side of the lines.
+
EXAMPLES
--------
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index e9e2b0cd9f..9c9618cead 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>] <commit>...
+ [--[no-]rerere-autoupdate] [-m <msg>] <commit>...
'git merge' <msg> HEAD <commit>...
DESCRIPTION
@@ -61,6 +61,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.
+
<commit>...::
Commits, usually other branch heads, to merge into our branch.
You need at least one <commit>. Specifying more than one
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 0412b4815c..c63932b16c 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
- [--repo=<repository>] [-f | --force] [-v | --verbose]
+ [--repo=<repository>] [-f | --force] [-v | --verbose] [-u | --set-upstream]
[<repository> <refspec>...]
DESCRIPTION
@@ -91,6 +91,10 @@ nor in any Push line of the corresponding remotes file---see below).
will be tab-separated and sent to stdout instead of stderr. The full
symbolic names of the refs will be given.
+--delete::
+ All listed refs are deleted from the remote repository. This is
+ the same as prefixing all refs with a colon.
+
--tags::
All refs under `$GIT_DIR/refs/tags` are pushed, in
addition to refspecs explicitly listed on the command
@@ -118,6 +122,13 @@ nor in any Push line of the corresponding remotes file---see below).
the name "origin" is used. For this latter case, this option
can be used to override the name "origin". In other words,
the difference between these two commands
+
+-u::
+--set-upstream::
+ For every branch that is up to date or successfully pushed, add
+ upstream (tracking) reference, used by argument-less
+ linkgit:git-pull[1] and other commands. For more information,
+ see 'branch.<name>.merge' in linkgit:git-config[1].
+
--------------------------
git push public #1
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index a31a4cbe36..567671c013 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-rebase.txt b/Documentation/git-rebase.txt
index 105e895073..823f2a4638 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -308,6 +308,16 @@ which makes little sense.
root commits will be rewritten to have <newbase> as parent
instead.
+--autosquash::
+ When the commit log message begins with "squash! ..." (or
+ "fixup! ..."), and there is a commit whose title begins with
+ the same ..., automatically modify the todo list of rebase -i
+ so that the commit marked for squashing comes right after the
+ commit to be modified, and change the action of the moved
+ commit from `pick` to `squash` (or `fixup`).
++
+This option is only valid when '--interactive' option is used.
+
include::merge-strategies.txt[]
NOTES
@@ -382,9 +392,12 @@ If you just want to edit the commit message for a commit, replace the
command "pick" with the command "reword".
If you want to fold two or more commits into one, replace the command
-"pick" with "squash" for the second and subsequent commit. If the
-commits had different authors, it will attribute the squashed commit to
-the author of the first commit.
+"pick" for the second and subsequent commits with "squash" or "fixup".
+If the commits had different authors, the folded commit will be
+attributed to the author of the first commit. The suggested commit
+message for the folded commit is the concatenation of the commit
+messages of the first commit and of those with the "squash" command,
+but omits the commit messages of commits with the "fixup" command.
'git rebase' will stop when "pick" has been replaced with "edit" or
when a command fails due to merge errors. When you are done editing
@@ -512,8 +525,8 @@ Easy case: The changes are literally the same.::
Hard case: The changes are not the same.::
This happens if the 'subsystem' rebase had conflicts, or used
- `\--interactive` to omit, edit, or squash commits; or if the
- upstream used one of `commit \--amend`, `reset`, or
+ `\--interactive` to omit, edit, squash, or fixup commits; or
+ if the upstream used one of `commit \--amend`, `reset`, or
`filter-branch`.
diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt
index 5cfdc0cfc5..4685a898f1 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-remote.txt b/Documentation/git-remote.txt
index b03ccaac3d..3fc599c0c7 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -14,6 +14,9 @@ SYNOPSIS
'git remote rename' <old> <new>
'git remote rm' <name>
'git remote set-head' <name> (-a | -d | <branch>)
+'git remote set-url' [--push] <name> <newurl> [<oldurl>]
+'git remote set-url --add' [--push] <name> <newurl>
+'git remote set-url --delete' [--push] <name> <url>
'git remote' [-v | --verbose] 'show' [-n] <name>
'git remote prune' [-n | --dry-run] <name>
'git remote' [-v | --verbose] 'update' [-p | --prune] [group | remote]...
@@ -101,6 +104,20 @@ remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to
`refs/remotes/origin/master` already exists; if not it must be fetched first.
+
+'set-url'::
+
+Changes URL remote points to. Sets first URL remote points to matching
+regex <oldurl> (first URL if no <oldurl> is given) to <newurl>. If
+<oldurl> doesn't match any URL, error occurs and nothing is changed.
++
+With '--push', push URLs are manipulated instead of fetch URLs.
++
+With '--add', instead of changing some URL, new URL is added.
++
+With '--delete', instead of changing some URL, all URLs matching
+regex <url> are deleted. Trying to delete all non-push URLs is an
+error.
+
'show'::
Gives some information about the remote <name>.
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 17c119d874..168db08627 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -62,11 +62,101 @@ This means that `git reset -p` is the opposite of `git add -p` (see
linkgit:git-add[1]).
-q::
+--quiet::
Be quiet, only report errors.
<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-rev-list.txt b/Documentation/git-rev-list.txt
index 78d3f48dfb..173f3fc785 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -21,9 +21,10 @@ SYNOPSIS
[ \--full-history ]
[ \--not ]
[ \--all ]
- [ \--branches ]
- [ \--tags ]
- [ \--remotes ]
+ [ \--branches[=pattern] ]
+ [ \--tags[=pattern] ]
+ [ \--remotes[=pattern] ]
+ [ \--glob=glob-pattern ]
[ \--stdin ]
[ \--quiet ]
[ \--topo-order ]
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 82ae16e0eb..e7845d4055 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -103,14 +103,27 @@ OPTIONS
--all::
Show all refs found in `$GIT_DIR/refs`.
---branches::
- Show branch refs found in `$GIT_DIR/refs/heads`.
+--branches[=pattern]::
+--tags[=pattern]::
+--remotes[=pattern]::
+ Show all branches, tags, or remote-tracking branches,
+ respectively (i.e., refs found in `$GIT_DIR/refs/heads`,
+ `$GIT_DIR/refs/tags`, or `$GIT_DIR/refs/remotes`,
+ respectively).
++
+If a `pattern` is given, only refs matching the given shell glob are
+shown. If the pattern does not contain a globbing character (`?`,
+`\*`, or `[`), it is turned into a prefix match by appending `/\*`.
---tags::
- Show tag refs found in `$GIT_DIR/refs/tags`.
+--glob=pattern::
+ Show all refs matching the shell glob pattern `pattern`. If
+ the pattern does not start with `refs/`, this is automatically
+ prepended. If the pattern does not contain a globbing
+ character (`?`, `\*`, or `[`), it is turned into a prefix
+ match by appending `/\*`.
---remotes::
- Show tag refs found in `$GIT_DIR/refs/remotes`.
+--show-toplevel::
+ Show the absolute path of the top-level directory.
--show-prefix::
When the command is invoked from a subdirectory, show the
@@ -231,6 +244,10 @@ when you run 'git merge'.
* The special construct '@\{-<n>\}' means the <n>th branch checked out
before the current one.
+* The suffix '@{upstream}' to a ref (short form 'ref@{u}') refers to
+ the branch the ref is set to build on top of. Missing ref defaults
+ to the current branch.
+
* A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.
'rev{caret}'
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index 4cdca0d874..99f3c1ea6c 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -62,6 +62,8 @@ COMMANDS
Set the 'useSvnsyncProps' option in the [svn-remote] config.
--rewrite-root=<URL>;;
Set the 'rewriteRoot' option in the [svn-remote] config.
+--rewrite-uuid=<UUID>;;
+ Set the 'rewriteUUID' option in the [svn-remote] config.
--username=<USER>;;
For transports that SVN handles authentication for (http,
https, and plain svn), specify the username. For other
@@ -239,6 +241,19 @@ discouraged.
where <name> is the name of the SVN repository as specified by the -R option to
'init' (or "svn" by default).
+--username;;
+ Specify the SVN username to perform the commit as. This option overrides
+ configuration property 'username'.
+
+--commit-url;;
+ Use the specified URL to connect to the destination Subversion
+ repository. This is useful in cases where the source SVN
+ repository is read-only. This option overrides configuration
+ property 'commiturl'.
++
+ git config --get-all svn-remote.<name>.commiturl
++
+
'tag'::
Create a tag in the SVN repository. This is a shorthand for
'branch -t'.
@@ -616,6 +631,12 @@ svn-remote.<name>.rewriteRoot::
the repository with a public http:// or svn:// URL in the
metadata so users of it will see the public URL.
+svn-remote.<name>.rewriteUUID::
+ Similar to the useSvmProps option; this is for users who need
+ to remap the UUID manually. This may be useful in situations
+ where the original UUID is not available via either useSvmProps
+ or useSvnsyncProps.
+
svn.brokenSymlinkWorkaround::
This disables potentially expensive checks to workaround
broken symlinks checked into SVN by broken clients. Set this
@@ -625,13 +646,14 @@ svn.brokenSymlinkWorkaround::
revision fetched. If unset, 'git svn' assumes this option to
be "true".
-Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps
+Since the noMetadata, rewriteRoot, rewriteUUID, useSvnsyncProps and useSvmProps
options all affect the metadata generated and used by 'git svn'; they
*must* be set in the configuration file before any history is imported
and these settings should never be changed once they are set.
-Additionally, only one of these four options can be used per-svn-remote
-section because they affect the 'git-svn-id:' metadata line.
+Additionally, only one of these options can be used per svn-remote
+section because they affect the 'git-svn-id:' metadata line, except
+for rewriteRoot and rewriteUUID which can be used together.
BASIC EXAMPLES
@@ -816,6 +838,22 @@ independent path component (surrounded by '/' or EOL). This
type of configuration is not automatically created by 'init' and
should be manually entered with a text-editor or using 'git config'.
+It is also possible to fetch a subset of branches or tags by using a
+comma-separated list of names within braces. For example:
+
+------------------------------------------------------------------------
+[svn-remote "huge-project"]
+ url = http://server.org/svn
+ fetch = trunk/src:refs/remotes/trunk
+ branches = branches/{red,green}/src:refs/remotes/branches/*
+ tags = tags/{1.0,2.0}/src:refs/remotes/tags/*
+------------------------------------------------------------------------
+
+Note that git-svn keeps track of the highest revision in which a branch
+or tag has appeared. If the subset of branches or tags is changed after
+fetching, then .git/svn/.metadata must be manually edited to remove (or
+reset) branches-maxRev and/or tags-maxRev as appropriate.
+
SEE ALSO
--------
linkgit:git-rebase[1]
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
index e8c19be500..68dc1879fe 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 2c0f8562da..8c5f5b05cb 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/gitattributes.txt b/Documentation/gitattributes.txt
index 814714c56e..b396a871b3 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -525,6 +525,23 @@ When left unspecified, the driver itself is used for both
internal merge and the final merge.
+`conflict-marker-size`
+^^^^^^^^^^^^^^^^^^^^^^
+
+This attribute controls the length of conflict markers left in
+the work tree file during a conflicted merge. Only setting to
+the value to a positive integer has any meaningful effect.
+
+For example, this line in `.gitattributes` can be used to tell the merge
+machinery to leave much longer (instead of the usual 7-character-long)
+conflict markers when merging the file `Documentation/git-merge.txt`
+results in a conflict.
+
+------------------------
+Documentation/git-merge.txt conflict-marker-size=32
+------------------------
+
+
Checking whitespace errors
~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index c005f37a38..f7815e96a2 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -27,6 +27,14 @@ interfaces on top of it called "porcelain". You may not want to use the
plumbing directly very often, but it can be good to know what the
plumbing does for when the porcelain isn't flushing.
+Back when this document was originally written, many porcelain
+commands were shell scripts. For simplicity, it still uses them as
+examples to illustrate how plumbing is fit together to form the
+porcelain commands. The source tree includes some of these scripts in
+contrib/examples/ for reference. Although these are not implemented as
+shell scripts anymore, the description of what the plumbing layer
+commands do is still valid.
+
[NOTE]
Deeper technical details are often marked as Notes, which you can
skip on your first reading.
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index 5064bf83fa..3b83dba1a0 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -74,3 +74,8 @@ option can be used to override --squash.
-v::
--verbose::
Be verbose.
+
+-X <option>::
+--strategy-option=<option>::
+ Pass merge strategy specific option through to the merge
+ strategy.
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
index 42910a3d5e..a5bc1dbb95 100644
--- a/Documentation/merge-strategies.txt
+++ b/Documentation/merge-strategies.txt
@@ -1,6 +1,11 @@
MERGE STRATEGIES
----------------
+The merge mechanism ('git-merge' and 'git-pull' commands) allows the
+backend 'merge strategies' to be chosen with `-s` option. Some strategies
+can also take their own options, which can be passed by giving `-X<option>`
+arguments to 'git-merge' and/or 'git-pull'.
+
resolve::
This can only resolve two heads (i.e. the current branch
and another branch you pulled from) using a 3-way merge
@@ -20,6 +25,27 @@ recursive::
Additionally this can detect and handle merges involving
renames. This is the default merge strategy when
pulling or merging one branch.
++
+The 'recursive' strategy can take the following options:
+
+ours;;
+ This option forces conflicting hunks to be auto-resolved cleanly by
+ favoring 'our' version. Changes from the other tree that do not
+ conflict with our side are reflected to the merge result.
++
+This should not be confused with the 'ours' merge strategy, which does not
+even look at what the other tree contains at all. It discards everything
+the other tree did, declaring 'our' history contains all that happened in it.
+
+theirs;;
+ This is opposite of 'ours'.
+
+subtree[=path];;
+ This option is a more advanced form of 'subtree' strategy, where
+ the strategy makes a guess on how two trees must be shifted to
+ match with each other when merging. Instead, the specified path
+ is prefixed (or stripped from the beginning) to make the shape of
+ two trees to match.
octopus::
This resolves cases with more than two heads, but refuses to do
@@ -33,7 +59,8 @@ ours::
merge is always that of the current branch head, effectively
ignoring all changes from all other branches. It is meant to
be used to supersede old development history of side
- branches.
+ branches. Note that this is different from the -Xours option to
+ the 'recursive' merge strategy.
subtree::
This is a modified recursive strategy. When merging trees A and
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 53a9168ba7..1686a54d22 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -134,6 +134,7 @@ The placeholders are:
- '%C(...)': color specification, as described in color.branch.* config option
- '%m': left, right or boundary mark
- '%n': newline
+- '%%': a raw '%'
- '%x00': print a byte from a hex code
- '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of
linkgit:git-shortlog[1].
diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt
index bff94991b6..aa96caeab2 100644
--- a/Documentation/pretty-options.txt
+++ b/Documentation/pretty-options.txt
@@ -28,3 +28,11 @@ people using 80-column terminals.
command to re-code the commit log message in the encoding
preferred by the user. For non plumbing commands this
defaults to UTF-8.
+
+--no-notes::
+--show-notes::
+ Show the notes (see linkgit:git-notes[1]) that annotate the
+ commit, when showing the commit log message. This is the default
+ for `git log`, `git show` and `git whatchanged` commands when
+ there is no `--pretty`, `--format` nor `--oneline` option is
+ given on the command line.
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 1f57aed337..3ef71179d9 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -228,20 +228,33 @@ endif::git-rev-list[]
Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the
command line as '<commit>'.
---branches::
+--branches[=pattern]::
Pretend as if all the refs in `$GIT_DIR/refs/heads` are listed
- on the command line as '<commit>'.
+ on the command line as '<commit>'. If `pattern` is given, limit
+ branches to ones matching given shell glob. If pattern lacks '?',
+ '*', or '[', '/*' at the end is impiled.
---tags::
+--tags[=pattern]::
Pretend as if all the refs in `$GIT_DIR/refs/tags` are listed
- on the command line as '<commit>'.
+ on the command line as '<commit>'. If `pattern` is given, limit
+ tags to ones matching given shell glob. If pattern lacks '?', '*',
+ or '[', '/*' at the end is impiled.
---remotes::
+--remotes[=pattern]::
Pretend as if all the refs in `$GIT_DIR/refs/remotes` are listed
- on the command line as '<commit>'.
+ on the command line as '<commit>'. If `pattern`is given, limit
+ remote tracking branches to ones matching given shell glob.
+ If pattern lacks '?', '*', or '[', '/*' at the end is impiled.
+
+--glob=glob-pattern::
+ Pretend as if all the refs matching shell glob `glob-pattern`
+ are listed on the command line as '<commit>'. Leading 'refs/',
+ is automatically prepended if missing. If pattern lacks '?', '*',
+ or '[', '/*' at the end is impiled.
+
ifndef::git-rev-list[]
--bisect::
diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt
index 5bbd18f020..add6f435b5 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/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt
index a0e0f850f8..afe2759951 100644
--- a/Documentation/technical/api-strbuf.txt
+++ b/Documentation/technical/api-strbuf.txt
@@ -199,6 +199,10 @@ character if the letter `n` appears after a `%`. The function returns
the length of the placeholder recognized and `strbuf_expand()` skips
over it.
+
+The format `%%` is automatically expanded to a single `%` as a quoting
+mechanism; callers do not need to handle the `%` placeholder themselves,
+and the callback function will not be invoked for this placeholder.
++
All other characters (non-percent and not skipped ones) are copied
verbatim to the strbuf. If the callback returned zero, meaning that the
placeholder is unknown, then the percent sign is copied, too.
@@ -214,6 +218,13 @@ which can be used by the programmer of the callback as she sees fit.
placeholder and replacement string. The array needs to be
terminated by an entry with placeholder set to NULL.
+`strbuf_addbuf_percentquote`::
+
+ Append the contents of one strbuf to another, quoting any
+ percent signs ("%") into double-percents ("%%") in the
+ destination. This is useful for literal data to be fed to either
+ strbuf_expand or to the *printf family of functions.
+
`strbuf_addf`::
Add a formatted string to the buffer.
diff --git a/Makefile b/Makefile
index be18389534..c0dbee2ae4 100644
--- a/Makefile
+++ b/Makefile
@@ -187,10 +187,6 @@ all::
# is a simplified version of the merge sort used in glibc. This is
# recommended if Git triggers O(n^2) behavior in your platform's qsort().
#
-# Define NO_EXTERNAL_GREP if you don't want "git grep" to ever call
-# your external grep (e.g., if your system lacks grep, if its grep is
-# broken, or spawning external process is slower than built-in grep git has).
-#
# Define UNRELIABLE_FSTAT if your system's fstat does not return the same
# information on a not yet closed file that lstat would return for the same
# file after it was closed.
@@ -222,7 +218,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
@@ -391,19 +387,10 @@ EXTRA_PROGRAMS =
# ... and all the rest that could be moved out of bindir to gitexecdir
PROGRAMS += $(EXTRA_PROGRAMS)
PROGRAMS += git-fast-import$X
-PROGRAMS += git-hash-object$X
PROGRAMS += git-imap-send$X
-PROGRAMS += git-index-pack$X
-PROGRAMS += git-merge-index$X
-PROGRAMS += git-merge-tree$X
-PROGRAMS += git-mktag$X
-PROGRAMS += git-pack-redundant$X
-PROGRAMS += git-patch-id$X
PROGRAMS += git-shell$X
PROGRAMS += git-show-index$X
-PROGRAMS += git-unpack-file$X
PROGRAMS += git-upload-pack$X
-PROGRAMS += git-var$X
PROGRAMS += git-http-backend$X
# List built-in command $C whose implementation cmd_$C() is not in
@@ -468,6 +455,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
@@ -500,6 +488,7 @@ LIB_H += reflog-walk.h
LIB_H += refs.h
LIB_H += remote.h
LIB_H += rerere.h
+LIB_H += resolve-undo.h
LIB_H += revision.h
LIB_H += run-command.h
LIB_H += sha1-lookup.h
@@ -561,6 +550,7 @@ LIB_OBJS += graph.o
LIB_OBJS += grep.o
LIB_OBJS += hash.o
LIB_OBJS += help.o
+LIB_OBJS += hex.o
LIB_OBJS += ident.o
LIB_OBJS += levenshtein.o
LIB_OBJS += list-objects.o
@@ -595,6 +585,7 @@ LIB_OBJS += refs.o
LIB_OBJS += remote.o
LIB_OBJS += replace_object.o
LIB_OBJS += rerere.o
+LIB_OBJS += resolve-undo.o
LIB_OBJS += revision.o
LIB_OBJS += run-command.o
LIB_OBJS += server-info.o
@@ -659,7 +650,9 @@ BUILTIN_OBJS += builtin-for-each-ref.o
BUILTIN_OBJS += builtin-fsck.o
BUILTIN_OBJS += builtin-gc.o
BUILTIN_OBJS += builtin-grep.o
+BUILTIN_OBJS += builtin-hash-object.o
BUILTIN_OBJS += builtin-help.o
+BUILTIN_OBJS += builtin-index-pack.o
BUILTIN_OBJS += builtin-init-db.o
BUILTIN_OBJS += builtin-log.o
BUILTIN_OBJS += builtin-ls-files.o
@@ -670,13 +663,18 @@ BUILTIN_OBJS += builtin-mailsplit.o
BUILTIN_OBJS += builtin-merge.o
BUILTIN_OBJS += builtin-merge-base.o
BUILTIN_OBJS += builtin-merge-file.o
+BUILTIN_OBJS += builtin-merge-index.o
BUILTIN_OBJS += builtin-merge-ours.o
BUILTIN_OBJS += builtin-merge-recursive.o
+BUILTIN_OBJS += builtin-merge-tree.o
+BUILTIN_OBJS += builtin-mktag.o
BUILTIN_OBJS += builtin-mktree.o
BUILTIN_OBJS += builtin-mv.o
BUILTIN_OBJS += builtin-name-rev.o
BUILTIN_OBJS += builtin-pack-objects.o
+BUILTIN_OBJS += builtin-pack-redundant.o
BUILTIN_OBJS += builtin-pack-refs.o
+BUILTIN_OBJS += builtin-patch-id.o
BUILTIN_OBJS += builtin-prune-packed.o
BUILTIN_OBJS += builtin-prune.o
BUILTIN_OBJS += builtin-push.o
@@ -699,11 +697,13 @@ BUILTIN_OBJS += builtin-stripspace.o
BUILTIN_OBJS += builtin-symbolic-ref.o
BUILTIN_OBJS += builtin-tag.o
BUILTIN_OBJS += builtin-tar-tree.o
+BUILTIN_OBJS += builtin-unpack-file.o
BUILTIN_OBJS += builtin-unpack-objects.o
BUILTIN_OBJS += builtin-update-index.o
BUILTIN_OBJS += builtin-update-ref.o
BUILTIN_OBJS += builtin-update-server-info.o
BUILTIN_OBJS += builtin-upload-archive.o
+BUILTIN_OBJS += builtin-var.o
BUILTIN_OBJS += builtin-verify-pack.o
BUILTIN_OBJS += builtin-verify-tag.o
BUILTIN_OBJS += builtin-write-tree.o
@@ -794,7 +794,6 @@ ifeq ($(uname_S),SunOS)
NO_MKDTEMP = YesPlease
NO_MKSTEMPS = YesPlease
NO_REGEX = YesPlease
- NO_EXTERNAL_GREP = YesPlease
THREADED_DELTA_SEARCH = YesPlease
ifeq ($(uname_R),5.7)
NEEDS_RESOLV = YesPlease
@@ -913,7 +912,6 @@ ifeq ($(uname_S),IRIX)
# NO_MMAP. If you suspect that your compiler is not affected by this
# issue, comment out the NO_MMAP statement.
NO_MMAP = YesPlease
- NO_EXTERNAL_GREP = UnfortunatelyYes
SNPRINTF_RETURNS_BOGUS = YesPlease
SHELL_PATH = /usr/gnu/bin/bash
NEEDS_LIBGEN = YesPlease
@@ -933,7 +931,6 @@ ifeq ($(uname_S),IRIX64)
# NO_MMAP. If you suspect that your compiler is not affected by this
# issue, comment out the NO_MMAP statement.
NO_MMAP = YesPlease
- NO_EXTERNAL_GREP = UnfortunatelyYes
SNPRINTF_RETURNS_BOGUS = YesPlease
SHELL_PATH=/usr/gnu/bin/bash
NEEDS_LIBGEN = YesPlease
@@ -985,15 +982,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 +1033,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 +1049,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 +1098,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 +1109,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
@@ -1340,9 +1345,6 @@ endif
ifdef DIR_HAS_BSD_GROUP_SEMANTICS
COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
endif
-ifdef NO_EXTERNAL_GREP
- BASIC_CFLAGS += -DNO_EXTERNAL_GREP
-endif
ifdef UNRELIABLE_FSTAT
BASIC_CFLAGS += -DUNRELIABLE_FSTAT
endif
@@ -1476,20 +1478,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 +1643,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 +1683,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 +1740,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 +1750,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 +1762,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
@@ -1780,8 +1781,10 @@ TEST_PROGRAMS_NEED_X += test-genrandom
TEST_PROGRAMS_NEED_X += test-match-trees
TEST_PROGRAMS_NEED_X += test-parse-options
TEST_PROGRAMS_NEED_X += test-path-utils
+TEST_PROGRAMS_NEED_X += test-run-command
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 +1879,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 +1893,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 +2010,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/advice.c b/advice.c
index cb666acc3c..936d98ba2b 100644
--- a/advice.c
+++ b/advice.c
@@ -3,6 +3,8 @@
int advice_push_nonfastforward = 1;
int advice_status_hints = 1;
int advice_commit_before_merge = 1;
+int advice_resolve_conflict = 1;
+int advice_implicit_identity = 1;
static struct {
const char *name;
@@ -11,6 +13,8 @@ static struct {
{ "pushnonfastforward", &advice_push_nonfastforward },
{ "statushints", &advice_status_hints },
{ "commitbeforemerge", &advice_commit_before_merge },
+ { "resolveconflict", &advice_resolve_conflict },
+ { "implicitidentity", &advice_implicit_identity },
};
int git_default_advice_config(const char *var, const char *value)
@@ -27,3 +31,17 @@ int git_default_advice_config(const char *var, const char *value)
return 0;
}
+
+void NORETURN die_resolve_conflict(const char *me)
+{
+ if (advice_resolve_conflict)
+ /*
+ * Message used both when 'git commit' fails and when
+ * other commands doing a merge do.
+ */
+ die("'%s' is not possible because you have unmerged files.\n"
+ "Please, fix them up in the work tree, and then use 'git add/rm <file>' as\n"
+ "appropriate to mark resolution and make a commit, or use 'git commit -a'.", me);
+ else
+ die("'%s' is not possible because you have unmerged files.", me);
+}
diff --git a/advice.h b/advice.h
index 3de5000d8c..9b7a3ad1ca 100644
--- a/advice.h
+++ b/advice.h
@@ -1,10 +1,16 @@
#ifndef ADVICE_H
#define ADVICE_H
+#include "git-compat-util.h"
+
extern int advice_push_nonfastforward;
extern int advice_status_hints;
extern int advice_commit_before_merge;
+extern int advice_resolve_conflict;
+extern int advice_implicit_identity;
int git_default_advice_config(const char *var, const char *value);
+extern void NORETURN die_resolve_conflict(const char *me);
+
#endif /* ADVICE_H */
diff --git a/archive.c b/archive.c
index 55b273246e..d700af3b62 100644
--- a/archive.c
+++ b/archive.c
@@ -87,8 +87,8 @@ static void setup_archive_check(struct git_attr_check *check)
static struct git_attr *attr_export_subst;
if (!attr_export_ignore) {
- attr_export_ignore = git_attr("export-ignore", 13);
- attr_export_subst = git_attr("export-subst", 12);
+ attr_export_ignore = git_attr("export-ignore");
+ attr_export_subst = git_attr("export-subst");
}
check[0].attr = attr_export_ignore;
check[1].attr = attr_export_subst;
@@ -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/attr.c b/attr.c
index 55bdb7cdeb..f5346ed32a 100644
--- a/attr.c
+++ b/attr.c
@@ -65,7 +65,7 @@ static int invalid_attr_name(const char *name, int namelen)
return 0;
}
-struct git_attr *git_attr(const char *name, int len)
+static struct git_attr *git_attr_internal(const char *name, int len)
{
unsigned hval = hash_name(name, len);
unsigned pos = hval % HASHSIZE;
@@ -95,6 +95,11 @@ struct git_attr *git_attr(const char *name, int len)
return a;
}
+struct git_attr *git_attr(const char *name)
+{
+ return git_attr_internal(name, strlen(name));
+}
+
/*
* .gitattributes file is one line per record, each of which is
*
@@ -162,7 +167,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
else {
e->setto = xmemdupz(equals + 1, ep - equals - 1);
}
- e->attr = git_attr(cp, len);
+ e->attr = git_attr_internal(cp, len);
}
(*num_attr)++;
return ep + strspn(ep, blank);
@@ -221,7 +226,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
sizeof(struct attr_state) * num_attr +
(is_macro ? 0 : namelen + 1));
if (is_macro)
- res->u.attr = git_attr(name, namelen);
+ res->u.attr = git_attr_internal(name, namelen);
else {
res->u.pattern = (char *)&(res->state[num_attr]);
memcpy(res->u.pattern, name, namelen);
diff --git a/attr.h b/attr.h
index 69b5767ebc..450f49d648 100644
--- a/attr.h
+++ b/attr.h
@@ -8,7 +8,7 @@ struct git_attr;
* Given a string, return the gitattribute object that
* corresponds to it.
*/
-struct git_attr *git_attr(const char *, int);
+struct git_attr *git_attr(const char *);
/* Internal use */
extern const char git_attr__true[];
diff --git a/base85.c b/base85.c
index b417a15bbc..e459feebbf 100644
--- a/base85.c
+++ b/base85.c
@@ -57,14 +57,8 @@ int decode_85(char *dst, const char *buffer, int len)
de = de85[ch];
if (--de < 0)
return error("invalid base85 alphabet %c", ch);
- /*
- * Detect overflow. The largest
- * 5-letter possible is "|NsC0" to
- * encode 0xffffffff, and "|NsC" gives
- * 0x03030303 at this point (i.e.
- * 0xffffffff = 0x03030303 * 85).
- */
- if (0x03030303 < acc ||
+ /* Detect overflow. */
+ if (0xffffffff / 85 < acc ||
0xffffffff - de < (acc *= 85))
return error("invalid base85 sequence %.5s", buffer-5);
acc += de;
@@ -84,8 +78,6 @@ int decode_85(char *dst, const char *buffer, int len)
void encode_85(char *buf, const unsigned char *data, int bytes)
{
- prep_base85();
-
say("encode 85");
while (bytes) {
unsigned acc = 0;
@@ -118,7 +110,7 @@ int main(int ac, char **av)
int len = strlen(av[2]);
encode_85(buf, av[2], len);
if (len <= 26) len = len + 'A' - 1;
- else len = len + 'a' - 26 + 1;
+ else len = len + 'a' - 26 - 1;
printf("encoded: %c%s\n", len, buf);
return 0;
}
diff --git a/bisect.c b/bisect.c
index f1a1f84aa0..6dc27ee7a6 100644
--- a/bisect.c
+++ b/bisect.c
@@ -593,7 +593,7 @@ struct commit_list *filter_skipped(struct commit_list *list,
* is increased by one between each call, but that should not matter
* for this application.
*/
-int get_prn(int count) {
+static int get_prn(int count) {
count = count * 1103515245 + 12345;
return ((unsigned)(count/65536) % PRN_MODULO);
}
@@ -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/bisect.h b/bisect.h
index 82f8fc1910..0862ce56d7 100644
--- a/bisect.h
+++ b/bisect.h
@@ -27,8 +27,6 @@ struct rev_list_info {
const char *header_prefix;
};
-extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all);
-
extern int bisect_next_all(const char *prefix);
extern int estimate_bisect_steps(int all);
diff --git a/blob.c b/blob.c
index bd7d078e1a..ae320bd8fa 100644
--- a/blob.c
+++ b/blob.c
@@ -23,24 +23,3 @@ int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size)
item->object.parsed = 1;
return 0;
}
-
-int parse_blob(struct blob *item)
-{
- enum object_type type;
- void *buffer;
- unsigned long size;
- int ret;
-
- if (item->object.parsed)
- return 0;
- buffer = read_sha1_file(item->object.sha1, &type, &size);
- if (!buffer)
- return error("Could not read %s",
- sha1_to_hex(item->object.sha1));
- if (type != OBJ_BLOB)
- return error("Object %s not a blob",
- sha1_to_hex(item->object.sha1));
- ret = parse_blob_buffer(item, buffer, size);
- free(buffer);
- return ret;
-}
diff --git a/blob.h b/blob.h
index ea5d9e9f8b..59b394eea3 100644
--- a/blob.h
+++ b/blob.h
@@ -13,6 +13,13 @@ struct blob *lookup_blob(const unsigned char *sha1);
int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size);
-int parse_blob(struct blob *item);
+/**
+ * Blobs do not contain references to other objects and do not have
+ * structured data that needs parsing. However, code may use the
+ * "parsed" bit in the struct object for a blob to determine whether
+ * its content has been found to actually be available, so
+ * parse_blob_buffer() is used (by object.c) to flag that the object
+ * has been read successfully from the database.
+ **/
#endif /* BLOB_H */
diff --git a/branch.c b/branch.c
index 05ef3f5c9c..9e1f63ed8d 100644
--- a/branch.c
+++ b/branch.c
@@ -49,9 +49,19 @@ static int should_setup_rebase(const char *origin)
void install_branch_config(int flag, const char *local, const char *origin, const char *remote)
{
+ const char *shortname = remote + 11;
+ int remote_is_branch = !prefixcmp(remote, "refs/heads/");
struct strbuf key = STRBUF_INIT;
int rebasing = should_setup_rebase(origin);
+ if (remote_is_branch
+ && !strcmp(local, shortname)
+ && !origin) {
+ warning("Not setting branch %s as its own upstream.",
+ local);
+ return;
+ }
+
strbuf_addf(&key, "branch.%s.remote", local);
git_config_set(key.buf, origin ? origin : ".");
@@ -71,8 +81,8 @@ void install_branch_config(int flag, const char *local, const char *origin, cons
strbuf_addstr(&key, origin ? "remote" : "local");
/* Are we tracking a proper "branch"? */
- if (!prefixcmp(remote, "refs/heads/")) {
- strbuf_addf(&key, " branch %s", remote + 11);
+ if (remote_is_branch) {
+ strbuf_addf(&key, " branch %s", shortname);
if (origin)
strbuf_addf(&key, " from %s", origin);
}
@@ -108,6 +118,7 @@ static int setup_tracking(const char *new_ref, const char *orig_ref,
switch (track) {
case BRANCH_TRACK_ALWAYS:
case BRANCH_TRACK_EXPLICIT:
+ case BRANCH_TRACK_OVERRIDE:
break;
default:
return 1;
@@ -128,18 +139,25 @@ void create_branch(const char *head,
const char *name, const char *start_name,
int force, int reflog, enum branch_track track)
{
- struct ref_lock *lock;
+ struct ref_lock *lock = NULL;
struct commit *commit;
unsigned char sha1[20];
char *real_ref, msg[PATH_MAX + 20];
struct strbuf ref = STRBUF_INIT;
int forcing = 0;
+ int dont_change_ref = 0;
+ int explicit_tracking = 0;
+
+ if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
+ explicit_tracking = 1;
if (strbuf_check_branch_ref(&ref, name))
die("'%s' is not a valid branch name.", name);
if (resolve_ref(ref.buf, sha1, 1, NULL)) {
- if (!force)
+ if (!force && track == BRANCH_TRACK_OVERRIDE)
+ dont_change_ref = 1;
+ else if (!force)
die("A branch named '%s' already exists.", name);
else if (!is_bare_repository() && !strcmp(head, name))
die("Cannot force update the current branch.");
@@ -153,12 +171,12 @@ void create_branch(const char *head,
switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
case 0:
/* Not branching from any existing branch */
- if (track == BRANCH_TRACK_EXPLICIT)
+ if (explicit_tracking)
die("Cannot setup tracking information; starting point is not a branch.");
break;
case 1:
/* Unique completion -- good, only if it is a real ref */
- if (track == BRANCH_TRACK_EXPLICIT && !strcmp(real_ref, "HEAD"))
+ if (explicit_tracking && !strcmp(real_ref, "HEAD"))
die("Cannot setup tracking information; starting point is not a branch.");
break;
default:
@@ -170,9 +188,11 @@ void create_branch(const char *head,
die("Not a valid branch point: '%s'.", start_name);
hashcpy(sha1, commit->object.sha1);
- lock = lock_any_ref_for_update(ref.buf, NULL, 0);
- if (!lock)
- die_errno("Failed to lock ref for update");
+ if (!dont_change_ref) {
+ lock = lock_any_ref_for_update(ref.buf, NULL, 0);
+ if (!lock)
+ die_errno("Failed to lock ref for update");
+ }
if (reflog)
log_all_ref_updates = 1;
@@ -180,15 +200,16 @@ void create_branch(const char *head,
if (forcing)
snprintf(msg, sizeof msg, "branch: Reset from %s",
start_name);
- else
+ else if (!dont_change_ref)
snprintf(msg, sizeof msg, "branch: Created from %s",
start_name);
if (real_ref && track)
setup_tracking(name, real_ref, track);
- if (write_ref_sha1(lock, sha1, msg) < 0)
- die_errno("Failed to write ref");
+ if (!dont_change_ref)
+ if (write_ref_sha1(lock, sha1, msg) < 0)
+ die_errno("Failed to write ref");
strbuf_release(&ref);
free(real_ref);
diff --git a/builtin-add.c b/builtin-add.c
index cb6e5906fb..2705f8d057 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -11,6 +11,7 @@
#include "run-command.h"
#include "parse-options.h"
#include "diff.h"
+#include "diffcore.h"
#include "revision.h"
static const char * const builtin_add_usage[] = {
@@ -20,6 +21,81 @@ static const char * const builtin_add_usage[] = {
static int patch_interactive, add_interactive, edit_interactive;
static int take_worktree_changes;
+struct update_callback_data
+{
+ int flags;
+ int add_errors;
+};
+
+static void update_callback(struct diff_queue_struct *q,
+ struct diff_options *opt, void *cbdata)
+{
+ int i;
+ struct update_callback_data *data = cbdata;
+
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ const char *path = p->one->path;
+ switch (p->status) {
+ default:
+ die("unexpected diff status %c", p->status);
+ case DIFF_STATUS_UNMERGED:
+ /*
+ * ADD_CACHE_IGNORE_REMOVAL is unset if "git
+ * add -u" is calling us, In such a case, a
+ * missing work tree file needs to be removed
+ * if there is an unmerged entry at stage #2,
+ * but such a diff record is followed by
+ * another with DIFF_STATUS_DELETED (and if
+ * there is no stage #2, we won't see DELETED
+ * nor MODIFIED). We can simply continue
+ * either way.
+ */
+ if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL))
+ continue;
+ /*
+ * Otherwise, it is "git add path" is asking
+ * to explicitly add it; we fall through. A
+ * missing work tree file is an error and is
+ * caught by add_file_to_index() in such a
+ * case.
+ */
+ case DIFF_STATUS_MODIFIED:
+ case DIFF_STATUS_TYPE_CHANGED:
+ if (add_file_to_index(&the_index, path, data->flags)) {
+ if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
+ die("updating files failed");
+ data->add_errors++;
+ }
+ break;
+ case DIFF_STATUS_DELETED:
+ if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
+ break;
+ if (!(data->flags & ADD_CACHE_PRETEND))
+ remove_file_from_index(&the_index, path);
+ if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
+ printf("remove '%s'\n", path);
+ break;
+ }
+ }
+}
+
+int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
+{
+ struct update_callback_data data;
+ struct rev_info rev;
+ init_revisions(&rev, prefix);
+ setup_revisions(0, NULL, &rev, NULL);
+ rev.prune_data = pathspec;
+ rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = update_callback;
+ data.flags = flags;
+ data.add_errors = 0;
+ rev.diffopt.format_callback_data = &data;
+ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
+ return !!data.add_errors;
+}
+
static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
{
int num_unmatched = 0, i;
diff --git a/builtin-apply.c b/builtin-apply.c
index 36e2f9dda5..2a1004d025 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -404,6 +404,9 @@ static char *squash_slash(char *name)
{
int i = 0, j = 0;
+ if (!name)
+ return NULL;
+
while (name[i]) {
if ((name[j++] = name[i++]) == '/')
while (name[i] == '/')
@@ -416,7 +419,10 @@ static char *squash_slash(char *name)
static char *find_name(const char *line, char *def, int p_value, int terminate)
{
int len;
- const char *start = line;
+ const char *start = NULL;
+
+ if (p_value == 0)
+ start = line;
if (*line == '"') {
struct strbuf name = STRBUF_INIT;
@@ -686,7 +692,7 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name,
if (isnull)
die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
another = find_name(line, NULL, p_value, TERM_TAB);
- if (!another || memcmp(another, name, len))
+ if (!another || memcmp(another, name, len + 1))
die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
free(another);
return orig_name;
@@ -1199,7 +1205,8 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
continue;
if (!patch->old_name && !patch->new_name) {
if (!patch->def_name)
- die("git diff header lacks filename information (line %d)", linenr);
+ die("git diff header lacks filename information when removing "
+ "%d leading pathname components (line %d)" , p_value, linenr);
patch->old_name = patch->new_name = patch->def_name;
}
patch->is_toplevel_relative = 1;
@@ -2666,7 +2673,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 446d6bff30..3fb41364a5 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-branch.c b/builtin-branch.c
index ddc9f2dab7..a28a13986d 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -93,9 +93,60 @@ static const char *branch_get_color(enum color_branch ix)
return "";
}
+static int branch_merged(int kind, const char *name,
+ struct commit *rev, struct commit *head_rev)
+{
+ /*
+ * This checks whether the merge bases of branch and HEAD (or
+ * the other branch this branch builds upon) contains the
+ * branch, which means that the branch has already been merged
+ * safely to HEAD (or the other branch).
+ */
+ struct commit *reference_rev = NULL;
+ const char *reference_name = NULL;
+ int merged;
+
+ if (kind == REF_LOCAL_BRANCH) {
+ struct branch *branch = branch_get(name);
+ unsigned char sha1[20];
+
+ if (branch &&
+ branch->merge &&
+ branch->merge[0] &&
+ branch->merge[0]->dst &&
+ (reference_name =
+ resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
+ reference_rev = lookup_commit_reference(sha1);
+ }
+ if (!reference_rev)
+ reference_rev = head_rev;
+
+ merged = in_merge_bases(rev, &reference_rev, 1);
+
+ /*
+ * After the safety valve is fully redefined to "check with
+ * upstream, if any, otherwise with HEAD", we should just
+ * return the result of the in_merge_bases() above without
+ * any of the following code, but during the transition period,
+ * a gentle reminder is in order.
+ */
+ if ((head_rev != reference_rev) &&
+ in_merge_bases(rev, &head_rev, 1) != merged) {
+ if (merged)
+ warning("deleting branch '%s' that has been merged to\n"
+ " '%s', but it is not yet merged to HEAD.",
+ name, reference_name);
+ else
+ warning("not deleting branch '%s' that is not yet merged to\n"
+ " '%s', even though it is merged to HEAD.",
+ name, reference_name);
+ }
+ return merged;
+}
+
static int delete_branches(int argc, const char **argv, int force, int kinds)
{
- struct commit *rev, *head_rev = head_rev;
+ struct commit *rev, *head_rev = NULL;
unsigned char sha1[20];
char *name = NULL;
const char *fmt, *remote;
@@ -148,15 +199,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
continue;
}
- /* This checks whether the merge bases of branch and
- * HEAD contains branch -- which means that the HEAD
- * contains everything in both.
- */
-
- if (!force &&
- !in_merge_bases(rev, &head_rev, 1)) {
- error("The branch '%s' is not an ancestor of "
- "your current HEAD.\n"
+ if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) {
+ error("The branch '%s' is not fully merged.\n"
"If you are sure you want to delete it, "
"run 'git branch -D %s'.", bname.buf, bname.buf);
ret = 1;
@@ -564,6 +608,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT__VERBOSE(&verbose),
OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))",
BRANCH_TRACK_EXPLICIT),
+ OPT_SET_INT( 0, "set-upstream", &track, "change upstream info",
+ BRANCH_TRACK_OVERRIDE),
OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"),
OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches",
REF_REMOTE_BRANCH),
diff --git a/builtin-check-attr.c b/builtin-check-attr.c
index 8bd0430098..3016d29caa 100644
--- a/builtin-check-attr.c
+++ b/builtin-check-attr.c
@@ -106,7 +106,7 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
const char *name;
struct git_attr *a;
name = argv[i];
- a = git_attr(name, strlen(name));
+ a = git_attr(name);
if (!a)
return error("%s: not a valid attribute name", name);
check[i].attr = a;
diff --git a/builtin-checkout.c b/builtin-checkout.c
index 64f3a11ae1..527781728e 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -17,6 +17,7 @@
#include "blob.h"
#include "xdiff-interface.h"
#include "ll-merge.h"
+#include "resolve-undo.h"
static const char * const checkout_usage[] = {
"git checkout [options] <branch>",
@@ -167,7 +168,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);
@@ -234,6 +235,10 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
if (report_path_error(ps_matched, pathspec, 0))
return 1;
+ /* "checkout -m path" to recreate conflicted state */
+ if (opts->merge)
+ unmerge_cache(pathspec);
+
/* Any unmerged paths? */
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
@@ -370,6 +375,7 @@ static int merge_working_tree(struct checkout_opts *opts,
if (read_cache_preload(NULL) < 0)
return error("corrupt index file");
+ resolve_undo_clear();
if (opts->force) {
ret = reset_tree(new->commit->tree, opts, 1);
if (ret)
@@ -397,7 +403,7 @@ static int merge_working_tree(struct checkout_opts *opts,
topts.initial_checkout = is_cache_unborn();
topts.update = 1;
topts.merge = 1;
- topts.gently = opts->merge;
+ topts.gently = opts->merge && old->commit;
topts.verbose_update = !opts->quiet;
topts.fn = twoway_merge;
topts.dir = xcalloc(1, sizeof(*topts.dir));
@@ -422,7 +428,13 @@ static int merge_working_tree(struct checkout_opts *opts,
struct merge_options o;
if (!opts->merge)
return 1;
- parse_commit(old->commit);
+
+ /*
+ * Without old->commit, the below is the same as
+ * the two-tree unpack we already tried and failed.
+ */
+ if (!old->commit)
+ return 1;
/* Do more real merge */
@@ -690,7 +702,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
@@ -716,7 +731,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 &&
@@ -743,8 +758,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
new.name = arg;
if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
setup_branch_path(&new);
- if (resolve_ref(new.path, rev, 1, NULL))
- new.commit = lookup_commit_reference(rev);
+
+ if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) &&
+ resolve_ref(new.path, rev, 1, NULL))
+ ;
else
new.path = NULL;
parse_commit(new.commit);
diff --git a/builtin-clean.c b/builtin-clean.c
index 28cdcd0274..3a70fa81bd 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 5df8b0f72c..58bacbd552 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 073fe90ba1..55676fd874 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -36,7 +36,20 @@ static const char * const builtin_status_usage[] = {
NULL
};
+static const char implicit_ident_advice[] =
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name Your Name\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"If the identity used for this commit is wrong, you can fix it with:\n"
+"\n"
+" git commit --amend --author='Your Name <you@example.com>'\n";
+
static unsigned char head_sha1[20];
+
static char *use_message_buffer;
static const char commit_editmsg[] = "COMMIT_EDITMSG";
static struct lock_file index_lock; /* real index */
@@ -68,7 +81,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 +120,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 +197,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 +218,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");
@@ -236,6 +258,16 @@ static void create_base_index(void)
exit(128); /* We've already reported the error, finish dying */
}
+static void refresh_cache_or_die(int refresh_flags)
+{
+ /*
+ * refresh_flags contains REFRESH_QUIET, so the only errors
+ * are for unmerged entries.
+ */
+ if (refresh_cache(refresh_flags | REFRESH_IN_PORCELAIN))
+ die_resolve_conflict("commit");
+}
+
static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
{
int fd;
@@ -275,7 +307,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
if (all || (also && pathspec && *pathspec)) {
int fd = hold_locked_index(&index_lock, 1);
add_files_to_cache(also ? prefix : NULL, pathspec, 0);
- refresh_cache(refresh_flags);
+ refresh_cache_or_die(refresh_flags);
if (write_cache(fd, active_cache, active_nr) ||
close_lock_file(&index_lock))
die("unable to write new_index file");
@@ -294,7 +326,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
*/
if (!pathspec || !*pathspec) {
fd = hold_locked_index(&index_lock, 1);
- refresh_cache(refresh_flags);
+ refresh_cache_or_die(refresh_flags);
if (write_cache(fd, active_cache, active_nr) ||
commit_locked_index(&index_lock))
die("unable to write new_index file");
@@ -582,7 +614,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;
@@ -624,7 +656,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
author_ident);
free(author_ident);
- if (!user_ident_explicitly_given)
+ if (!user_ident_sufficiently_given())
fprintf(fp,
"%s"
"# Committer: %s\n",
@@ -1015,7 +1047,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
s.pathspec = get_pathspec(prefix, argv);
read_cache();
- refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
+ refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
s.in_merge = in_merge;
wt_status_collect(&s);
@@ -1046,9 +1078,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
{
struct rev_info rev;
struct commit *commit;
- static const char *format = "format:%h] %s";
+ struct strbuf format = STRBUF_INIT;
unsigned char junk_sha1[20];
const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
+ struct pretty_print_context pctx = {0};
+ struct strbuf author_ident = STRBUF_INIT;
+ struct strbuf committer_ident = STRBUF_INIT;
commit = lookup_commit(sha1);
if (!commit)
@@ -1056,6 +1091,25 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
if (!commit || parse_commit(commit))
die("could not parse newly created commit");
+ strbuf_addstr(&format, "format:%h] %s");
+
+ format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
+ format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
+ if (strbuf_cmp(&author_ident, &committer_ident)) {
+ strbuf_addstr(&format, "\n Author: ");
+ strbuf_addbuf_percentquote(&format, &author_ident);
+ }
+ if (!user_ident_sufficiently_given()) {
+ strbuf_addstr(&format, "\n Committer: ");
+ strbuf_addbuf_percentquote(&format, &committer_ident);
+ if (advice_implicit_identity) {
+ strbuf_addch(&format, '\n');
+ strbuf_addstr(&format, implicit_ident_advice);
+ }
+ }
+ strbuf_release(&author_ident);
+ strbuf_release(&committer_ident);
+
init_revisions(&rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
@@ -1066,7 +1120,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
rev.verbose_header = 1;
rev.show_root_diff = 1;
- get_commit_format(format, &rev);
+ get_commit_format(format.buf, &rev);
rev.always_show_header = 0;
rev.diffopt.detect_rename = 1;
rev.diffopt.rename_limit = 100;
@@ -1085,10 +1139,11 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
struct pretty_print_context ctx = {0};
struct strbuf buf = STRBUF_INIT;
ctx.date_mode = DATE_NORMAL;
- format_commit_message(commit, format + 7, &buf, &ctx);
+ format_commit_message(commit, format.buf + 7, &buf, &ctx);
printf("%s\n", buf.buf);
strbuf_release(&buf);
}
+ strbuf_release(&format);
}
static int git_commit_config(const char *k, const char *v, void *cb)
@@ -1097,6 +1152,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 +1301,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-config.c b/builtin-config.c
index a2d656edb3..2e3ef911d6 100644
--- a/builtin-config.c
+++ b/builtin-config.c
@@ -45,6 +45,7 @@ static int end_null;
#define TYPE_BOOL (1<<0)
#define TYPE_INT (1<<1)
#define TYPE_BOOL_OR_INT (1<<2)
+#define TYPE_PATH (1<<3)
static struct option builtin_config_options[] = {
OPT_GROUP("Config file location"),
@@ -69,6 +70,7 @@ static struct option builtin_config_options[] = {
OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL),
OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT),
OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT),
+ OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH),
OPT_GROUP("Other"),
OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
OPT_END(),
@@ -94,6 +96,7 @@ static int show_config(const char *key_, const char *value_, void *cb)
{
char value[256];
const char *vptr = value;
+ int must_free_vptr = 0;
int dup_error = 0;
if (!use_key_regexp && strcmp(key_, key))
@@ -123,6 +126,9 @@ static int show_config(const char *key_, const char *value_, void *cb)
vptr = v ? "true" : "false";
else
sprintf(value, "%d", v);
+ } else if (types == TYPE_PATH) {
+ git_config_pathname(&vptr, key_, value_);
+ must_free_vptr = 1;
}
else
vptr = value_?value_:"";
@@ -133,6 +139,12 @@ static int show_config(const char *key_, const char *value_, void *cb)
}
else
printf("%s%c", vptr, term);
+ if (must_free_vptr)
+ /* If vptr must be freed, it's a pointer to a
+ * dynamically allocated buffer, it's safe to cast to
+ * const.
+ */
+ free((char *)vptr);
return 0;
}
@@ -215,7 +227,13 @@ static char *normalize_value(const char *key, const char *value)
if (!value)
return NULL;
- if (types == 0)
+ if (types == 0 || types == TYPE_PATH)
+ /*
+ * We don't do normalization for TYPE_PATH here: If
+ * the path is like ~/foobar/, we prefer to store
+ * "~/foobar/" in the config file, and to expand the ~
+ * when retrieving the value.
+ */
normalized = xstrdup(value);
else {
normalized = xmalloc(64);
diff --git a/builtin-gc.c b/builtin-gc.c
index 093517e390..c304638b78 100644
--- a/builtin-gc.c
+++ b/builtin-gc.c
@@ -180,12 +180,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
char buf[80];
struct option builtin_gc_options[] = {
+ OPT__QUIET(&quiet),
{ OPTION_STRING, 0, "prune", &prune_expire, "date",
"prune unreferenced objects",
PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
- OPT_BOOLEAN('q', "quiet", &quiet, "suppress progress reports"),
OPT_END()
};
diff --git a/builtin-grep.c b/builtin-grep.c
index a5b6719a1a..da854fa94f 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -14,14 +14,7 @@
#include "userdiff.h"
#include "grep.h"
#include "quote.h"
-
-#ifndef NO_EXTERNAL_GREP
-#ifdef __unix__
-#define NO_EXTERNAL_GREP 0
-#else
-#define NO_EXTERNAL_GREP 1
-#endif
-#endif
+#include "dir.h"
static char const * const grep_usage[] = {
"git grep [options] [-e] <pattern> [<rev>...] [[--] path...]",
@@ -42,8 +35,6 @@ static int grep_config(const char *var, const char *value, void *cb)
opt->color = git_config_colorbool(var, value, -1);
return 0;
}
- if (!strcmp(var, "color.grep.external"))
- return git_config_string(&(opt->color_external), var, value);
if (!strcmp(var, "color.grep.match")) {
if (!value)
return config_error_nonbool(var);
@@ -191,8 +182,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 +196,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);
@@ -215,292 +205,12 @@ static int grep_file(struct grep_opt *opt, const char *filename)
return i;
}
-#if !NO_EXTERNAL_GREP
-static int exec_grep(int argc, const char **argv)
-{
- pid_t pid;
- int status;
-
- argv[argc] = NULL;
- pid = fork();
- if (pid < 0)
- return pid;
- if (!pid) {
- execvp("grep", (char **) argv);
- exit(255);
- }
- while (waitpid(pid, &status, 0) < 0) {
- if (errno == EINTR)
- continue;
- return -1;
- }
- if (WIFEXITED(status)) {
- if (!WEXITSTATUS(status))
- return 1;
- return 0;
- }
- return -1;
-}
-
-#define MAXARGS 1000
-#define ARGBUF 4096
-#define push_arg(a) do { \
- if (nr < MAXARGS) argv[nr++] = (a); \
- else die("maximum number of args exceeded"); \
- } while (0)
-
-/*
- * If you send a singleton filename to grep, it does not give
- * the name of the file. GNU grep has "-H" but we would want
- * that behaviour in a portable way.
- *
- * So we keep two pathnames in argv buffer unsent to grep in
- * the main loop if we need to do more than one grep.
- */
-static int flush_grep(struct grep_opt *opt,
- int argc, int arg0, const char **argv, int *kept)
-{
- int status;
- int count = argc - arg0;
- const char *kept_0 = NULL;
-
- if (count <= 2) {
- /*
- * Because we keep at least 2 paths in the call from
- * the main loop (i.e. kept != NULL), and MAXARGS is
- * far greater than 2, this usually is a call to
- * conclude the grep. However, the user could attempt
- * to overflow the argv buffer by giving too many
- * options to leave very small number of real
- * arguments even for the call in the main loop.
- */
- if (kept)
- die("insanely many options to grep");
-
- /*
- * If we have two or more paths, we do not have to do
- * anything special, but we need to push /dev/null to
- * get "-H" behaviour of GNU grep portably but when we
- * are not doing "-l" nor "-L" nor "-c".
- */
- if (count == 1 &&
- !opt->name_only &&
- !opt->unmatch_name_only &&
- !opt->count) {
- argv[argc++] = "/dev/null";
- argv[argc] = NULL;
- }
- }
-
- else if (kept) {
- /*
- * Called because we found many paths and haven't finished
- * iterating over the cache yet. We keep two paths
- * for the concluding call. argv[argc-2] and argv[argc-1]
- * has the last two paths, so save the first one away,
- * replace it with NULL while sending the list to grep,
- * and recover them after we are done.
- */
- *kept = 2;
- kept_0 = argv[argc-2];
- argv[argc-2] = NULL;
- argc -= 2;
- }
-
- if (opt->pre_context || opt->post_context) {
- /*
- * grep handles hunk marks between files, but we need to
- * do that ourselves between multiple calls.
- */
- if (opt->show_hunk_mark)
- write_or_die(1, "--\n", 3);
- else
- opt->show_hunk_mark = 1;
- }
-
- status = exec_grep(argc, argv);
-
- if (kept_0) {
- /*
- * Then recover them. Now the last arg is beyond the
- * terminating NULL which is at argc, and the second
- * from the last is what we saved away in kept_0
- */
- argv[arg0++] = kept_0;
- argv[arg0] = argv[argc+1];
- }
- return status;
-}
-
-static void grep_add_color(struct strbuf *sb, const char *escape_seq)
-{
- size_t orig_len = sb->len;
-
- while (*escape_seq) {
- if (*escape_seq == 'm')
- strbuf_addch(sb, ';');
- else if (*escape_seq != '\033' && *escape_seq != '[')
- strbuf_addch(sb, *escape_seq);
- escape_seq++;
- }
- if (sb->len > orig_len && sb->buf[sb->len - 1] == ';')
- strbuf_setlen(sb, sb->len - 1);
-}
-
-static int external_grep(struct grep_opt *opt, const char **paths, int cached)
-{
- int i, nr, argc, hit, len, status;
- const char *argv[MAXARGS+1];
- char randarg[ARGBUF];
- char *argptr = randarg;
- struct grep_pat *p;
-
- if (opt->extended || (opt->relative && opt->prefix_length))
- return -1;
- len = nr = 0;
- push_arg("grep");
- if (opt->fixed)
- push_arg("-F");
- if (opt->linenum)
- push_arg("-n");
- if (!opt->pathname)
- push_arg("-h");
- if (opt->regflags & REG_EXTENDED)
- push_arg("-E");
- if (opt->ignore_case)
- push_arg("-i");
- if (opt->binary == GREP_BINARY_NOMATCH)
- push_arg("-I");
- if (opt->word_regexp)
- push_arg("-w");
- if (opt->name_only)
- push_arg("-l");
- if (opt->unmatch_name_only)
- push_arg("-L");
- if (opt->null_following_name)
- /* in GNU grep git's "-z" translates to "-Z" */
- push_arg("-Z");
- if (opt->count)
- push_arg("-c");
- if (opt->post_context || opt->pre_context) {
- if (opt->post_context != opt->pre_context) {
- if (opt->pre_context) {
- push_arg("-B");
- len += snprintf(argptr, sizeof(randarg)-len,
- "%u", opt->pre_context) + 1;
- if (sizeof(randarg) <= len)
- die("maximum length of args exceeded");
- push_arg(argptr);
- argptr += len;
- }
- if (opt->post_context) {
- push_arg("-A");
- len += snprintf(argptr, sizeof(randarg)-len,
- "%u", opt->post_context) + 1;
- if (sizeof(randarg) <= len)
- die("maximum length of args exceeded");
- push_arg(argptr);
- argptr += len;
- }
- }
- else {
- push_arg("-C");
- len += snprintf(argptr, sizeof(randarg)-len,
- "%u", opt->post_context) + 1;
- if (sizeof(randarg) <= len)
- die("maximum length of args exceeded");
- push_arg(argptr);
- argptr += len;
- }
- }
- for (p = opt->pattern_list; p; p = p->next) {
- push_arg("-e");
- push_arg(p->pattern);
- }
- if (opt->color) {
- struct strbuf sb = STRBUF_INIT;
-
- grep_add_color(&sb, opt->color_match);
- setenv("GREP_COLOR", sb.buf, 1);
-
- strbuf_reset(&sb);
- strbuf_addstr(&sb, "mt=");
- grep_add_color(&sb, opt->color_match);
- strbuf_addstr(&sb, ":sl=:cx=:fn=:ln=:bn=:se=");
- setenv("GREP_COLORS", sb.buf, 1);
-
- strbuf_release(&sb);
-
- if (opt->color_external && strlen(opt->color_external) > 0)
- push_arg(opt->color_external);
- } else {
- unsetenv("GREP_COLOR");
- unsetenv("GREP_COLORS");
- }
- unsetenv("GREP_OPTIONS");
-
- hit = 0;
- argc = nr;
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- char *name;
- int kept;
- if (!S_ISREG(ce->ce_mode))
- continue;
- if (!pathspec_matches(paths, ce->name, opt->max_depth))
- continue;
- name = ce->name;
- if (name[0] == '-') {
- int len = ce_namelen(ce);
- name = xmalloc(len + 3);
- memcpy(name, "./", 2);
- memcpy(name + 2, ce->name, len + 1);
- }
- argv[argc++] = name;
- if (MAXARGS <= argc) {
- status = flush_grep(opt, argc, nr, argv, &kept);
- if (0 < status)
- hit = 1;
- argc = nr + kept;
- }
- if (ce_stage(ce)) {
- do {
- i++;
- } while (i < active_nr &&
- !strcmp(ce->name, active_cache[i]->name));
- i--; /* compensate for loop control */
- }
- }
- if (argc > nr) {
- status = flush_grep(opt, argc, nr, argv, NULL);
- if (0 < status)
- hit = 1;
- }
- return hit;
-}
-#endif
-
-static int grep_cache(struct grep_opt *opt, const char **paths, int cached,
- int external_grep_allowed)
+static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
{
int hit = 0;
int nr;
read_cache();
-#if !NO_EXTERNAL_GREP
- /*
- * Use the external "grep" command for the case where
- * we grep through the checked-out files. It tends to
- * be a lot more optimized
- */
- if (!cached && external_grep_allowed) {
- hit = external_grep(opt, paths, cached);
- if (hit >= 0)
- return hit;
- hit = 0;
- }
-#endif
-
for (nr = 0; nr < active_nr; nr++) {
struct cache_entry *ce = active_cache[nr];
if (!S_ISREG(ce->ce_mode))
@@ -512,7 +222,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);
@@ -610,6 +320,21 @@ static int grep_object(struct grep_opt *opt, const char **paths,
die("unable to grep from object of type %s", typename(obj->type));
}
+static int grep_directory(struct grep_opt *opt, const char **paths)
+{
+ struct dir_struct dir;
+ int i, hit = 0;
+
+ memset(&dir, 0, sizeof(dir));
+ setup_standard_excludes(&dir);
+
+ fill_directory(&dir, paths);
+ for (i = 0; i < dir.nr; i++)
+ hit |= grep_file(opt, dir.entries[i]->name);
+ free_grep_patterns(opt);
+ return hit;
+}
+
static int context_callback(const struct option *opt, const char *arg,
int unset)
{
@@ -697,16 +422,19 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
{
int hit = 0;
int cached = 0;
- int external_grep_allowed = 1;
int seen_dashdash = 0;
+ int external_grep_allowed__ignored;
struct grep_opt opt;
struct object_array list = { 0, 0, NULL };
const char **paths = NULL;
int i;
int dummy;
+ int nongit = 0, use_index = 1;
struct option options[] = {
OPT_BOOLEAN(0, "cached", &cached,
"search in index instead of in the work tree"),
+ OPT_BOOLEAN(0, "index", &use_index,
+ "--no-index finds in contents not managed by git"),
OPT_GROUP(""),
OPT_BOOLEAN('v', "invert-match", &opt.invert,
"show non-matching lines"),
@@ -780,18 +508,15 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
OPT_BOOLEAN(0, "all-match", &opt.all_match,
"show only matches from files that match all patterns"),
OPT_GROUP(""),
-#if NO_EXTERNAL_GREP
- OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed,
- "allow calling of grep(1) (ignored by this build)"),
-#else
- OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed,
- "allow calling of grep(1) (default)"),
-#endif
+ OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored,
+ "allow calling of grep(1) (ignored by this build)"),
{ OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage",
PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
OPT_END()
};
+ prefix = setup_git_directory_gently(&nongit);
+
/*
* 'git grep -h', unlike 'git grep -h <pattern>', is a request
* to show usage information and exit.
@@ -829,6 +554,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
PARSE_OPT_STOP_AT_NON_OPTION |
PARSE_OPT_NO_INTERNAL_HELP);
+ if (use_index && nongit)
+ /* die the same way as if we did it at the beginning */
+ setup_git_directory();
+
/* First unrecognized non-option token */
if (argc > 0 && !opt.pattern_list) {
append_grep_pattern(&opt, argv[0], "command line", 0,
@@ -837,8 +566,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
argc--;
}
- if ((opt.color && !opt.color_external) || opt.funcname)
- external_grep_allowed = 0;
if (!opt.pattern_list)
die("no pattern given.");
if (!opt.fixed && opt.ignore_case)
@@ -881,10 +608,18 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
paths[1] = NULL;
}
+ if (!use_index) {
+ if (cached)
+ die("--cached cannot be used with --no-index.");
+ if (list.nr)
+ die("--no-index cannot be used with revs.");
+ return !grep_directory(&opt, paths);
+ }
+
if (!list.nr) {
if (!cached)
setup_work_tree();
- return !grep_cache(&opt, paths, cached, external_grep_allowed);
+ return !grep_cache(&opt, paths, cached);
}
if (cached)
diff --git a/hash-object.c b/builtin-hash-object.c
index 9455dd0709..6a5f5b5f0e 100644
--- a/hash-object.c
+++ b/builtin-hash-object.c
@@ -73,17 +73,14 @@ static const struct option hash_object_options[] = {
OPT_END()
};
-int main(int argc, const char **argv)
+int cmd_hash_object(int argc, const char **argv, const char *prefix)
{
int i;
- const char *prefix = NULL;
int prefix_length = -1;
const char *errstr = NULL;
type = blob_type;
- git_extract_argv0_path(argv[0]);
-
argc = parse_options(argc, argv, NULL, hash_object_options,
hash_object_usage, 0);
diff --git a/builtin-help.c b/builtin-help.c
index 09ad4b04f9..3182a2bec4 100644
--- a/builtin-help.c
+++ b/builtin-help.c
@@ -23,13 +23,14 @@ static struct man_viewer_info_list {
} *man_viewer_info_list;
enum help_format {
+ HELP_FORMAT_NONE,
HELP_FORMAT_MAN,
HELP_FORMAT_INFO,
HELP_FORMAT_WEB,
};
static int show_all = 0;
-static enum help_format help_format = HELP_FORMAT_MAN;
+static enum help_format help_format = HELP_FORMAT_NONE;
static struct option builtin_help_options[] = {
OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
@@ -415,10 +416,12 @@ int cmd_help(int argc, const char **argv, const char *prefix)
{
int nongit;
const char *alias;
+ enum help_format parsed_help_format;
load_command_list("git-", &main_cmds, &other_cmds);
argc = parse_options(argc, argv, prefix, builtin_help_options,
builtin_help_usage, 0);
+ parsed_help_format = help_format;
if (show_all) {
printf("usage: %s\n\n", git_usage_string);
@@ -437,6 +440,9 @@ int cmd_help(int argc, const char **argv, const char *prefix)
setup_git_directory_gently(&nongit);
git_config(git_help_config, NULL);
+ if (parsed_help_format != HELP_FORMAT_NONE)
+ help_format = parsed_help_format;
+
alias = alias_lookup(argv[0]);
if (alias && !is_git_command(argv[0])) {
printf("`git %s' is aliased to `%s'\n", argv[0], alias);
@@ -444,6 +450,7 @@ int cmd_help(int argc, const char **argv, const char *prefix)
}
switch (help_format) {
+ case HELP_FORMAT_NONE:
case HELP_FORMAT_MAN:
show_man_page(argv[0]);
break;
diff --git a/index-pack.c b/builtin-index-pack.c
index 190f372dd8..b4cf8c53e0 100644
--- a/index-pack.c
+++ b/builtin-index-pack.c
@@ -166,7 +166,7 @@ static void use(int bytes)
consumed_bytes += bytes;
}
-static char *open_pack_file(char *pack_name)
+static const char *open_pack_file(const char *pack_name)
{
if (from_stdin) {
input_fd = 0;
@@ -870,18 +870,16 @@ static int git_index_pack_config(const char *k, const char *v, void *cb)
return git_default_config(k, v, cb);
}
-int main(int argc, char **argv)
+int cmd_index_pack(int argc, const char **argv, const char *prefix)
{
int i, fix_thin_pack = 0;
- char *curr_pack, *pack_name = NULL;
- char *curr_index, *index_name = NULL;
+ const char *curr_pack, *curr_index;
+ const char *index_name = NULL, *pack_name = NULL;
const char *keep_name = NULL, *keep_msg = NULL;
char *index_name_buf = NULL, *keep_name_buf = NULL;
struct pack_idx_entry **idx_objects;
unsigned char pack_sha1[20];
- git_extract_argv0_path(argv[0]);
-
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(index_pack_usage);
@@ -906,7 +904,7 @@ int main(int argc, char **argv)
}
for (i = 1; i < argc; i++) {
- char *arg = argv[i];
+ const char *arg = argv[i];
if (*arg == '-') {
if (!strcmp(arg, "--stdin")) {
@@ -1039,9 +1037,9 @@ int main(int argc, char **argv)
free(index_name_buf);
free(keep_name_buf);
if (pack_name == NULL)
- free(curr_pack);
+ free((void *) curr_pack);
if (index_name == NULL)
- free(curr_index);
+ free((void *) curr_index);
return 0;
}
diff --git a/builtin-log.c b/builtin-log.c
index 41b6df490f..8d16832f7e 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -58,6 +58,9 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
usage(builtin_log_usage);
argc = setup_revisions(argc, argv, rev, "HEAD");
+ if (!rev->show_notes_given && !rev->pretty_given)
+ rev->show_notes = 1;
+
if (rev->diffopt.pickaxe || rev->diffopt.filter)
rev->always_show_header = 0;
if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index c9a03e5427..b065061392 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -11,6 +11,8 @@
#include "builtin.h"
#include "tree.h"
#include "parse-options.h"
+#include "resolve-undo.h"
+#include "string-list.h"
static int abbrev;
static int show_deleted;
@@ -18,6 +20,7 @@ static int show_cached;
static int show_others;
static int show_stage;
static int show_unmerged;
+static int show_resolve_undo;
static int show_modified;
static int show_killed;
static int show_valid_bit;
@@ -37,6 +40,8 @@ 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 const char *tag_resolve_undo = "";
static void show_dir_entry(const char *tag, struct dir_entry *ent)
{
@@ -155,6 +160,38 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
write_name_quoted(ce->name + offset, stdout, line_terminator);
}
+static int show_one_ru(struct string_list_item *item, void *cbdata)
+{
+ int offset = prefix_offset;
+ const char *path = item->string;
+ struct resolve_undo_info *ui = item->util;
+ int i, len;
+
+ len = strlen(path);
+ if (len < prefix_len)
+ return 0; /* outside of the prefix */
+ if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched))
+ return 0; /* uninterested */
+ for (i = 0; i < 3; i++) {
+ if (!ui->mode[i])
+ continue;
+ printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
+ abbrev
+ ? find_unique_abbrev(ui->sha1[i], abbrev)
+ : sha1_to_hex(ui->sha1[i]),
+ i + 1);
+ write_name_quoted(path + offset, stdout, line_terminator);
+ }
+ return 0;
+}
+
+static void show_ru_info(const char *prefix)
+{
+ if (!the_index.resolve_undo)
+ return;
+ for_each_string_list(show_one_ru, the_index.resolve_undo, NULL);
+}
+
static void show_files(struct dir_struct *dir, const char *prefix)
{
int i;
@@ -178,7 +215,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 +230,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);
@@ -454,6 +494,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
DIR_HIDE_EMPTY_DIRECTORIES),
OPT_BOOLEAN('u', "unmerged", &show_unmerged,
"show unmerged files in the output"),
+ OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
+ "show resolve-undo information"),
{ OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
"skip files matching pattern",
0, option_parse_exclude },
@@ -481,6 +523,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 +535,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
tag_modified = "C ";
tag_other = "? ";
tag_killed = "K ";
+ tag_skip_worktree = "S ";
+ tag_resolve_undo = "U ";
}
if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)
require_work_tree = 1;
@@ -508,7 +555,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();
@@ -529,7 +575,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
/* With no flags, we default to showing the cached files */
if (!(show_stage | show_deleted | show_others | show_unmerged |
- show_killed | show_modified))
+ show_killed | show_modified | show_resolve_undo))
show_cached = 1;
if (prefix)
@@ -544,6 +590,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
overlay_tree_on_cache(with_tree, prefix);
}
show_files(&dir, prefix);
+ if (show_resolve_undo)
+ show_ru_info(prefix);
if (ps_matched) {
int bad;
diff --git a/builtin-merge-file.c b/builtin-merge-file.c
index afd2ea7a73..1e70073a7e 100644
--- a/builtin-merge-file.c
+++ b/builtin-merge-file.c
@@ -25,15 +25,20 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
const char *names[3] = { NULL, NULL, NULL };
mmfile_t mmfs[3];
mmbuffer_t result = {NULL, 0};
- xpparam_t xpp = {XDF_NEED_MINIMAL};
+ xmparam_t xmp = {{XDF_NEED_MINIMAL}};
int ret = 0, i = 0, to_stdout = 0;
- int merge_level = XDL_MERGE_ZEALOUS_ALNUM;
- int merge_style = 0, quiet = 0;
+ int level = XDL_MERGE_ZEALOUS_ALNUM;
+ int style = 0, quiet = 0;
+ int favor = 0;
int nongit;
struct option options[] = {
OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"),
- OPT_SET_INT(0, "diff3", &merge_style, "use a diff3 based merge", XDL_MERGE_DIFF3),
+ OPT_SET_INT(0, "diff3", &style, "use a diff3 based merge", XDL_MERGE_DIFF3),
+ OPT_SET_INT(0, "ours", &favor, "for conflicts, use our version",
+ XDL_MERGE_FAVOR_OURS),
+ OPT_SET_INT(0, "theirs", &favor, "for conflicts, use their version",
+ XDL_MERGE_FAVOR_THEIRS),
OPT__QUIET(&quiet),
OPT_CALLBACK('L', NULL, names, "name",
"set labels for file1/orig_file/file2", &label_cb),
@@ -45,7 +50,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
/* Read the configuration file */
git_config(git_xmerge_config, NULL);
if (0 <= git_xmerge_style)
- merge_style = git_xmerge_style;
+ style = git_xmerge_style;
}
argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0);
@@ -68,7 +73,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
}
ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
- &xpp, merge_level | merge_style, &result);
+ &xmp, XDL_MERGE_FLAGS(level, style, favor), &result);
for (i = 0; i < 3; i++)
free(mmfs[i].ptr);
diff --git a/merge-index.c b/builtin-merge-index.c
index 19ddd03e0b..2c4cf5e559 100644
--- a/merge-index.c
+++ b/builtin-merge-index.c
@@ -66,7 +66,7 @@ static void merge_all(void)
}
}
-int main(int argc, char **argv)
+int cmd_merge_index(int argc, const char **argv, const char *prefix)
{
int i, force_file = 0;
@@ -78,9 +78,6 @@ int main(int argc, char **argv)
if (argc < 3)
usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
- git_extract_argv0_path(argv[0]);
-
- setup_git_directory();
read_cache();
i = 1;
@@ -94,7 +91,7 @@ int main(int argc, char **argv)
}
pgm = argv[i++];
for (; i < argc; i++) {
- char *arg = argv[i];
+ const char *arg = argv[i];
if (!force_file && *arg == '-') {
if (!strcmp(arg, "--")) {
force_file = 1;
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
index 710674c6b2..d8875d5892 100644
--- a/builtin-merge-recursive.c
+++ b/builtin-merge-recursive.c
@@ -25,19 +25,30 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
struct commit *result;
init_merge_options(&o);
- if (argv[0]) {
- int namelen = strlen(argv[0]);
- if (8 < namelen &&
- !strcmp(argv[0] + namelen - 8, "-subtree"))
- o.subtree_merge = 1;
- }
+ if (argv[0] && !suffixcmp(argv[0], "-subtree"))
+ o.subtree_shift = "";
if (argc < 4)
usagef("%s <base>... -- <head> <remote> ...", argv[0]);
for (i = 1; i < argc; ++i) {
- if (!strcmp(argv[i], "--"))
- break;
+ const char *arg = argv[i];
+
+ if (!prefixcmp(arg, "--")) {
+ if (!arg[2])
+ break;
+ if (!strcmp(arg+2, "ours"))
+ o.recursive_variant = MERGE_RECURSIVE_OURS;
+ else if (!strcmp(arg+2, "theirs"))
+ o.recursive_variant = MERGE_RECURSIVE_THEIRS;
+ else if (!strcmp(arg+2, "subtree"))
+ o.subtree_shift = "";
+ else if (!prefixcmp(arg+2, "subtree="))
+ o.subtree_shift = arg + 10;
+ else
+ die("Unknown option %s", arg);
+ continue;
+ }
if (bases_count < ARRAY_SIZE(bases)-1) {
unsigned char *sha = xmalloc(20);
if (get_sha1(argv[i], sha))
diff --git a/merge-tree.c b/builtin-merge-tree.c
index f01e7c81ae..a4a4f2ce4c 100644
--- a/merge-tree.c
+++ b/builtin-merge-tree.c
@@ -54,7 +54,7 @@ static const char *explanation(struct merge_list *entry)
return "removed in remote";
}
-extern void *merge_file(struct blob *, struct blob *, struct blob *, unsigned long *);
+extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *);
static void *result(struct merge_list *entry, unsigned long *size)
{
@@ -76,7 +76,7 @@ static void *result(struct merge_list *entry, unsigned long *size)
their = NULL;
if (entry)
their = entry->blob;
- return merge_file(base, our, their, size);
+ return merge_file(entry->path, base, our, their, size);
}
static void *origin(struct merge_list *entry, unsigned long *size)
@@ -337,7 +337,7 @@ static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
return buf;
}
-int main(int argc, char **argv)
+int cmd_merge_tree(int argc, const char **argv, const char *prefix)
{
struct tree_desc t[3];
void *buf1, *buf2, *buf3;
@@ -345,10 +345,6 @@ int main(int argc, char **argv)
if (argc != 4)
usage(merge_tree_usage);
- git_extract_argv0_path(argv[0]);
-
- setup_git_directory();
-
buf1 = get_tree_descriptor(t+0, argv[1]);
buf2 = get_tree_descriptor(t+1, argv[2]);
buf3 = get_tree_descriptor(t+2, argv[3]);
diff --git a/builtin-merge.c b/builtin-merge.c
index f1c84d759d..3aaec7bed7 100644
--- a/builtin-merge.c
+++ b/builtin-merge.c
@@ -24,6 +24,7 @@
#include "rerere.h"
#include "help.h"
#include "merge-recursive.h"
+#include "resolve-undo.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
@@ -50,8 +51,11 @@ static struct commit_list *remoteheads;
static unsigned char head[20], stash[20];
static struct strategy **use_strategies;
static size_t use_strategies_nr, use_strategies_alloc;
+static const char **xopts;
+static size_t xopts_nr, xopts_alloc;
static const char *branch;
static int verbosity;
+static int allow_rerere_auto;
static struct strategy all_strategy[] = {
{ "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -146,6 +150,17 @@ static int option_parse_strategy(const struct option *opt,
return 0;
}
+static int option_parse_x(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ return 0;
+
+ ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
+ xopts[xopts_nr++] = xstrdup(arg);
+ return 0;
+}
+
static int option_parse_n(const struct option *opt,
const char *arg, int unset)
{
@@ -170,8 +185,11 @@ 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('X', "strategy-option", &xopts, "option=value",
+ "option for selected merge strategy", option_parse_x),
OPT_CALLBACK('m', "message", &merge_msg, "message",
"message to be used for the merge commit (if any)",
option_parse_message),
@@ -534,7 +552,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
const char *head_arg)
{
const char **args;
- int i = 0, ret;
+ int i = 0, x = 0, ret;
struct commit_list *j;
struct strbuf buf = STRBUF_INIT;
int index_fd;
@@ -563,7 +581,20 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
init_merge_options(&o);
if (!strcmp(strategy, "subtree"))
- o.subtree_merge = 1;
+ o.subtree_shift = "";
+
+ for (x = 0; x < xopts_nr; x++) {
+ if (!strcmp(xopts[x], "ours"))
+ o.recursive_variant = MERGE_RECURSIVE_OURS;
+ else if (!strcmp(xopts[x], "theirs"))
+ o.recursive_variant = MERGE_RECURSIVE_THEIRS;
+ else if (!strcmp(xopts[x], "subtree"))
+ o.subtree_shift = "";
+ else if (!prefixcmp(xopts[x], "subtree="))
+ o.subtree_shift = xopts[x]+8;
+ else
+ die("Unknown option for merge-recursive: -X%s", xopts[x]);
+ }
o.branch1 = head_arg;
o.branch2 = remoteheads->item->util;
@@ -581,10 +612,16 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
rollback_lock_file(lock);
return clean ? 0 : 1;
} else {
- args = xmalloc((4 + commit_list_count(common) +
+ args = xmalloc((4 + xopts_nr + commit_list_count(common) +
commit_list_count(remoteheads)) * sizeof(char *));
strbuf_addf(&buf, "merge-%s", strategy);
args[i++] = buf.buf;
+ for (x = 0; x < xopts_nr; x++) {
+ char *s = xmalloc(strlen(xopts[x])+2+1);
+ strcpy(s, "--");
+ strcpy(s+2, xopts[x]);
+ args[i++] = s;
+ }
for (j = common; j; j = j->next)
args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
args[i++] = "--";
@@ -595,6 +632,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
ret = run_command_v_opt(args, RUN_GIT_CMD);
strbuf_release(&buf);
i = 1;
+ for (x = 0; x < xopts_nr; x++)
+ free((void *)args[i++]);
for (j = common; j; j = j->next)
free((void *)args[i++]);
i += 2;
@@ -604,6 +643,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
discard_cache();
if (read_cache() < 0)
die("failed to read the cache");
+ resolve_undo_clear();
return ret;
}
}
@@ -618,11 +658,10 @@ static void count_diff_files(struct diff_queue_struct *q,
static int count_unmerged_entries(void)
{
- const struct index_state *state = &the_index;
int i, ret = 0;
- for (i = 0; i < state->cache_nr; i++)
- if (ce_stage(state->cache[i]))
+ for (i = 0; i < active_nr; i++)
+ if (ce_stage(active_cache[i]))
ret++;
return ret;
@@ -790,7 +829,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;
@@ -847,12 +886,22 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
const char *best_strategy = NULL, *wt_strategy = NULL;
struct commit_list **remotes = &remoteheads;
- if (file_exists(git_path("MERGE_HEAD")))
- die("You have not concluded your merge. (MERGE_HEAD exists)");
- if (read_cache_unmerged())
- die("You are in the middle of a conflicted merge."
- " (index unmerged)");
+ if (read_cache_unmerged()) {
+ die_resolve_conflict("merge");
+ }
+ if (file_exists(git_path("MERGE_HEAD"))) {
+ /*
+ * There is no unmerged entry, don't advise 'git
+ * add/rm <file>', just 'git commit'.
+ */
+ if (advice_resolve_conflict)
+ die("You have not concluded your merge (MERGE_HEAD exists).\n"
+ "Please, commit your changes before you can merge.");
+ else
+ die("You have not concluded your merge (MERGE_HEAD exists).");
+ }
+ resolve_undo_clear();
/*
* Check if we are _not_ on a detached HEAD, i.e. if there is a
* current branch.
diff --git a/mktag.c b/builtin-mktag.c
index a3b4270c18..1cb0f3f2a7 100644
--- a/mktag.c
+++ b/builtin-mktag.c
@@ -153,7 +153,7 @@ static int verify_tag(char *buffer, unsigned long size)
#undef PD_FMT
-int main(int argc, char **argv)
+int cmd_mktag(int argc, const char **argv, const char *prefix)
{
struct strbuf buf = STRBUF_INIT;
unsigned char result_sha1[20];
@@ -161,10 +161,6 @@ int main(int argc, char **argv)
if (argc != 1)
usage("git mktag < signaturefile");
- git_extract_argv0_path(argv[0]);
-
- setup_git_directory();
-
if (strbuf_read(&buf, 0, 4096) < 0) {
die_errno("could not read from stdin");
}
diff --git a/builtin-mv.c b/builtin-mv.c
index 82471869a0..c07f53b343 100644
--- a/builtin-mv.c
+++ b/builtin-mv.c
@@ -24,10 +24,13 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
result[count] = NULL;
for (i = 0; i < count; i++) {
int length = strlen(result[i]);
- if (length > 0 && is_dir_sep(result[i][length - 1]))
- result[i] = xmemdupz(result[i], length - 1);
- if (base_name)
- result[i] = basename((char *)result[i]);
+ int to_copy = length;
+ while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
+ to_copy--;
+ if (to_copy != length || base_name) {
+ char *it = xmemdupz(result[i], to_copy);
+ result[i] = base_name ? strdup(basename(it)) : it;
+ }
}
return get_pathspec(prefix, result);
}
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 4429d53a1e..b0887d759d 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -525,7 +525,8 @@ static void write_pack_file(void)
if (!pack_to_stdout) {
mode_t mode = umask(0);
struct stat st;
- char *idx_tmp_name, tmpname[PATH_MAX];
+ const char *idx_tmp_name;
+ char tmpname[PATH_MAX];
umask(mode);
mode = 0444 & ~mode;
@@ -569,7 +570,7 @@ static void write_pack_file(void)
if (rename(idx_tmp_name, tmpname))
die_errno("unable to rename temporary index file");
- free(idx_tmp_name);
+ free((void *) idx_tmp_name);
free(pack_tmp_name);
puts(sha1_to_hex(sha1));
}
@@ -673,7 +674,7 @@ static void setup_delta_attr_check(struct git_attr_check *check)
static struct git_attr *attr_delta;
if (!attr_delta)
- attr_delta = git_attr("delta", 5);
+ attr_delta = git_attr("delta");
check[0].attr = attr_delta;
}
@@ -1256,15 +1257,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 +1592,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 +1650,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 +1771,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
active_threads--;
}
}
+ cleanup_threaded_search();
free(p);
}
diff --git a/pack-redundant.c b/builtin-pack-redundant.c
index 21c61dbbe9..41e1615a28 100644
--- a/pack-redundant.c
+++ b/builtin-pack-redundant.c
@@ -568,7 +568,7 @@ static struct pack_list * add_pack(struct packed_git *p)
return pack_list_insert(&altodb_packs, &l);
}
-static struct pack_list * add_pack_file(char *filename)
+static struct pack_list * add_pack_file(const char *filename)
{
struct packed_git *p = packed_git;
@@ -593,7 +593,7 @@ static void load_all(void)
}
}
-int main(int argc, char **argv)
+int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
{
int i;
struct pack_list *min, *red, *pl;
@@ -601,13 +601,9 @@ int main(int argc, char **argv)
unsigned char *sha1;
char buf[42]; /* 40 byte sha1 + \n + \0 */
- git_extract_argv0_path(argv[0]);
-
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(pack_redundant_usage);
- setup_git_directory();
-
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--")) {
diff --git a/patch-id.c b/builtin-patch-id.c
index 0df4cb086b..af0911e4bd 100644
--- a/patch-id.c
+++ b/builtin-patch-id.c
@@ -75,13 +75,11 @@ static void generate_id_list(void)
static const char patch_id_usage[] = "git patch-id < patch";
-int main(int argc, char **argv)
+int cmd_patch_id(int argc, const char **argv, const char *prefix)
{
if (argc != 1)
usage(patch_id_usage);
- git_extract_argv0_path(argv[0]);
-
generate_id_list();
return 0;
}
diff --git a/builtin-push.c b/builtin-push.c
index dcfb53f188..5df66081a6 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -15,6 +15,7 @@ static const char * const push_usage[] = {
};
static int thin;
+static int deleterefs;
static const char *receivepack;
static const char **refspec;
@@ -39,11 +40,24 @@ static void set_refspecs(const char **refs, int nr)
if (nr <= ++i)
die("tag shorthand without <tag>");
len = strlen(refs[i]) + 11;
- tag = xmalloc(len);
- strcpy(tag, "refs/tags/");
+ if (deleterefs) {
+ tag = xmalloc(len+1);
+ strcpy(tag, ":refs/tags/");
+ } else {
+ tag = xmalloc(len);
+ strcpy(tag, "refs/tags/");
+ }
strcat(tag, refs[i]);
ref = tag;
- }
+ } else if (deleterefs && !strchr(ref, ':')) {
+ char *delref;
+ int len = strlen(ref)+1;
+ delref = xmalloc(len);
+ strcpy(delref, ":");
+ strcat(delref, ref);
+ ref = delref;
+ } else if (deleterefs)
+ die("--delete only accepts plain target ref names");
add_refspec(ref);
}
}
@@ -111,8 +125,8 @@ static int push_with_options(struct transport *transport, int flags)
if (nonfastforward && advice_push_nonfastforward) {
printf("To prevent you from losing history, non-fast-forward updates were rejected\n"
- "Merge the remote changes before pushing again. See the 'non-fast-forward'\n"
- "section of 'git push --help' for details.\n");
+ "Merge the remote changes before pushing again. See the 'Note about\n"
+ "fast-forwards' section of 'git push --help' for details.\n");
}
return 1;
@@ -196,6 +210,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL),
OPT_BIT( 0 , "mirror", &flags, "mirror all refs",
(TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
+ OPT_BOOLEAN( 0, "delete", &deleterefs, "delete refs"),
OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"),
OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
@@ -203,12 +218,19 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
+ OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
+ TRANSPORT_PUSH_SET_UPSTREAM),
OPT_END()
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, push_usage, 0);
+ if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
+ die("--delete is incompatible with --all, --mirror and --tags");
+ if (deleterefs && argc < 2)
+ die("--delete doesn't make sense without any refs");
+
if (tags)
add_refspec("refs/tags/*");
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 2a3a32cbfe..5fda9905fc 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -13,6 +13,7 @@
#include "dir.h"
#include "builtin.h"
#include "parse-options.h"
+#include "resolve-undo.h"
static int nr_trees;
static struct tree *trees[MAX_UNPACK_TREES];
@@ -31,7 +32,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 +99,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()
};
@@ -122,6 +125,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
die("You need to resolve your current index first");
stage = opts.merge = 1;
}
+ resolve_undo_clear();
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
diff --git a/builtin-remote.c b/builtin-remote.c
index a5019397ff..277765b864 100644
--- a/builtin-remote.c
+++ b/builtin-remote.c
@@ -16,6 +16,9 @@ static const char * const builtin_remote_usage[] = {
"git remote [-v | --verbose] show [-n] <name>",
"git remote prune [-n | --dry-run] <name>",
"git remote [-v | --verbose] update [-p | --prune] [group | remote]",
+ "git remote set-url <name> <newurl> [<oldurl>]",
+ "git remote set-url --add <name> <newurl>",
+ "git remote set-url --delete <name> <url>",
NULL
};
@@ -54,6 +57,13 @@ static const char * const builtin_remote_update_usage[] = {
NULL
};
+static const char * const builtin_remote_seturl_usage[] = {
+ "git remote set-url [--push] <name> <newurl> [<oldurl>]",
+ "git remote set-url --add <name> <newurl>",
+ "git remote set-url --delete <name> <url>",
+ NULL
+};
+
#define GET_REF_STATES (1<<0)
#define GET_HEAD_NAMES (1<<1)
#define GET_PUSH_REF_STATES (1<<2)
@@ -1238,13 +1248,11 @@ static int update(int argc, const char **argv)
fetch_argv[fetch_argc++] = "--prune";
if (verbose)
fetch_argv[fetch_argc++] = "-v";
- if (argc < 2) {
+ fetch_argv[fetch_argc++] = "--multiple";
+ if (argc < 2)
fetch_argv[fetch_argc++] = "default";
- } else {
- fetch_argv[fetch_argc++] = "--multiple";
- for (i = 1; i < argc; i++)
- fetch_argv[fetch_argc++] = argv[i];
- }
+ for (i = 1; i < argc; i++)
+ fetch_argv[fetch_argc++] = argv[i];
if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) {
git_config(get_remote_default, &default_defined);
@@ -1257,6 +1265,92 @@ static int update(int argc, const char **argv)
return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
}
+static int set_url(int argc, const char **argv)
+{
+ int i, push_mode = 0, add_mode = 0, delete_mode = 0;
+ int matches = 0, negative_matches = 0;
+ const char *remotename = NULL;
+ const char *newurl = NULL;
+ const char *oldurl = NULL;
+ struct remote *remote;
+ regex_t old_regex;
+ const char **urlset;
+ int urlset_nr;
+ struct strbuf name_buf = STRBUF_INIT;
+ struct option options[] = {
+ OPT_BOOLEAN('\0', "push", &push_mode,
+ "manipulate push URLs"),
+ OPT_BOOLEAN('\0', "add", &add_mode,
+ "add URL"),
+ OPT_BOOLEAN('\0', "delete", &delete_mode,
+ "delete URLs"),
+ OPT_END()
+ };
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
+ PARSE_OPT_KEEP_ARGV0);
+
+ if (add_mode && delete_mode)
+ die("--add --delete doesn't make sense");
+
+ if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3))
+ usage_with_options(builtin_remote_seturl_usage, options);
+
+ remotename = argv[1];
+ newurl = argv[2];
+ if (argc > 3)
+ oldurl = argv[3];
+
+ if (delete_mode)
+ oldurl = newurl;
+
+ if (!remote_is_configured(remotename))
+ die("No such remote '%s'", remotename);
+ remote = remote_get(remotename);
+
+ if (push_mode) {
+ strbuf_addf(&name_buf, "remote.%s.pushurl", remotename);
+ urlset = remote->pushurl;
+ urlset_nr = remote->pushurl_nr;
+ } else {
+ strbuf_addf(&name_buf, "remote.%s.url", remotename);
+ urlset = remote->url;
+ urlset_nr = remote->url_nr;
+ }
+
+ /* Special cases that add new entry. */
+ if ((!oldurl && !delete_mode) || add_mode) {
+ if (add_mode)
+ git_config_set_multivar(name_buf.buf, newurl,
+ "^$", 0);
+ else
+ git_config_set(name_buf.buf, newurl);
+ strbuf_release(&name_buf);
+ return 0;
+ }
+
+ /* Old URL specified. Demand that one matches. */
+ if (regcomp(&old_regex, oldurl, REG_EXTENDED))
+ die("Invalid old URL pattern: %s", oldurl);
+
+ for (i = 0; i < urlset_nr; i++)
+ if (!regexec(&old_regex, urlset[i], 0, NULL, 0))
+ matches++;
+ else
+ negative_matches++;
+ if (!delete_mode && !matches)
+ die("No such URL found: %s", oldurl);
+ if (delete_mode && !negative_matches && !push_mode)
+ die("Will not delete all non-push URLs");
+
+ regfree(&old_regex);
+
+ if (!delete_mode)
+ git_config_set_multivar(name_buf.buf, newurl, oldurl, 0);
+ else
+ git_config_set_multivar(name_buf.buf, NULL, oldurl, 1);
+ return 0;
+}
+
static int get_one_entry(struct remote *remote, void *priv)
{
struct string_list *list = priv;
@@ -1336,6 +1430,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
result = rm(argc, argv);
else if (!strcmp(argv[0], "set-head"))
result = set_head(argc, argv);
+ else if (!strcmp(argv[0], "set-url"))
+ result = set_url(argc, argv);
else if (!strcmp(argv[0], "show"))
result = show(argc, argv);
else if (!strcmp(argv[0], "prune"))
diff --git a/builtin-rerere.c b/builtin-rerere.c
index 2be9ffb77b..34f9acee91 100644
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
@@ -103,15 +103,29 @@ 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();
+ return rerere(flags);
- if (!strcmp(argv[1], "-h"))
- usage(git_rerere_usage);
+ if (!strcmp(argv[1], "forget")) {
+ const char **pathspec = get_pathspec(prefix, argv + 2);
+ return rerere_forget(pathspec);
+ }
- 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 11d1c6e4d6..0f5022eed2 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)
@@ -202,6 +221,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
struct commit *commit;
char *reflog_action, msg[1024];
const struct option options[] = {
+ OPT__QUIET(&quiet),
OPT_SET_INT(0, "mixed", &reset_type,
"reset HEAD and index", MIXED),
OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
@@ -209,8 +229,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
"reset HEAD, index and working tree", HARD),
OPT_SET_INT(0, "merge", &reset_type,
"reset HEAD, index and working tree", MERGE),
- OPT_BOOLEAN('q', NULL, &quiet,
- "disable showing new HEAD in hard reset and progress message"),
OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
OPT_END()
};
@@ -286,9 +304,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
if (reset_type == NONE)
reset_type = MIXED; /* by default */
- if ((reset_type == HARD || reset_type == MERGE)
- && !is_inside_work_tree())
- die("%s reset requires a work tree",
+ 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
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
index cd97ded4d2..c924b3a2c7 100644
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
@@ -253,7 +253,7 @@ static void print_var_int(const char *var, int val)
printf("%s=%d\n", var, val);
}
-int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
+static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
{
int cnt, flags = info->bisect_show_flags;
char hex[41] = "";
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index 37d0233521..a8c5043ded 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -41,6 +41,7 @@ static int is_rev_argument(const char *arg)
"--all",
"--bisect",
"--dense",
+ "--branches=",
"--branches",
"--header",
"--max-age=",
@@ -51,8 +52,11 @@ static int is_rev_argument(const char *arg)
"--objects-edge",
"--parents",
"--pretty",
+ "--remotes=",
"--remotes",
+ "--glob=",
"--sparse",
+ "--tags=",
"--tags",
"--topo-order",
"--date-order",
@@ -569,18 +573,43 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
for_each_ref_in("refs/bisect/good", anti_reference, NULL);
continue;
}
+ if (!prefixcmp(arg, "--branches=")) {
+ for_each_glob_ref_in(show_reference, arg + 11,
+ "refs/heads/", NULL);
+ continue;
+ }
if (!strcmp(arg, "--branches")) {
for_each_branch_ref(show_reference, NULL);
continue;
}
+ if (!prefixcmp(arg, "--tags=")) {
+ for_each_glob_ref_in(show_reference, arg + 7,
+ "refs/tags/", NULL);
+ continue;
+ }
if (!strcmp(arg, "--tags")) {
for_each_tag_ref(show_reference, NULL);
continue;
}
+ if (!prefixcmp(arg, "--glob=")) {
+ for_each_glob_ref(show_reference, arg + 7, NULL);
+ continue;
+ }
+ if (!prefixcmp(arg, "--remotes=")) {
+ for_each_glob_ref_in(show_reference, arg + 10,
+ "refs/remotes/", NULL);
+ continue;
+ }
if (!strcmp(arg, "--remotes")) {
for_each_remote_ref(show_reference, NULL);
continue;
}
+ if (!strcmp(arg, "--show-toplevel")) {
+ const char *work_tree = get_git_work_tree();
+ if (work_tree)
+ puts(work_tree);
+ continue;
+ }
if (!strcmp(arg, "--show-prefix")) {
if (prefix)
puts(prefix);
diff --git a/builtin-revert.c b/builtin-revert.c
index 151aa6a981..8ac86f0943 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(),
};
@@ -233,6 +235,19 @@ static struct tree *empty_tree(void)
return tree;
}
+static NORETURN void die_dirty_index(const char *me)
+{
+ if (read_cache_unmerged()) {
+ die_resolve_conflict(me);
+ } else {
+ if (advice_commit_before_merge)
+ die("Your local changes would be overwritten by %s.\n"
+ "Please, commit your changes or stash them to proceed.", me);
+ else
+ die("Your local changes would be overwritten by %s.\n", me);
+ }
+}
+
static int revert_or_cherry_pick(int argc, const char **argv)
{
unsigned char head[20];
@@ -269,7 +284,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
if (get_sha1("HEAD", head))
die ("You do not have a valid HEAD");
if (index_differs_from("HEAD", 0))
- die ("Dirty index: cannot %s", me);
+ die_dirty_index(me);
}
discard_cache();
@@ -395,7 +410,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-rm.c b/builtin-rm.c
index 57975dbcfd..f3772c84de 100644
--- a/builtin-rm.c
+++ b/builtin-rm.c
@@ -169,9 +169,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (read_cache() < 0)
die("index file corrupt");
- refresh_cache(REFRESH_QUIET);
pathspec = get_pathspec(prefix, argv);
+ refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL);
+
seen = NULL;
for (i = 0; pathspec[i] ; i++)
/* nothing */;
diff --git a/builtin-send-pack.c b/builtin-send-pack.c
index 8fffdbf200..76c72065de 100644
--- a/builtin-send-pack.c
+++ b/builtin-send-pack.c
@@ -406,50 +406,20 @@ int send_pack(struct send_pack_args *args,
*/
new_refs = 0;
for (ref = remote_refs; ref; ref = ref->next) {
-
- if (ref->peer_ref)
- hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
- else if (!args->send_mirror)
+ if (!ref->peer_ref && !args->send_mirror)
continue;
- ref->deletion = is_null_sha1(ref->new_sha1);
- if (ref->deletion && !allow_deleting_refs) {
- ref->status = REF_STATUS_REJECT_NODELETE;
- continue;
- }
- if (!ref->deletion &&
- !hashcmp(ref->old_sha1, ref->new_sha1)) {
- ref->status = REF_STATUS_UPTODATE;
+ /* Check for statuses set by set_ref_status_for_push() */
+ switch (ref->status) {
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ case REF_STATUS_UPTODATE:
continue;
+ default:
+ ; /* do nothing */
}
- /* This part determines what can overwrite what.
- * The rules are:
- *
- * (0) you can always use --force or +A:B notation to
- * selectively force individual ref pairs.
- *
- * (1) if the old thing does not exist, it is OK.
- *
- * (2) if you do not have the old thing, you are not allowed
- * to overwrite it; you would not know what you are losing
- * otherwise.
- *
- * (3) if both new and old are commit-ish, and new is a
- * descendant of old, it is OK.
- *
- * (4) regardless of all of the above, removing :B is
- * always allowed.
- */
-
- ref->nonfastforward =
- !ref->deletion &&
- !is_null_sha1(ref->old_sha1) &&
- (!has_sha1_file(ref->old_sha1)
- || !ref_newer(ref->new_sha1, ref->old_sha1));
-
- if (ref->nonfastforward && !ref->force && !args->force_update) {
- ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
+ if (ref->deletion && !allow_deleting_refs) {
+ ref->status = REF_STATUS_REJECT_NODELETE;
continue;
}
@@ -673,6 +643,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
return -1;
+ set_ref_status_for_push(remote_refs, args.send_mirror,
+ args.force_update);
+
ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
if (helper_status)
diff --git a/unpack-file.c b/builtin-unpack-file.c
index e9d8934691..608590ada8 100644
--- a/unpack-file.c
+++ b/builtin-unpack-file.c
@@ -22,18 +22,15 @@ static char *create_temp_file(unsigned char *sha1)
return path;
}
-int main(int argc, char **argv)
+int cmd_unpack_file(int argc, const char **argv, const char *prefix)
{
unsigned char sha1[20];
- git_extract_argv0_path(argv[0]);
-
if (argc != 2 || !strcmp(argv[1], "-h"))
usage("git unpack-file <sha1>");
if (get_sha1(argv[1], sha1))
die("Not a valid object name %s", argv[1]);
- setup_git_directory();
git_config(git_default_config, NULL);
puts(create_temp_file(sha1));
diff --git a/builtin-update-index.c b/builtin-update-index.c
index a6b7f2d636..3ab214d24e 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -9,6 +9,7 @@
#include "tree-walk.h"
#include "builtin.h"
#include "refs.h"
+#include "resolve-undo.h"
/*
* Default to not allowing changes to the list of files. The
@@ -24,8 +25,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 +43,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 +174,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 +207,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 +281,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 +398,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];
@@ -432,7 +441,18 @@ static int unresolve_one(const char *path)
/* See if there is such entry in the index. */
pos = cache_name_pos(path, namelen);
- if (pos < 0) {
+ if (0 <= pos) {
+ /* already merged */
+ pos = unmerge_cache_entry_at(pos);
+ if (pos < active_nr) {
+ struct cache_entry *ce = active_cache[pos];
+ if (ce_stage(ce) &&
+ ce_namelen(ce) == namelen &&
+ !memcmp(ce->name, path, namelen))
+ return 0;
+ }
+ /* no resolve-undo information; fall back */
+ } else {
/* If there isn't, either it is unmerged, or
* resolved as "removed" by mistake. We do not
* want to do anything in the former case.
@@ -648,11 +668,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")) {
@@ -703,6 +731,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
verbose = 1;
continue;
}
+ if (!strcmp(path, "--clear-resolve-undo")) {
+ resolve_undo_clear();
+ continue;
+ }
if (!strcmp(path, "-h") || !strcmp(path, "--help"))
usage(update_index_usage);
die("unknown option %s", path);
diff --git a/var.c b/builtin-var.c
index d9892f85ce..2280518190 100644
--- a/var.c
+++ b/builtin-var.c
@@ -72,7 +72,7 @@ static int show_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
-int main(int argc, char **argv)
+int cmd_var(int argc, const char **argv, const char *prefix)
{
const char *val;
int nongit;
@@ -80,8 +80,6 @@ int main(int argc, char **argv)
usage(var_usage);
}
- git_extract_argv0_path(argv[0]);
-
setup_git_directory_gently(&nongit);
val = NULL;
diff --git a/builtin.h b/builtin.h
index c3f83c093f..e8202f3f5e 100644
--- a/builtin.h
+++ b/builtin.h
@@ -55,8 +55,10 @@ extern int cmd_fsck(int argc, const char **argv, const char *prefix);
extern int cmd_gc(int argc, const char **argv, const char *prefix);
extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
extern int cmd_grep(int argc, const char **argv, const char *prefix);
+extern int cmd_hash_object(int argc, const char **argv, const char *prefix);
extern int cmd_help(int argc, const char **argv, const char *prefix);
extern int cmd_http_fetch(int argc, const char **argv, const char *prefix);
+extern int cmd_index_pack(int argc, const char **argv, const char *prefix);
extern int cmd_init_db(int argc, const char **argv, const char *prefix);
extern int cmd_log(int argc, const char **argv, const char *prefix);
extern int cmd_log_reflog(int argc, const char **argv, const char *prefix);
@@ -67,13 +69,18 @@ extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
extern int cmd_merge(int argc, const char **argv, const char *prefix);
extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_index(int argc, const char **argv, const char *prefix);
extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_mktag(int argc, const char **argv, const char *prefix);
extern int cmd_mktree(int argc, const char **argv, const char *prefix);
extern int cmd_mv(int argc, const char **argv, const char *prefix);
extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
+extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix);
+extern int cmd_patch_id(int argc, const char **argv, const char *prefix);
extern int cmd_pickaxe(int argc, const char **argv, const char *prefix);
extern int cmd_prune(int argc, const char **argv, const char *prefix);
extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
@@ -98,12 +105,14 @@ extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
extern int cmd_tag(int argc, const char **argv, const char *prefix);
extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_unpack_file(int argc, const char **argv, const char *prefix);
extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix);
extern int cmd_update_index(int argc, const char **argv, const char *prefix);
extern int cmd_update_ref(int argc, const char **argv, const char *prefix);
extern int cmd_update_server_info(int argc, const char **argv, const char *prefix);
extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
extern int cmd_upload_tar(int argc, const char **argv, const char *prefix);
+extern int cmd_var(int argc, const char **argv, const char *prefix);
extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
extern int cmd_version(int argc, const char **argv, const char *prefix);
extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index bf468e5235..7d5c21e3f1 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)
@@ -282,6 +288,7 @@ static inline int ce_to_dtype(const struct cache_entry *ce)
struct index_state {
struct cache_entry **cache;
unsigned int cache_nr, cache_alloc, cache_changed;
+ struct string_list *resolve_undo;
struct cache_tree *cache_tree;
struct cache_time timestamp;
void *alloc;
@@ -336,6 +343,9 @@ static inline void remove_name_hash(struct cache_entry *ce)
#define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
#define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase))
#define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen))
+#define resolve_undo_clear() resolve_undo_clear_index(&the_index)
+#define unmerge_cache_entry_at(at) unmerge_index_entry_at(&the_index, at)
+#define unmerge_cache(pathspec) unmerge_index(&the_index, pathspec)
#endif
enum object_type {
@@ -445,7 +455,6 @@ extern int index_name_pos(const struct index_state *, const char *name, int name
#define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */
#define ADD_CACHE_NEW_ONLY 16 /* Do not replace existing ones */
extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
-extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
extern int remove_index_entry_at(struct index_state *, int pos);
extern void remove_marked_cache_entries(struct index_state *istate);
@@ -464,7 +473,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);
@@ -473,9 +484,6 @@ extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_obje
extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
-/* "careful lstat()" */
-extern int check_path(const char *path, int len, struct stat *st, int skiplen);
-
#define REFRESH_REALLY 0x0001 /* ignore_valid */
#define REFRESH_UNMERGED 0x0002 /* allow unmerged */
#define REFRESH_QUIET 0x0004 /* be quiet about it */
@@ -529,6 +537,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,
@@ -544,6 +553,7 @@ enum branch_track {
BRANCH_TRACK_REMOTE,
BRANCH_TRACK_ALWAYS,
BRANCH_TRACK_EXPLICIT,
+ BRANCH_TRACK_OVERRIDE,
};
enum rebase_setup_type {
@@ -618,7 +628,6 @@ static inline void hashclr(unsigned char *hash)
{
memset(hash, 0, 20);
}
-extern int is_empty_blob_sha1(const unsigned char *sha1);
#define EMPTY_TREE_SHA1_HEX \
"4b825dc642cb6eb9a060e54bf8d69288fbee4904"
@@ -688,7 +697,6 @@ extern int has_sha1_pack(const unsigned char *sha1);
extern int has_sha1_file(const unsigned char *sha1);
extern int has_loose_object_nonlocal(const unsigned char *sha1);
-extern int has_pack_file(const unsigned char *sha1);
extern int has_pack_index(const unsigned char *sha1);
extern const signed char hexval_table[256];
@@ -702,7 +710,11 @@ static inline unsigned int hexval(unsigned char c)
#define DEFAULT_ABBREV 7
extern int get_sha1(const char *str, unsigned char *sha1);
-extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode);
+extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int gently, const char *prefix);
+static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode)
+{
+ return get_sha1_with_mode_1(str, sha1, mode, 1, NULL);
+}
extern int get_sha1_hex(const char *hex, unsigned char *sha1);
extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
extern int read_ref(const char *filename, unsigned char *sha1);
@@ -710,6 +722,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[];
@@ -784,8 +797,6 @@ extern int has_symlink_leading_path(const char *name, int len);
extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
extern int has_symlink_or_noent_leading_path(const char *name, int len);
extern int has_dirs_only_path(const char *name, int len, int prefix_len);
-extern void invalidate_lstat_cache(const char *name, int len);
-extern void clear_lstat_cache(void);
extern void schedule_dir_for_removal(const char *name, int len);
extern void remove_scheduled_dirs(void);
@@ -925,7 +936,11 @@ extern const char *config_exclusive_filename;
#define MAX_GITNAME (1000)
extern char git_default_email[MAX_GITNAME];
extern char git_default_name[MAX_GITNAME];
+#define IDENT_NAME_GIVEN 01
+#define IDENT_MAIL_GIVEN 02
+#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN)
extern int user_ident_explicitly_given;
+extern int user_ident_sufficiently_given(void);
extern const char *git_commit_encoding;
extern const char *git_log_output_encoding;
@@ -993,6 +1008,7 @@ extern int diff_auto_refresh_index;
/* match-trees.c */
void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
+void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *, const char *);
/*
* whitespace rules.
diff --git a/commit.h b/commit.h
index e5332efcfc..3cf5166581 100644
--- a/commit.h
+++ b/commit.h
@@ -70,10 +70,10 @@ struct pretty_print_context
const char *after_subject;
enum date_mode date_mode;
int need_8bit_cte;
+ int show_notes;
struct reflog_walk_info *reflog_info;
};
-extern int non_ascii(int);
extern int has_non_ascii(const char *text);
struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
extern char *reencode_commit_message(const struct commit *commit,
diff --git a/compat/mingw.c b/compat/mingw.c
index 0d73f15fa8..ab65f77ab9 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 b3d299f5bc..e254fb4e06 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 9c753a560f..023aba0238 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/regex/regex.c b/compat/regex/regex.c
index 67d5c370a0..556d8ab11f 100644
--- a/compat/regex/regex.c
+++ b/compat/regex/regex.c
@@ -2808,7 +2808,7 @@ re_set_registers (bufp, regs, num_regs, starts, ends)
{
bufp->regs_allocated = REGS_UNALLOCATED;
regs->num_regs = 0;
- regs->start = regs->end = (regoff_t) 0;
+ regs->start = regs->end = (regoff_t *) 0;
}
}
diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl
index 8a2112f22f..4374771df2 100644
--- a/compat/vcbuild/scripts/clink.pl
+++ b/compat/vcbuild/scripts/clink.pl
@@ -31,6 +31,7 @@ while (@ARGV) {
push(@args, "iconv.lib");
} elsif ("$arg" eq "-lcrypto") {
push(@args, "libeay32.lib");
+ } elsif ("$arg" eq "-lssl") {
push(@args, "ssleay32.lib");
} elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") {
$arg =~ s/^-L/-LIBPATH:/;
diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c
new file mode 100644
index 0000000000..631c0a46ea
--- /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 0000000000..b8e1bcb046
--- /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 37385ce9d3..6963fbea43 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;
}
@@ -528,8 +533,7 @@ static int git_default_user_config(const char *var, const char *value)
if (!value)
return config_error_nonbool(var);
strlcpy(git_default_name, value, sizeof(git_default_name));
- if (git_default_email[0])
- user_ident_explicitly_given = 1;
+ user_ident_explicitly_given |= IDENT_NAME_GIVEN;
return 0;
}
@@ -537,8 +541,7 @@ static int git_default_user_config(const char *var, const char *value)
if (!value)
return config_error_nonbool(var);
strlcpy(git_default_email, value, sizeof(git_default_email));
- if (git_default_name[0])
- user_ident_explicitly_given = 1;
+ user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
return 0;
}
diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl
index d506717bfd..23da787dc5 100644
--- a/contrib/buildsystems/engine.pl
+++ b/contrib/buildsystems/engine.pl
@@ -135,6 +135,11 @@ sub parseMakeOutput
}
} while($ate_next);
+ if ($text =~ /^test /) {
+ # options to test (eg -o) may be mistaken for linker options
+ next;
+ }
+
if($text =~ / -c /) {
# compilation
handleCompileLine($text, $line);
@@ -180,9 +185,6 @@ sub parseMakeOutput
# } elsif ($text =~ /generate-cmdlist\.sh/) {
# # command for generating list of commands
#
-# } elsif ($text =~ /^test / && $text =~ /|| rm -f /) {
-# # commands removing executables, if they exist
-#
# } elsif ($text =~ /new locations or Tcl/) {
# # command for detecting Tcl/Tk changes
#
@@ -317,6 +319,7 @@ sub handleLinkLine
push(@libs, "zlib.lib");
} elsif ("$part" eq "-lcrypto") {
push(@libs, "libeay32.lib");
+ } elsif ("$part" eq "-lssl") {
push(@libs, "ssleay32.lib");
} elsif ($part =~ /^-/) {
push(@lflags, $part);
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 3a6498c04b..9651720410 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -142,11 +142,9 @@ __git_ps1 ()
elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
- git diff --no-ext-diff --ignore-submodules \
- --quiet --exit-code || w="*"
+ git diff --no-ext-diff --quiet --exit-code || w="*"
if git rev-parse --quiet --verify HEAD >/dev/null; then
- git diff-index --cached --quiet \
- --ignore-submodules HEAD -- || i="+"
+ git diff-index --cached --quiet HEAD -- || i="+"
else
i="#"
fi
diff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh
index 500635fe4b..8f617fcb70 100755
--- a/contrib/examples/git-merge.sh
+++ b/contrib/examples/git-merge.sh
@@ -31,10 +31,11 @@ LF='
'
all_strategies='recur recursive octopus resolve stupid ours subtree'
+all_strategies="$all_strategies recursive-ours recursive-theirs"
default_twohead_strategies='recursive'
default_octopus_strategies='octopus'
no_fast_forward_strategies='subtree ours'
-no_trivial_strategies='recursive recur subtree ours'
+no_trivial_strategies='recursive recur subtree ours recursive-ours recursive-theirs'
use_strategies=
allow_fast_forward=t
diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
index 48059d0aa7..d1512e0acf 100755
--- a/contrib/fast-import/git-p4
+++ b/contrib/fast-import/git-p4
@@ -732,7 +732,7 @@ class P4Submit(Command):
if os.environ.has_key("P4EDITOR"):
editor = os.environ.get("P4EDITOR")
else:
- editor = read_pipe("git var GIT_EDITOR")
+ editor = read_pipe("git var GIT_EDITOR").strip()
system(editor + " " + fileName)
response = "y"
diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py
index 2a6839d81e..854cd94ba5 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 491e7141b4..27acce58bc 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;
@@ -377,9 +378,9 @@ static void setup_convert_check(struct git_attr_check *check)
static struct git_attr *attr_filter;
if (!attr_crlf) {
- attr_crlf = git_attr("crlf", 4);
- attr_ident = git_attr("ident", 5);
- attr_filter = git_attr("filter", 6);
+ attr_crlf = git_attr("crlf");
+ attr_ident = git_attr("ident");
+ attr_filter = git_attr("filter");
user_convert_tail = &user_convert;
git_config(read_convert_config, NULL);
}
diff --git a/daemon.c b/daemon.c
index 5783e24011..360635eb1c 100644
--- a/daemon.c
+++ b/daemon.c
@@ -147,7 +147,6 @@ static char *path_ok(char *directory)
{ "IP", ip_address },
{ "P", tcp_port },
{ "D", directory },
- { "%", "%" },
{ NULL }
};
@@ -562,6 +561,24 @@ static int execute(struct sockaddr *addr)
return -1;
}
+static int addrcmp(const struct sockaddr_storage *s1,
+ const struct sockaddr_storage *s2)
+{
+ if (s1->ss_family != s2->ss_family)
+ return s1->ss_family - s2->ss_family;
+ if (s1->ss_family == AF_INET)
+ return memcmp(&((struct sockaddr_in *)s1)->sin_addr,
+ &((struct sockaddr_in *)s2)->sin_addr,
+ sizeof(struct in_addr));
+#ifndef NO_IPV6
+ if (s1->ss_family == AF_INET6)
+ return memcmp(&((struct sockaddr_in6 *)s1)->sin6_addr,
+ &((struct sockaddr_in6 *)s2)->sin6_addr,
+ sizeof(struct in6_addr));
+#endif
+ return 0;
+}
+
static int max_connections = 32;
static unsigned int live_children;
@@ -576,17 +593,12 @@ static void add_child(pid_t pid, struct sockaddr *addr, int addrlen)
{
struct child *newborn, **cradle;
- /*
- * This must be xcalloc() -- we'll compare the whole sockaddr_storage
- * but individual address may be shorter.
- */
newborn = xcalloc(1, sizeof(*newborn));
live_children++;
newborn->pid = pid;
memcpy(&newborn->address, addr, addrlen);
for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next)
- if (!memcmp(&(*cradle)->address, &newborn->address,
- sizeof(newborn->address)))
+ if (!addrcmp(&(*cradle)->address, &newborn->address))
break;
newborn->next = *cradle;
*cradle = newborn;
@@ -619,8 +631,7 @@ static void kill_some_child(void)
return;
for (; (next = blanket->next); blanket = next)
- if (!memcmp(&blanket->address, &next->address,
- sizeof(next->address))) {
+ if (!addrcmp(&blanket->address, &next->address)) {
kill(blanket->pid, SIGTERM);
break;
}
diff --git a/date.c b/date.c
index 5d05ef61cf..45f3684cee 100644
--- a/date.c
+++ b/date.c
@@ -9,7 +9,7 @@
/*
* This is like mktime, but without normalization of tm_wday and tm_yday.
*/
-time_t tm_to_time_t(const struct tm *tm)
+static time_t tm_to_time_t(const struct tm *tm)
{
static const int mdays[] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
diff --git a/diff-lib.c b/diff-lib.c
index 349f612b61..23e180eed1 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -10,6 +10,7 @@
#include "cache-tree.h"
#include "unpack-trees.h"
#include "refs.h"
+#include "submodule.h"
/*
* diff-files
@@ -72,6 +73,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
unsigned int oldmode, newmode;
struct cache_entry *ce = active_cache[i];
int changed;
+ unsigned dirty_submodule = 0;
if (DIFF_OPT_TST(&revs->diffopt, QUICK) &&
DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
@@ -159,7 +161,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
continue;
}
- if (ce_uptodate(ce))
+ if ((ce_uptodate(ce) && !S_ISGITLINK(ce->ce_mode)) || ce_skip_worktree(ce))
continue;
/* If CE_VALID is set, don't look at workdir for file removal */
@@ -172,10 +174,16 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
if (silent_on_removed)
continue;
diff_addremove(&revs->diffopt, '-', ce->ce_mode,
- ce->sha1, ce->name);
+ ce->sha1, ce->name, 0);
continue;
}
changed = ce_match_stat(ce, &st, ce_option);
+ if (S_ISGITLINK(ce->ce_mode)
+ && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH))
+ && is_submodule_modified(ce->name)) {
+ changed = 1;
+ dirty_submodule = 1;
+ }
if (!changed) {
ce_mark_uptodate(ce);
if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
@@ -185,7 +193,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
newmode = ce_mode_from_stat(ce, st.st_mode);
diff_change(&revs->diffopt, oldmode, newmode,
ce->sha1, (changed ? null_sha1 : ce->sha1),
- ce->name);
+ ce->name, 0, dirty_submodule);
}
diffcore_std(&revs->diffopt);
@@ -201,16 +209,18 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
static void diff_index_show_file(struct rev_info *revs,
const char *prefix,
struct cache_entry *ce,
- const unsigned char *sha1, unsigned int mode)
+ const unsigned char *sha1, unsigned int mode,
+ unsigned dirty_submodule)
{
diff_addremove(&revs->diffopt, prefix[0], mode,
- sha1, ce->name);
+ sha1, ce->name, dirty_submodule);
}
static int get_stat_data(struct cache_entry *ce,
const unsigned char **sha1p,
unsigned int *modep,
- int cached, int match_missing)
+ int cached, int match_missing,
+ unsigned *dirty_submodule, int output_format)
{
const unsigned char *sha1 = ce->sha1;
unsigned int mode = ce->ce_mode;
@@ -230,6 +240,12 @@ static int get_stat_data(struct cache_entry *ce,
return -1;
}
changed = ce_match_stat(ce, &st, 0);
+ if (S_ISGITLINK(ce->ce_mode)
+ && (!changed || (output_format & DIFF_FORMAT_PATCH))
+ && is_submodule_modified(ce->name)) {
+ changed = 1;
+ *dirty_submodule = 1;
+ }
if (changed) {
mode = ce_mode_from_stat(ce, st.st_mode);
sha1 = null_sha1;
@@ -247,15 +263,17 @@ static void show_new_file(struct rev_info *revs,
{
const unsigned char *sha1;
unsigned int mode;
+ unsigned dirty_submodule = 0;
/*
* New file in the index: it might actually be different in
* the working copy.
*/
- if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0)
+ if (get_stat_data(new, &sha1, &mode, cached, match_missing,
+ &dirty_submodule, revs->diffopt.output_format) < 0)
return;
- diff_index_show_file(revs, "+", new, sha1, mode);
+ diff_index_show_file(revs, "+", new, sha1, mode, dirty_submodule);
}
static int show_modified(struct rev_info *revs,
@@ -266,11 +284,13 @@ static int show_modified(struct rev_info *revs,
{
unsigned int mode, oldmode;
const unsigned char *sha1;
+ unsigned dirty_submodule = 0;
- if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
+ if (get_stat_data(new, &sha1, &mode, cached, match_missing,
+ &dirty_submodule, revs->diffopt.output_format) < 0) {
if (report_missing)
diff_index_show_file(revs, "-", old,
- old->sha1, old->ce_mode);
+ old->sha1, old->ce_mode, 0);
return -1;
}
@@ -305,7 +325,7 @@ static int show_modified(struct rev_info *revs,
return 0;
diff_change(&revs->diffopt, oldmode, mode,
- old->sha1, sha1, old->name);
+ old->sha1, sha1, old->name, 0, dirty_submodule);
return 0;
}
@@ -323,7 +343,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".
@@ -351,7 +372,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,
* Something removed from the tree?
*/
if (!idx) {
- diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode);
+ diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode, 0);
return;
}
diff --git a/diff.c b/diff.c
index aad4b3977a..160dbfd718 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;
/*
@@ -2029,9 +2029,14 @@ static int populate_from_stdin(struct diff_filespec *s)
static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
{
int len;
- char *data = xmalloc(100);
+ char *data = xmalloc(100), *dirty = "";
+
+ /* Are we looking at the work tree? */
+ if (!s->sha1_valid && s->dirty_submodule)
+ dirty = "-dirty";
+
len = snprintf(data, 100,
- "Subproject commit %s\n", sha1_to_hex(s->sha1));
+ "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty);
s->data = data;
s->size = len;
s->should_free = 1;
@@ -2294,7 +2299,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);
@@ -3714,7 +3719,7 @@ int diff_result_code(struct diff_options *opt, int status)
void diff_addremove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
- const char *concatpath)
+ const char *concatpath, unsigned dirty_submodule)
{
struct diff_filespec *one, *two;
@@ -3746,8 +3751,10 @@ void diff_addremove(struct diff_options *options,
if (addremove != '+')
fill_filespec(one, sha1, mode);
- if (addremove != '-')
+ if (addremove != '-') {
fill_filespec(two, sha1, mode);
+ two->dirty_submodule = dirty_submodule;
+ }
diff_queue(&diff_queued_diff, one, two);
if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
@@ -3758,7 +3765,8 @@ void diff_change(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
- const char *concatpath)
+ const char *concatpath,
+ unsigned old_dirty_submodule, unsigned new_dirty_submodule)
{
struct diff_filespec *one, *two;
@@ -3771,6 +3779,8 @@ void diff_change(struct diff_options *options,
const unsigned char *tmp_c;
tmp = old_mode; old_mode = new_mode; new_mode = tmp;
tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
+ tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule;
+ new_dirty_submodule = tmp;
}
if (options->prefix &&
@@ -3781,6 +3791,8 @@ void diff_change(struct diff_options *options,
two = alloc_filespec(concatpath);
fill_filespec(one, old_sha1, old_mode);
fill_filespec(two, new_sha1, new_mode);
+ one->dirty_submodule = old_dirty_submodule;
+ two->dirty_submodule = new_dirty_submodule;
diff_queue(&diff_queued_diff, one, two);
if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
@@ -3818,6 +3830,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/diff.h b/diff.h
index 6f6d0ed01d..968a8dce95 100644
--- a/diff.h
+++ b/diff.h
@@ -14,12 +14,13 @@ typedef void (*change_fn_t)(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
- const char *fullpath);
+ const char *fullpath,
+ unsigned old_dirty_submodule, unsigned new_dirty_submodule);
typedef void (*add_remove_fn_t)(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
- const char *fullpath);
+ const char *fullpath, unsigned dirty_submodule);
typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
struct diff_options *options, void *data);
@@ -177,13 +178,14 @@ extern void diff_addremove(struct diff_options *,
int addremove,
unsigned mode,
const unsigned char *sha1,
- const char *fullpath);
+ const char *fullpath, unsigned dirty_submodule);
extern void diff_change(struct diff_options *,
unsigned mode1, unsigned mode2,
const unsigned char *sha1,
const unsigned char *sha2,
- const char *fullpath);
+ const char *fullpath,
+ unsigned dirty_submodule1, unsigned dirty_submodule2);
extern void diff_unmerge(struct diff_options *,
const char *path,
diff --git a/diffcore.h b/diffcore.h
index 5b634585e8..66687c3fe5 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -42,6 +42,7 @@ struct diff_filespec {
#define DIFF_FILE_VALID(spec) (((spec)->mode) != 0)
unsigned should_free : 1; /* data should be free()'ed */
unsigned should_munmap : 1; /* data should be munmap()'ed */
+ unsigned dirty_submodule : 1; /* For submodules: its work tree is dirty */
struct userdiff_driver *driver;
/* data should be considered "binary"; -1 means "don't know yet" */
diff --git a/dir.c b/dir.c
index d0999ba055..67c3af6a1a 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,24 +236,39 @@ 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;
+ if (size == 0) {
+ free(buf);
+ return 0;
+ }
+ if (buf[size-1] != '\n') {
+ buf = xrealloc(buf, size+1);
+ buf[size++] = '\n';
+ }
}
- 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+1);
+ if (read_in_full(fd, buf, size) != size) {
+ free(buf);
+ close(fd);
+ return -1;
+ }
+ buf[size++] = '\n';
+ 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') {
@@ -241,17 +280,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 +334,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 +346,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 +359,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 +422,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:
@@ -625,6 +665,92 @@ static int get_dtype(struct dirent *de, const char *path, int len)
return dtype;
}
+enum path_treatment {
+ path_ignored,
+ path_handled,
+ path_recurse,
+};
+
+static enum path_treatment treat_one_path(struct dir_struct *dir,
+ char *path, int *len,
+ const struct path_simplify *simplify,
+ int dtype, struct dirent *de)
+{
+ int exclude = excluded(dir, path, &dtype);
+ if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
+ && in_pathspec(path, *len, simplify))
+ dir_add_ignored(dir, path, *len);
+
+ /*
+ * Excluded? If we don't explicitly want to show
+ * ignored files, ignore it
+ */
+ if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
+ return path_ignored;
+
+ if (dtype == DT_UNKNOWN)
+ dtype = get_dtype(de, path, *len);
+
+ /*
+ * Do we want to see just the ignored files?
+ * We still need to recurse into directories,
+ * even if we don't ignore them, since the
+ * directory may contain files that we do..
+ */
+ if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
+ if (dtype != DT_DIR)
+ return path_ignored;
+ }
+
+ switch (dtype) {
+ default:
+ return path_ignored;
+ case DT_DIR:
+ memcpy(path + *len, "/", 2);
+ (*len)++;
+ switch (treat_directory(dir, path, *len, simplify)) {
+ case show_directory:
+ if (exclude != !!(dir->flags
+ & DIR_SHOW_IGNORED))
+ return path_ignored;
+ break;
+ case recurse_into_directory:
+ return path_recurse;
+ case ignore_directory:
+ return path_ignored;
+ }
+ break;
+ case DT_REG:
+ case DT_LNK:
+ break;
+ }
+ return path_handled;
+}
+
+static enum path_treatment treat_path(struct dir_struct *dir,
+ struct dirent *de,
+ char *path, int path_max,
+ int baselen,
+ const struct path_simplify *simplify,
+ int *len)
+{
+ int dtype;
+
+ if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
+ return path_ignored;
+ *len = strlen(de->d_name);
+ /* Ignore overly long pathnames! */
+ if (*len + baselen + 8 > path_max)
+ return path_ignored;
+ memcpy(path + baselen, de->d_name, *len + 1);
+ *len += baselen;
+ if (simplify_away(path, *len, simplify))
+ return path_ignored;
+
+ dtype = DTYPE(de);
+ return treat_one_path(dir, path, len, simplify, dtype, de);
+}
+
/*
* Read a directory tree. We currently ignore anything but
* directories, regular files and symlinks. That's because git
@@ -634,7 +760,10 @@ static int get_dtype(struct dirent *de, const char *path, int len)
* Also, we ignore the name ".git" (even if it is not a directory).
* That likely will not change.
*/
-static int read_directory_recursive(struct dir_struct *dir, const char *base, int baselen, int check_only, const struct path_simplify *simplify)
+static int read_directory_recursive(struct dir_struct *dir,
+ const char *base, int baselen,
+ int check_only,
+ const struct path_simplify *simplify)
{
DIR *fdir = opendir(*base ? base : ".");
int contents = 0;
@@ -645,70 +774,16 @@ static int read_directory_recursive(struct dir_struct *dir, const char *base, in
memcpy(path, base, baselen);
while ((de = readdir(fdir)) != NULL) {
- int len, dtype;
- int exclude;
-
- if (is_dot_or_dotdot(de->d_name) ||
- !strcmp(de->d_name, ".git"))
- continue;
- len = strlen(de->d_name);
- /* Ignore overly long pathnames! */
- if (len + baselen + 8 > sizeof(path))
+ int len;
+ switch (treat_path(dir, de, path, sizeof(path),
+ baselen, simplify, &len)) {
+ case path_recurse:
+ contents += read_directory_recursive
+ (dir, path, len, 0, simplify);
continue;
- memcpy(path + baselen, de->d_name, len+1);
- len = baselen + len;
- if (simplify_away(path, len, simplify))
+ case path_ignored:
continue;
-
- dtype = DTYPE(de);
- exclude = excluded(dir, path, &dtype);
- if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
- && in_pathspec(path, len, simplify))
- dir_add_ignored(dir, path,len);
-
- /*
- * Excluded? If we don't explicitly want to show
- * ignored files, ignore it
- */
- if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
- continue;
-
- if (dtype == DT_UNKNOWN)
- dtype = get_dtype(de, path, len);
-
- /*
- * Do we want to see just the ignored files?
- * We still need to recurse into directories,
- * even if we don't ignore them, since the
- * directory may contain files that we do..
- */
- if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
- if (dtype != DT_DIR)
- continue;
- }
-
- switch (dtype) {
- default:
- continue;
- case DT_DIR:
- memcpy(path + len, "/", 2);
- len++;
- switch (treat_directory(dir, path, len, simplify)) {
- case show_directory:
- if (exclude != !!(dir->flags
- & DIR_SHOW_IGNORED))
- continue;
- break;
- case recurse_into_directory:
- contents += read_directory_recursive(dir,
- path, len, 0, simplify);
- continue;
- case ignore_directory:
- continue;
- }
- break;
- case DT_REG:
- case DT_LNK:
+ case path_handled:
break;
}
contents++;
@@ -778,6 +853,41 @@ static void free_simplify(struct path_simplify *simplify)
free(simplify);
}
+static int treat_leading_path(struct dir_struct *dir,
+ const char *path, int len,
+ const struct path_simplify *simplify)
+{
+ char pathbuf[PATH_MAX];
+ int baselen, blen;
+ const char *cp;
+
+ while (len && path[len - 1] == '/')
+ len--;
+ if (!len)
+ return 1;
+ baselen = 0;
+ while (1) {
+ cp = path + baselen + !!baselen;
+ cp = memchr(cp, '/', path + len - cp);
+ if (!cp)
+ baselen = len;
+ else
+ baselen = cp - path;
+ memcpy(pathbuf, path, baselen);
+ pathbuf[baselen] = '\0';
+ if (!is_directory(pathbuf))
+ return 0;
+ if (simplify_away(pathbuf, baselen, simplify))
+ return 0;
+ blen = baselen;
+ if (treat_one_path(dir, pathbuf, &blen, simplify,
+ DT_DIR, NULL) == path_ignored)
+ return 0; /* do not recurse into it */
+ if (len <= baselen)
+ return 1; /* finished checking */
+ }
+}
+
int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec)
{
struct path_simplify *simplify;
@@ -786,7 +896,8 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const char
return dir->nr;
simplify = create_simplify(pathspec);
- read_directory_recursive(dir, path, len, 0, simplify);
+ if (!len || treat_leading_path(dir, path, len, simplify))
+ read_directory_recursive(dir, path, len, 0, simplify);
free_simplify(simplify);
qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
diff --git a/dir.h b/dir.h
index 320b6a2f38..3bead5f0e2 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 615f5754d6..d8340031d2 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 06d24f14c6..004182c99d 100644
--- a/entry.c
+++ b/entry.c
@@ -179,7 +179,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
* This is like 'lstat()', except it refuses to follow symlinks
* in the path, after skipping "skiplen".
*/
-int check_path(const char *path, int len, struct stat *st, int skiplen)
+static int check_path(const char *path, int len, struct stat *st, int skiplen)
{
const char *slash = path + len;
@@ -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 5171d9f9a4..739ec27040 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 cd87049871..901784fe91 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -245,6 +245,7 @@ struct branch
const char *name;
struct tree_entry branch_tree;
uintmax_t last_commit;
+ uintmax_t num_notes;
unsigned active : 1;
unsigned pack_id : PACK_ID_BITS;
unsigned char sha1[20];
@@ -295,6 +296,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 +321,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 +358,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 +464,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);
@@ -693,6 +703,7 @@ static struct branch *new_branch(const char *name)
b->table_next_branch = branch_table[hc];
b->branch_tree.versions[0].mode = S_IFDIR;
b->branch_tree.versions[1].mode = S_IFDIR;
+ b->num_notes = 0;
b->active = 0;
b->pack_id = MAX_PACK_ID;
branch_table[hc] = b;
@@ -1602,13 +1613,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 +1628,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 +1644,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 +1652,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 +1713,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;
@@ -1860,6 +1913,109 @@ static void load_branch(struct branch *b)
}
}
+static unsigned char convert_num_notes_to_fanout(uintmax_t num_notes)
+{
+ unsigned char fanout = 0;
+ while ((num_notes >>= 8))
+ fanout++;
+ return fanout;
+}
+
+static void construct_path_with_fanout(const char *hex_sha1,
+ unsigned char fanout, char *path)
+{
+ unsigned int i = 0, j = 0;
+ if (fanout >= 20)
+ die("Too large fanout (%u)", fanout);
+ while (fanout) {
+ path[i++] = hex_sha1[j++];
+ path[i++] = hex_sha1[j++];
+ path[i++] = '/';
+ fanout--;
+ }
+ memcpy(path + i, hex_sha1 + j, 40 - j);
+ path[i + 40 - j] = '\0';
+}
+
+static uintmax_t do_change_note_fanout(
+ struct tree_entry *orig_root, struct tree_entry *root,
+ char *hex_sha1, unsigned int hex_sha1_len,
+ char *fullpath, unsigned int fullpath_len,
+ unsigned char fanout)
+{
+ struct tree_content *t = root->tree;
+ struct tree_entry *e, leaf;
+ unsigned int i, tmp_hex_sha1_len, tmp_fullpath_len;
+ uintmax_t num_notes = 0;
+ unsigned char sha1[20];
+ char realpath[60];
+
+ for (i = 0; t && i < t->entry_count; i++) {
+ e = t->entries[i];
+ tmp_hex_sha1_len = hex_sha1_len + e->name->str_len;
+ tmp_fullpath_len = fullpath_len;
+
+ /*
+ * We're interested in EITHER existing note entries (entries
+ * with exactly 40 hex chars in path, not including directory
+ * separators), OR directory entries that may contain note
+ * entries (with < 40 hex chars in path).
+ * Also, each path component in a note entry must be a multiple
+ * of 2 chars.
+ */
+ if (!e->versions[1].mode ||
+ tmp_hex_sha1_len > 40 ||
+ e->name->str_len % 2)
+ continue;
+
+ /* This _may_ be a note entry, or a subdir containing notes */
+ memcpy(hex_sha1 + hex_sha1_len, e->name->str_dat,
+ e->name->str_len);
+ if (tmp_fullpath_len)
+ fullpath[tmp_fullpath_len++] = '/';
+ memcpy(fullpath + tmp_fullpath_len, e->name->str_dat,
+ e->name->str_len);
+ tmp_fullpath_len += e->name->str_len;
+ fullpath[tmp_fullpath_len] = '\0';
+
+ if (tmp_hex_sha1_len == 40 && !get_sha1_hex(hex_sha1, sha1)) {
+ /* This is a note entry */
+ construct_path_with_fanout(hex_sha1, fanout, realpath);
+ if (!strcmp(fullpath, realpath)) {
+ /* Note entry is in correct location */
+ num_notes++;
+ continue;
+ }
+
+ /* Rename fullpath to realpath */
+ if (!tree_content_remove(orig_root, fullpath, &leaf))
+ die("Failed to remove path %s", fullpath);
+ tree_content_set(orig_root, realpath,
+ leaf.versions[1].sha1,
+ leaf.versions[1].mode,
+ leaf.tree);
+ } else if (S_ISDIR(e->versions[1].mode)) {
+ /* This is a subdir that may contain note entries */
+ if (!e->tree)
+ load_tree(e);
+ num_notes += do_change_note_fanout(orig_root, e,
+ hex_sha1, tmp_hex_sha1_len,
+ fullpath, tmp_fullpath_len, fanout);
+ }
+
+ /* The above may have reallocated the current tree_content */
+ t = root->tree;
+ }
+ return num_notes;
+}
+
+static uintmax_t change_note_fanout(struct tree_entry *root,
+ unsigned char fanout)
+{
+ char hex_sha1[40], path[60];
+ return do_change_note_fanout(root, root, hex_sha1, 0, path, 0, fanout);
+}
+
static void file_change_m(struct branch *b)
{
const char *p = command_buf.buf + 2;
@@ -2010,14 +2166,16 @@ static void file_change_cr(struct branch *b, int rename)
leaf.tree);
}
-static void note_change_n(struct branch *b)
+static void note_change_n(struct branch *b, unsigned char old_fanout)
{
const char *p = command_buf.buf + 2;
static struct strbuf uq = STRBUF_INIT;
struct object_entry *oe = oe;
struct branch *s;
unsigned char sha1[20], commit_sha1[20];
+ char path[60];
uint16_t inline_data = 0;
+ unsigned char new_fanout;
/* <dataref> or 'inline' */
if (*p == ':') {
@@ -2071,7 +2229,7 @@ static void note_change_n(struct branch *b)
if (oe->type != OBJ_BLOB)
die("Not a blob (actually a %s): %s",
typename(oe->type), command_buf.buf);
- } else {
+ } else if (!is_null_sha1(sha1)) {
enum object_type type = sha1_object_info(sha1, NULL);
if (type < 0)
die("Blob not found: %s", command_buf.buf);
@@ -2080,8 +2238,17 @@ static void note_change_n(struct branch *b)
typename(type), command_buf.buf);
}
- tree_content_set(&b->branch_tree, sha1_to_hex(commit_sha1), sha1,
- S_IFREG | 0644, NULL);
+ construct_path_with_fanout(sha1_to_hex(commit_sha1), old_fanout, path);
+ if (tree_content_remove(&b->branch_tree, path, NULL))
+ b->num_notes--;
+
+ if (is_null_sha1(sha1))
+ return; /* nothing to insert */
+
+ b->num_notes++;
+ new_fanout = convert_num_notes_to_fanout(b->num_notes);
+ construct_path_with_fanout(sha1_to_hex(commit_sha1), new_fanout, path);
+ tree_content_set(&b->branch_tree, path, sha1, S_IFREG | 0644, NULL);
}
static void file_change_deleteall(struct branch *b)
@@ -2090,6 +2257,7 @@ static void file_change_deleteall(struct branch *b)
hashclr(b->branch_tree.versions[0].sha1);
hashclr(b->branch_tree.versions[1].sha1);
load_tree(&b->branch_tree);
+ b->num_notes = 0;
}
static void parse_from_commit(struct branch *b, char *buf, unsigned long size)
@@ -2213,6 +2381,7 @@ static void parse_new_commit(void)
char *committer = NULL;
struct hash_list *merge_list = NULL;
unsigned int merge_count;
+ unsigned char prev_fanout, new_fanout;
/* Obtain the branch name from the rest of our command */
sp = strchr(command_buf.buf, ' ') + 1;
@@ -2243,6 +2412,8 @@ static void parse_new_commit(void)
load_branch(b);
}
+ prev_fanout = convert_num_notes_to_fanout(b->num_notes);
+
/* file_change* */
while (command_buf.len > 0) {
if (!prefixcmp(command_buf.buf, "M "))
@@ -2254,7 +2425,7 @@ static void parse_new_commit(void)
else if (!prefixcmp(command_buf.buf, "C "))
file_change_cr(b, 0);
else if (!prefixcmp(command_buf.buf, "N "))
- note_change_n(b);
+ note_change_n(b, prev_fanout);
else if (!strcmp("deleteall", command_buf.buf))
file_change_deleteall(b);
else {
@@ -2265,6 +2436,10 @@ static void parse_new_commit(void)
break;
}
+ new_fanout = convert_num_notes_to_fanout(b->num_notes);
+ if (new_fanout != prev_fanout)
+ b->num_notes = change_note_fanout(&b->branch_tree, new_fanout);
+
/* build the tree and the commit */
store_tree(&b->branch_tree);
hashcpy(b->branch_tree.versions[0].sha1,
@@ -2305,6 +2480,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 +2501,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 +2537,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 +2595,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 +2755,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 +2802,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 +2826,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 4838cdb9ed..2f46fda47b 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 5c596875c2..620a7c6371 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
@@ -177,6 +178,9 @@ extern char *gitbasename(char *);
#ifdef __GNUC__
#define NORETURN __attribute__((__noreturn__))
#define NORETURN_PTR __attribute__((__noreturn__))
+#elif defined(_MSC_VER)
+#define NORETURN __declspec(noreturn)
+#define NORETURN_PTR
#else
#define NORETURN
#define NORETURN_PTR
@@ -198,7 +202,7 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)))
extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
extern int prefixcmp(const char *str, const char *prefix);
-extern time_t tm_to_time_t(const struct tm *tm);
+extern int suffixcmp(const char *str, const char *suffix);
static inline const char *skip_prefix(const char *str, const char *prefix)
{
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index a7d215c8aa..4853bf7a0d 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -609,16 +609,16 @@ $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
my %index; # holds filenames of one index per branch
unless (-d $git_dir) {
- system("git-init");
+ system(qw(git init));
die "Cannot init the GIT db at $git_tree: $?\n" if $?;
- system("git-read-tree");
+ system(qw(git read-tree));
die "Cannot init an empty tree: $?\n" if $?;
$last_branch = $opt_o;
$orig_branch = "";
} else {
- open(F, "git-symbolic-ref HEAD |") or
- die "Cannot run git-symbolic-ref: $!\n";
+ open(F, "-|", qw(git symbolic-ref HEAD)) or
+ die "Cannot run git symbolic-ref: $!\n";
chomp ($last_branch = <F>);
$last_branch = basename($last_branch);
close(F);
@@ -627,12 +627,12 @@ unless (-d $git_dir) {
$last_branch = "master";
}
$orig_branch = $last_branch;
- $tip_at_start = `git-rev-parse --verify HEAD`;
+ $tip_at_start = `git rev-parse --verify HEAD`;
# Get the last import timestamps
my $fmt = '($ref, $author) = (%(refname), %(author));';
- open(H, "git-for-each-ref --perl --format='$fmt' $remote |") or
- die "Cannot run git-for-each-ref: $!\n";
+ my @cmd = ('git', 'for-each-ref', '--perl', "--format=$fmt", $remote);
+ open(H, "-|", @cmd) or die "Cannot run git for-each-ref: $!\n";
while (defined(my $entry = <H>)) {
my ($ref, $author);
eval($entry) || die "cannot eval refs list: $@";
@@ -687,7 +687,7 @@ unless ($opt_P) {
print $cvspsfh $_;
}
close CVSPS;
- $? == 0 or die "git-cvsimport: fatal: cvsps reported error\n";
+ $? == 0 or die "git cvsimport: fatal: cvsps reported error\n";
close $cvspsfh;
} else {
$cvspsfile = munge_user_filename($opt_P);
@@ -716,27 +716,27 @@ my $state = 0;
sub update_index (\@\@) {
my $old = shift;
my $new = shift;
- open(my $fh, '|-', qw(git-update-index -z --index-info))
- or die "unable to open git-update-index: $!";
+ open(my $fh, '|-', qw(git update-index -z --index-info))
+ or die "unable to open git update-index: $!";
print $fh
(map { "0 0000000000000000000000000000000000000000\t$_\0" }
@$old),
(map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" }
@$new)
- or die "unable to write to git-update-index: $!";
+ or die "unable to write to git update-index: $!";
close $fh
- or die "unable to write to git-update-index: $!";
- $? and die "git-update-index reported error: $?";
+ or die "unable to write to git update-index: $!";
+ $? and die "git update-index reported error: $?";
}
sub write_tree () {
- open(my $fh, '-|', qw(git-write-tree))
- or die "unable to open git-write-tree: $!";
+ open(my $fh, '-|', qw(git write-tree))
+ or die "unable to open git write-tree: $!";
chomp(my $tree = <$fh>);
is_sha1($tree)
or die "Cannot get tree id ($tree): $!";
close($fh)
- or die "Error running git-write-tree: $?\n";
+ or die "Error running git write-tree: $?\n";
print "Tree ID $tree\n" if $opt_v;
return $tree;
}
@@ -751,7 +751,7 @@ sub commit {
if ($branch eq $opt_o && !$index{branch} &&
!get_headref("$remote/$branch")) {
# looks like an initial commit
- # use the index primed by git-init
+ # use the index primed by git init
$ENV{GIT_INDEX_FILE} = "$git_dir/index";
$index{$branch} = "$git_dir/index";
} else {
@@ -761,9 +761,9 @@ sub commit {
$index{$branch} = tmpnam();
$ENV{GIT_INDEX_FILE} = $index{$branch};
if ($ancestor) {
- system("git-read-tree", "$remote/$ancestor");
+ system("git", "read-tree", "$remote/$ancestor");
} else {
- system("git-read-tree", "$remote/$branch");
+ system("git", "read-tree", "$remote/$branch");
}
die "read-tree failed: $?\n" if $?;
}
@@ -798,7 +798,7 @@ sub commit {
$ENV{GIT_COMMITTER_EMAIL} = $author_email;
$ENV{GIT_COMMITTER_DATE} = $commit_date;
my $pid = open2(my $commit_read, my $commit_write,
- 'git-commit-tree', $tree, @commit_args);
+ 'git', 'commit-tree', $tree, @commit_args);
# compatibility with git2cvs
substr($logmsg,32767) = "" if length($logmsg) > 32767;
@@ -811,7 +811,7 @@ sub commit {
}
print($commit_write "$logmsg\n") && close($commit_write)
- or die "Error writing to git-commit-tree: $!\n";
+ or die "Error writing to git commit-tree: $!\n";
print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v;
chomp(my $cid = <$commit_read>);
@@ -820,9 +820,9 @@ sub commit {
close($commit_read);
waitpid($pid,0);
- die "Error running git-commit-tree: $?\n" if $?;
+ die "Error running git commit-tree: $?\n" if $?;
- system('git-update-ref', "$remote/$branch", $cid) == 0
+ system('git' , 'update-ref', "$remote/$branch", $cid) == 0
or die "Cannot write branch $branch for update: $!\n";
if ($tag) {
@@ -832,7 +832,7 @@ sub commit {
$xtag =~ s/[\/]/$opt_s/g;
$xtag =~ s/\[//g;
- system('git-tag', '-f', $xtag, $cid) == 0
+ system('git' , 'tag', '-f', $xtag, $cid) == 0
or die "Cannot create tag $xtag: $!\n";
print "Created tag '$xtag' on '$branch'\n" if $opt_v;
@@ -969,7 +969,7 @@ while (<CVS>) {
my $pid = open(my $F, '-|');
die $! unless defined $pid;
if (!$pid) {
- exec("git-hash-object", "-w", $tmpname)
+ exec("git", "hash-object", "-w", $tmpname)
or die "Cannot create object: $!\n";
}
my $sha = <$F>;
@@ -993,7 +993,7 @@ while (<CVS>) {
}
commit();
if (($commitcount & 1023) == 0) {
- system("git repack -a -d");
+ system(qw(git repack -a -d));
}
$state = 1;
} elsif ($state == 11 and /^-+$/) {
@@ -1013,11 +1013,11 @@ unless ($opt_P) {
# The heuristic of repacking every 1024 commits can leave a
# lot of unpacked data. If there is more than 1MB worth of
# not-packed objects, repack once more.
-my $line = `git-count-objects`;
+my $line = `git count-objects`;
if ($line =~ /^(\d+) objects, (\d+) kilobytes$/) {
my ($n_objects, $kb) = ($1, $2);
1024 < $kb
- and system("git repack -a -d");
+ and system(qw(git repack -a -d));
}
foreach my $git_index (values %index) {
@@ -1038,28 +1038,28 @@ if ($orig_branch) {
if ($opt_i) {
exit 0;
}
- my $tip_at_end = `git-rev-parse --verify HEAD`;
+ my $tip_at_end = `git rev-parse --verify HEAD`;
if ($tip_at_start ne $tip_at_end) {
for ($tip_at_start, $tip_at_end) { chomp; }
print "Fetched into the current branch.\n" if $opt_v;
- system(qw(git-read-tree -u -m),
+ system(qw(git read-tree -u -m),
$tip_at_start, $tip_at_end);
die "Fast-forward update failed: $?\n" if $?;
}
else {
- system(qw(git-merge cvsimport HEAD), "$remote/$opt_o");
+ system(qw(git merge cvsimport HEAD), "$remote/$opt_o");
die "Could not merge $opt_o into the current branch.\n" if $?;
}
} else {
$orig_branch = "master";
print "DONE; creating $orig_branch branch\n" if $opt_v;
- system("git-update-ref", "refs/heads/master", "$remote/$opt_o")
+ system("git", "update-ref", "refs/heads/master", "$remote/$opt_o")
unless defined get_headref('refs/heads/master');
- system("git-symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
+ system("git", "symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
if ($opt_r && $opt_o ne 'HEAD');
- system('git-update-ref', 'HEAD', "$orig_branch");
+ system('git', 'update-ref', 'HEAD', "$orig_branch");
unless ($opt_i) {
- system('git checkout -f');
+ system(qw(git checkout -f));
die "checkout failed: $?\n" if $?;
}
}
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index 15da2789e0..28041060c8 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -104,6 +104,7 @@ $log->info("--------------- STARTING -----------------");
my $usage =
"Usage: git cvsserver [options] [pserver|server] [<directory> ...]\n".
" --base-path <path> : Prepend to requested CVSROOT\n".
+ " Can be read from GIT_CVSSERVER_BASE_PATH\n".
" --strict-paths : Don't allow recursing into subdirectories\n".
" --export-all : Don't check for gitcvs.enabled in config\n".
" --version, -V : Print version information and exit\n".
@@ -111,7 +112,8 @@ my $usage =
"\n".
"<directory> ... is a list of allowed directories. If no directories\n".
"are given, all are allowed. This is an additional restriction, gitcvs\n".
- "access still needs to be enabled by the gitcvs.enabled config option.\n";
+ "access still needs to be enabled by the gitcvs.enabled config option.\n".
+ "Alternately, one directory may be specified in GIT_CVSSERVER_ROOT.\n";
my @opts = ( 'help|h|H', 'version|V',
'base-path=s', 'strict-paths', 'export-all' );
@@ -148,6 +150,24 @@ if ($state->{'export-all'} && !@{$state->{allowed_roots}}) {
die "--export-all can only be used together with an explicit whitelist\n";
}
+# Environment handling for running under git-shell
+if (exists $ENV{GIT_CVSSERVER_BASE_PATH}) {
+ if ($state->{'base-path'}) {
+ die "Cannot specify base path both ways.\n";
+ }
+ my $base_path = $ENV{GIT_CVSSERVER_BASE_PATH};
+ $state->{'base-path'} = $base_path;
+ $log->debug("Picked up base path '$base_path' from environment.\n");
+}
+if (exists $ENV{GIT_CVSSERVER_ROOT}) {
+ if (@{$state->{allowed_roots}}) {
+ die "Cannot specify roots both ways: @ARGV\n";
+ }
+ my $allowed_root = $ENV{GIT_CVSSERVER_ROOT};
+ $state->{allowed_roots} = [ $allowed_root ];
+ $log->debug("Picked up allowed root '$allowed_root' from environment.\n");
+}
+
# if we are called with a pserver argument,
# deal with the authentication cat before entering the
# main loop
diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh
index 57e8e3256d..524f5ea8ab 100755
--- a/git-difftool--helper.sh
+++ b/git-difftool--helper.sh
@@ -3,16 +3,16 @@
# This script is typically launched by using the 'git difftool'
# convenience command.
#
-# Copyright (c) 2009 David Aguilar
+# Copyright (c) 2009, 2010 David Aguilar
-# Load common functions from git-mergetool--lib
TOOL_MODE=diff
. git-mergetool--lib
# difftool.prompt controls the default prompt/no-prompt behavior
# and is overridden with $GIT_DIFFTOOL*_PROMPT.
should_prompt () {
- prompt=$(git config --bool difftool.prompt || echo true)
+ prompt_merge=$(git config --bool mergetool.prompt || echo true)
+ prompt=$(git config --bool difftool.prompt || echo $prompt_merge)
if test "$prompt" = true; then
test -z "$GIT_DIFFTOOL_NO_PROMPT"
else
@@ -20,7 +20,11 @@ should_prompt () {
fi
}
-# Sets up shell variables and runs a merge tool
+# Indicates that --extcmd=... was specified
+use_ext_cmd () {
+ test -n "$GIT_DIFFTOOL_EXTCMD"
+}
+
launch_merge_tool () {
# Merged is the filename as it appears in the work tree
# Local is the contents of a/filename
@@ -35,20 +39,28 @@ launch_merge_tool () {
# the user with the real $MERGED name before launching $merge_tool.
if should_prompt; then
printf "\nViewing: '$MERGED'\n"
- printf "Hit return to launch '%s': " "$merge_tool"
+ if use_ext_cmd; then
+ printf "Hit return to launch '%s': " \
+ "$GIT_DIFFTOOL_EXTCMD"
+ else
+ printf "Hit return to launch '%s': " "$merge_tool"
+ fi
read ans
fi
- # Run the appropriate merge tool command
- run_merge_tool "$merge_tool"
+ if use_ext_cmd; then
+ eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"'
+ else
+ run_merge_tool "$merge_tool"
+ fi
}
-# Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values
-test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL"
-test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL"
-
-if test -z "$merge_tool"; then
- merge_tool="$(get_merge_tool)" || exit
+if ! use_ext_cmd; then
+ if test -n "$GIT_DIFF_TOOL"; then
+ merge_tool="$GIT_DIFF_TOOL"
+ else
+ merge_tool="$(get_merge_tool)" || exit
+ fi
fi
# Launch the merge tool on each path provided by 'git diff'
diff --git a/git-difftool.perl b/git-difftool.perl
index ba5e60a45e..d975d072db 100755
--- a/git-difftool.perl
+++ b/git-difftool.perl
@@ -1,5 +1,5 @@
#!/usr/bin/env perl
-# Copyright (c) 2009 David Aguilar
+# Copyright (c) 2009, 2010 David Aguilar
#
# This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
# git-difftool--helper script.
@@ -15,13 +15,17 @@ use warnings;
use Cwd qw(abs_path);
use File::Basename qw(dirname);
+require Git;
+
my $DIR = abs_path(dirname($0));
sub usage
{
print << 'USAGE';
-usage: git difftool [--tool=<tool>] [-y|--no-prompt] ["git diff" options]
+usage: git difftool [-t|--tool=<tool>] [-x|--extcmd=<cmd>]
+ [-y|--no-prompt] [-g|--gui]
+ ['git diff' options]
USAGE
exit 1;
}
@@ -63,6 +67,24 @@ sub generate_command
$ENV{GIT_DIFF_TOOL} = substr($arg, 7);
next;
}
+ if ($arg eq '-x' || $arg eq '--extcmd') {
+ usage() if $#ARGV <= $idx;
+ $ENV{GIT_DIFFTOOL_EXTCMD} = $ARGV[$idx + 1];
+ $skip_next = 1;
+ next;
+ }
+ if ($arg =~ /^--extcmd=/) {
+ $ENV{GIT_DIFFTOOL_EXTCMD} = substr($arg, 9);
+ next;
+ }
+ if ($arg eq '-g' || $arg eq '--gui') {
+ my $tool = Git::command_oneline('config',
+ 'diff.guitool');
+ if (length($tool)) {
+ $ENV{GIT_DIFF_TOOL} = $tool;
+ }
+ next;
+ }
if ($arg eq '-y' || $arg eq '--no-prompt') {
$ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
delete $ENV{GIT_DIFFTOOL_PROMPT};
diff --git a/git-pull.sh b/git-pull.sh
index 9e69ada413..38331a8611 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -13,11 +13,33 @@ set_reflog_action "pull $*"
require_work_tree
cd_to_toplevel
-test -z "$(git ls-files -u)" ||
- die "You are in the middle of a conflicted merge."
+
+die_conflict () {
+ git diff-index --cached --name-status -r --ignore-submodules HEAD --
+ if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
+ die "Pull is not possible because you have unmerged files.
+Please, fix them up in the work tree, and then use 'git add/rm <file>'
+as appropriate to mark resolution, or use 'git commit -a'."
+ else
+ die "Pull is not possible because you have unmerged files."
+ fi
+}
+
+die_merge () {
+ if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
+ die "You have not concluded your merge (MERGE_HEAD exists).
+Please, commit your changes before you can merge."
+ else
+ die "You have not concluded your merge (MERGE_HEAD exists)."
+ fi
+}
+
+test -z "$(git ls-files -u)" || die_conflict
+test -f "$GIT_DIR/MERGE_HEAD" && die_merge
strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
log_arg= verbosity=
+merge_args=
curr_branch=$(git symbolic-ref -q HEAD)
curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||")
rebase=$(git config --bool branch.$curr_branch_short.rebase)
@@ -62,6 +84,18 @@ do
esac
strategy_args="${strategy_args}-s $strategy "
;;
+ -X*)
+ case "$#,$1" in
+ 1,-X)
+ usage ;;
+ *,-X)
+ xx="-X $(git rev-parse --sq-quote "$2")"
+ shift ;;
+ *,*)
+ xx=$(git rev-parse --sq-quote "$1") ;;
+ esac
+ merge_args="$merge_args$xx "
+ ;;
-r|--r|--re|--reb|--reba|--rebas|--rebase)
rebase=true
;;
@@ -233,8 +267,15 @@ then
fi
merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
-test true = "$rebase" &&
- exec git-rebase $diffstat $strategy_args --onto $merge_head \
- ${oldremoteref:-$merge_head}
-exec git-merge $diffstat $no_commit $squash $no_ff $ff_only $log_arg $strategy_args \
- "$merge_name" HEAD $merge_head $verbosity
+case "$rebase" in
+true)
+ eval="git-rebase $diffstat $strategy_args $merge_args"
+ eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
+ ;;
+*)
+ eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only"
+ eval="$eval $log_arg $strategy_args $merge_args"
+ eval="$eval \"\$merge_name\" HEAD $merge_head $verbosity"
+ ;;
+esac
+eval "exec $eval"
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index d52932878c..9187e9bdba 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -28,24 +28,81 @@ abort abort rebasing process and restore original branch
skip skip current patch and continue rebasing process
no-verify override pre-rebase hook from stopping the operation
root rebase all reachable commmits up to the root(s)
+autosquash move commits that begin with squash!/fixup! under -i
"
. git-sh-setup
require_work_tree
DOTEST="$GIT_DIR/rebase-merge"
+
+# The file containing rebase commands, comments, and empty lines.
+# This file is created by "git rebase -i" then edited by the user. As
+# the lines are processed, they are removed from the front of this
+# file and written to the tail of $DONE.
TODO="$DOTEST"/git-rebase-todo
+
+# The rebase command lines that have already been processed. A line
+# is moved here when it is first handled, before any associated user
+# actions.
DONE="$DOTEST"/done
+
+# The commit message that is planned to be used for any changes that
+# need to be committed following a user interaction.
MSG="$DOTEST"/message
+
+# The file into which is accumulated the suggested commit message for
+# squash/fixup commands. When the first of a series of squash/fixups
+# is seen, the file is created and the commit message from the
+# previous commit and from the first squash/fixup commit are written
+# to it. The commit message for each subsequent squash/fixup commit
+# is appended to the file as it is processed.
+#
+# The first line of the file is of the form
+# # This is a combination of $COUNT commits.
+# where $COUNT is the number of commits whose messages have been
+# written to the file so far (including the initial "pick" commit).
+# Each time that a commit message is processed, this line is read and
+# updated. It is deleted just before the combined commit is made.
SQUASH_MSG="$DOTEST"/message-squash
+
+# If the current series of squash/fixups has not yet included a squash
+# command, then this file exists and holds the commit message of the
+# original "pick" commit. (If the series ends without a "squash"
+# command, then this can be used as the commit message of the combined
+# commit without opening the editor.)
+FIXUP_MSG="$DOTEST"/message-fixup
+
+# $REWRITTEN is the name of a directory containing files for each
+# commit that is reachable by at least one merge base of $HEAD and
+# $UPSTREAM. They are not necessarily rewritten, but their children
+# might be. This ensures that commits on merged, but otherwise
+# unrelated side branches are left alone. (Think "X" in the man page's
+# example.)
REWRITTEN="$DOTEST"/rewritten
+
DROPPED="$DOTEST"/dropped
+
+# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
+# GIT_AUTHOR_DATE that will be used for the commit that is currently
+# being rebased.
+AUTHOR_SCRIPT="$DOTEST"/author-script
+
+# When an "edit" rebase command is being processed, the SHA1 of the
+# commit to be edited is recorded in this file. When "git rebase
+# --continue" is executed, if there are any staged changes then they
+# will be amended to the HEAD commit, but only provided the HEAD
+# commit is still the commit to be edited. When any other rebase
+# command is processed, this file is deleted.
+AMEND="$DOTEST"/amend
+
PRESERVE_MERGES=
STRATEGY=
ONTO=
VERBOSE=
OK_TO_SKIP_PRE_REBASE=
REBASE_ROOT=
+AUTOSQUASH=
GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
@@ -70,6 +127,11 @@ output () {
esac
}
+# Output the commit message for the specified commit.
+commit_message () {
+ git cat-file commit "$1" | sed "1,/^$/d"
+}
+
run_pre_rebase_hook () {
if test -z "$OK_TO_SKIP_PRE_REBASE" &&
test -x "$GIT_DIR/hooks/pre-rebase"
@@ -129,10 +191,10 @@ make_patch () {
echo "Root commit"
;;
esac > "$DOTEST"/patch
- test -f "$DOTEST"/message ||
- git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message
- test -f "$DOTEST"/author-script ||
- get_author_ident_from_commit "$1" > "$DOTEST"/author-script
+ test -f "$MSG" ||
+ commit_message "$1" > "$MSG"
+ test -f "$AUTHOR_SCRIPT" ||
+ get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT"
}
die_with_patch () {
@@ -150,13 +212,22 @@ has_action () {
sane_grep '^[^#]' "$1" >/dev/null
}
+# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
+# GIT_AUTHOR_DATE exported from the current environment.
+do_with_author () {
+ (
+ export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+ "$@"
+ )
+}
+
pick_one () {
no_ff=
case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
test -d "$REWRITTEN" &&
pick_one_preserving_merges "$@" && return
- if test ! -z "$REBASE_ROOT"
+ if test -n "$REBASE_ROOT"
then
output git cherry-pick "$@"
return
@@ -164,11 +235,10 @@ pick_one () {
parent_sha1=$(git rev-parse --verify $sha1^) ||
die "Could not get the parent of $sha1"
current_sha1=$(git rev-parse --verify HEAD)
- if test "$no_ff$current_sha1" = "$parent_sha1"; then
+ if test -z "$no_ff" && test "$current_sha1" = "$parent_sha1"
+ then
output git reset --hard $sha1
- test "a$1" = a-n && output git reset --soft $current_sha1
- sha1=$(git rev-parse --short $sha1)
- output warn Fast-forward to $sha1
+ output warn Fast-forward to $(git rev-parse --short $sha1)
else
output git cherry-pick "$@"
fi
@@ -269,14 +339,11 @@ pick_one_preserving_merges () {
# redo merge
author_script=$(get_author_ident_from_commit $sha1)
eval "$author_script"
- msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')"
+ msg="$(commit_message $sha1)"
# No point in merging the first parent, that's HEAD
new_parents=${new_parents# $first_parent}
- if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
- GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
- GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
- output git merge $STRATEGY -m "$msg" \
- $new_parents
+ if ! do_with_author output \
+ git merge $STRATEGY -m "$msg" $new_parents
then
printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
die_with_patch $sha1 "Error redoing merge $sha1"
@@ -300,34 +367,66 @@ nth_string () {
esac
}
-make_squash_message () {
+update_squash_messages () {
if test -f "$SQUASH_MSG"; then
- COUNT=$(($(sed -n "s/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p" \
- < "$SQUASH_MSG" | sed -ne '$p')+1))
- echo "# This is a combination of $COUNT commits."
- sed -e 1d -e '2,/^./{
- /^$/d
- }' <"$SQUASH_MSG"
+ mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit
+ COUNT=$(($(sed -n \
+ -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \
+ -e "q" < "$SQUASH_MSG".bak)+1))
+ {
+ echo "# This is a combination of $COUNT commits."
+ sed -e 1d -e '2,/^./{
+ /^$/d
+ }' <"$SQUASH_MSG".bak
+ } >$SQUASH_MSG
else
+ commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG"
COUNT=2
- echo "# This is a combination of two commits."
- echo "# The first commit's message is:"
- echo
- git cat-file commit HEAD | sed -e '1,/^$/d'
+ {
+ echo "# This is a combination of 2 commits."
+ echo "# The first commit's message is:"
+ echo
+ cat "$FIXUP_MSG"
+ } >$SQUASH_MSG
fi
- echo
- echo "# This is the $(nth_string $COUNT) commit message:"
- echo
- git cat-file commit $1 | sed -e '1,/^$/d'
+ case $1 in
+ squash)
+ rm -f "$FIXUP_MSG"
+ echo
+ echo "# This is the $(nth_string $COUNT) commit message:"
+ echo
+ commit_message $2
+ ;;
+ fixup)
+ echo
+ echo "# The $(nth_string $COUNT) commit message will be skipped:"
+ echo
+ commit_message $2 | sed -e 's/^/# /'
+ ;;
+ esac >>$SQUASH_MSG
}
peek_next_command () {
- sed -n "1s/ .*$//p" < "$TODO"
+ sed -n -e "/^#/d" -e "/^$/d" -e "s/ .*//p" -e "q" < "$TODO"
+}
+
+# A squash/fixup has failed. Prepare the long version of the squash
+# commit message, then die_with_patch. This code path requires the
+# user to edit the combined commit message for all commits that have
+# been squashed/fixedup so far. So also erase the old squash
+# messages, effectively causing the combined commit to be used as the
+# new basis for any further squash/fixups. Args: sha1 rest
+die_failed_squash() {
+ mv "$SQUASH_MSG" "$MSG" || exit
+ rm -f "$FIXUP_MSG"
+ cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit
+ warn
+ warn "Could not apply $1... $2"
+ die_with_patch $1 ""
}
do_next () {
- rm -f "$DOTEST"/message "$DOTEST"/author-script \
- "$DOTEST"/amend || exit
+ rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
read command sha1 rest < "$TODO"
case "$command" in
'#'*|''|noop)
@@ -355,7 +454,7 @@ do_next () {
pick_one $sha1 ||
die_with_patch $sha1 "Could not apply $sha1... $rest"
make_patch $sha1
- git rev-parse --verify HEAD > "$DOTEST"/amend
+ git rev-parse --verify HEAD > "$AMEND"
warn "Stopped at $sha1... $rest"
warn "You can amend the commit now, with"
warn
@@ -367,52 +466,49 @@ do_next () {
warn
exit 0
;;
- squash|s)
- comment_for_reflog squash
+ squash|s|fixup|f)
+ case "$command" in
+ squash|s)
+ squash_style=squash
+ ;;
+ fixup|f)
+ squash_style=fixup
+ ;;
+ esac
+ comment_for_reflog $squash_style
test -f "$DONE" && has_action "$DONE" ||
- die "Cannot 'squash' without a previous commit"
+ die "Cannot '$squash_style' without a previous commit"
mark_action_done
- make_squash_message $sha1 > "$MSG"
- failed=f
+ update_squash_messages $squash_style $sha1
author_script=$(get_author_ident_from_commit HEAD)
+ echo "$author_script" > "$AUTHOR_SCRIPT"
+ eval "$author_script"
output git reset --soft HEAD^
- pick_one -n $sha1 || failed=t
+ pick_one -n $sha1 || die_failed_squash $sha1 "$rest"
case "$(peek_next_command)" in
- squash|s)
- USE_OUTPUT=output
- MSG_OPT=-F
- EDIT_OR_FILE="$MSG"
- cp "$MSG" "$SQUASH_MSG"
+ squash|s|fixup|f)
+ # This is an intermediate commit; its message will only be
+ # used in case of trouble. So use the long version:
+ do_with_author output git commit --no-verify -F "$SQUASH_MSG" ||
+ die_failed_squash $sha1 "$rest"
;;
*)
- USE_OUTPUT=
- MSG_OPT=
- EDIT_OR_FILE=-e
- rm -f "$SQUASH_MSG" || exit
- cp "$MSG" "$GIT_DIR"/SQUASH_MSG
- rm -f "$GIT_DIR"/MERGE_MSG || exit
+ # This is the final command of this squash/fixup group
+ if test -f "$FIXUP_MSG"
+ then
+ do_with_author git commit --no-verify -F "$FIXUP_MSG" ||
+ die_failed_squash $sha1 "$rest"
+ else
+ cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit
+ rm -f "$GIT_DIR"/MERGE_MSG
+ do_with_author git commit --no-verify -e ||
+ die_failed_squash $sha1 "$rest"
+ fi
+ rm -f "$SQUASH_MSG" "$FIXUP_MSG"
;;
esac
- echo "$author_script" > "$DOTEST"/author-script
- if test $failed = f
- then
- # This is like --amend, but with a different message
- eval "$author_script"
- GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
- GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
- GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
- $USE_OUTPUT git commit --no-verify \
- $MSG_OPT "$EDIT_OR_FILE" || failed=t
- fi
- if test $failed = t
- then
- cp "$MSG" "$GIT_DIR"/MERGE_MSG
- warn
- warn "Could not apply $sha1... $rest"
- die_with_patch $sha1 ""
- fi
;;
*)
warn "Unknown command: $command $sha1 $rest"
@@ -495,6 +591,56 @@ get_saved_options () {
test -f "$DOTEST"/rebase-root && REBASE_ROOT=t
}
+# Rearrange the todo list that has both "pick sha1 msg" and
+# "pick sha1 fixup!/squash! msg" appears in it so that the latter
+# comes immediately after the former, and change "pick" to
+# "fixup"/"squash".
+rearrange_squash () {
+ sed -n -e 's/^pick \([0-9a-f]*\) \(squash\)! /\1 \2 /p' \
+ -e 's/^pick \([0-9a-f]*\) \(fixup\)! /\1 \2 /p' \
+ "$1" >"$1.sq"
+ test -s "$1.sq" || return
+
+ used=
+ while read pick sha1 message
+ do
+ case " $used" in
+ *" $sha1 "*) continue ;;
+ esac
+ echo "$pick $sha1 $message"
+ while read squash action msg
+ do
+ case "$message" in
+ "$msg"*)
+ echo "$action $squash $action! $msg"
+ used="$used$squash "
+ ;;
+ esac
+ done <"$1.sq"
+ done >"$1.rearranged" <"$1"
+ cat "$1.rearranged" >"$1"
+ rm -f "$1.sq" "$1.rearranged"
+}
+
+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
@@ -522,21 +668,20 @@ do
then
: Nothing to commit -- skip this
else
- . "$DOTEST"/author-script ||
+ . "$AUTHOR_SCRIPT" ||
die "Cannot find the author identity"
amend=
- if test -f "$DOTEST"/amend
+ if test -f "$AMEND"
then
amend=$(git rev-parse --verify HEAD)
- test "$amend" = $(cat "$DOTEST"/amend) ||
+ test "$amend" = $(cat "$AMEND") ||
die "\
You have uncommitted changes in your working tree. Please, commit them
first and then run 'git rebase --continue' again."
git reset --soft HEAD^ ||
die "Cannot rewind the HEAD"
fi
- export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE &&
- git commit --no-verify -F "$DOTEST"/message -e || {
+ do_with_author git commit --no-verify -F "$MSG" -e || {
test -n "$amend" && git reset --soft $amend
die "Could not commit staged changes."
}
@@ -600,9 +745,12 @@ first and then run 'git rebase --continue' again."
--root)
REBASE_ROOT=t
;;
+ --autosquash)
+ AUTOSQUASH=t
+ ;;
--onto)
shift
- ONTO=$(git rev-parse --verify "$1") ||
+ ONTO=$(parse_onto "$1") ||
die "Does not point to a valid commit: $1"
;;
--)
@@ -660,13 +808,6 @@ first and then run 'git rebase --continue' again."
test t = "$VERBOSE" && : > "$DOTEST"/verbose
if test t = "$PRESERVE_MERGES"
then
- # $REWRITTEN contains files for each commit that is
- # reachable by at least one merge base of $HEAD and
- # $UPSTREAM. They are not necessarily rewritten, but
- # their children might be.
- # This ensures that commits on merged, but otherwise
- # unrelated side branches are left alone. (Think "X"
- # in the man page's example.)
if test -z "$REBASE_ROOT"
then
mkdir "$REWRITTEN" &&
@@ -759,6 +900,7 @@ first and then run 'git rebase --continue' again."
fi
test -s "$TODO" || echo noop >> "$TODO"
+ test -n "$AUTOSQUASH" && rearrange_squash "$TODO"
cat >> "$TODO" << EOF
# Rebase $SHORTREVISIONS onto $SHORTONTO
@@ -768,6 +910,7 @@ first and then run 'git rebase --continue' again."
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
+# f, fixup = like "squash", but discard this commit's log message
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
diff --git a/git-rebase.sh b/git-rebase.sh
index b121f4537c..eddc02875f 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/git-sh-setup.sh b/git-sh-setup.sh
index dfcb8078f5..d56426dd39 100755
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -120,20 +120,11 @@ is_bare_repository () {
}
cd_to_toplevel () {
- cdup=$(git rev-parse --show-cdup)
- if test ! -z "$cdup"
- then
- # The "-P" option says to follow "physical" directory
- # structure instead of following symbolic links. When cdup is
- # "../", this means following the ".." entry in the current
- # directory instead textually removing a symlink path element
- # from the PWD shell variable. The "-P" behavior is more
- # consistent with the C-style chdir used by most of Git.
- cd -P "$cdup" || {
- echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
- exit 1
- }
- fi
+ cdup=$(git rev-parse --show-toplevel) &&
+ cd "$cdup" || {
+ echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
+ exit 1
+ }
}
require_work_tree () {
diff --git a/git-submodule.sh b/git-submodule.sh
index 77d223292c..664f21721c 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -688,7 +688,11 @@ cmd_summary() {
echo
done |
if test -n "$for_status"; then
- echo "# Modified submodules:"
+ if [ -n "$files" ]; then
+ echo "# Submodules changed but not updated:"
+ else
+ echo "# Submodule changes to be committed:"
+ fi
echo "#"
sed -e 's|^|# |' -e 's|^# $|#|'
else
diff --git a/git-svn.perl b/git-svn.perl
index 650c9e5f02..b321c968af 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -115,6 +115,7 @@ my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared,
'use-svm-props' => sub { $icv{useSvmProps} = 1 },
'use-svnsync-props' => sub { $icv{useSvnsyncProps} = 1 },
'rewrite-root=s' => sub { $icv{rewriteRoot} = $_[1] },
+ 'rewrite-uuid=s' => sub { $icv{rewriteUUID} = $_[1] },
%remote_opts );
my %cmt_opts = ( 'edit|e' => \$_edit,
'rmdir' => \$SVN::Git::Editor::_rmdir,
@@ -155,12 +156,16 @@ my %cmd = (
{ 'message|m=s' => \$_message,
'destination|d=s' => \$_branch_dest,
'dry-run|n' => \$_dry_run,
- 'tag|t' => \$_tag } ],
+ 'tag|t' => \$_tag,
+ 'username=s' => \$Git::SVN::Prompt::_username,
+ 'commit-url=s' => \$_commit_url } ],
tag => [ sub { $_tag = 1; cmd_branch(@_) },
'Create a tag in the SVN repository',
{ 'message|m=s' => \$_message,
'destination|d=s' => \$_branch_dest,
- 'dry-run|n' => \$_dry_run } ],
+ 'dry-run|n' => \$_dry_run,
+ 'username=s' => \$Git::SVN::Prompt::_username,
+ 'commit-url=s' => \$_commit_url } ],
'set-tree' => [ \&cmd_set_tree,
"Set an SVN repository to a git tree-ish",
{ 'stdin' => \$_stdin, %cmt_opts, %fc_opts, } ],
@@ -708,7 +713,21 @@ sub cmd_branch {
}
}
my ($lft, $rgt) = @{ $glob->{path} }{qw/left right/};
- my $dst = join '/', $remote->{url}, $lft, $branch_name, ($rgt || ());
+ my $url;
+ if (defined $_commit_url) {
+ $url = $_commit_url;
+ } else {
+ $url = eval { command_oneline('config', '--get',
+ "svn-remote.$gs->{repo_id}.commiturl") };
+ if (!$url) {
+ $url = $remote->{url};
+ }
+ }
+ my $dst = join '/', $url, $lft, $branch_name, ($rgt || ());
+
+ if ($dst =~ /^https:/ && $src =~ /^http:/) {
+ $src=~s/^http:/https:/;
+ }
my $ctx = SVN::Client->new(
auth => Git::SVN::Ra::_auth_providers(),
@@ -1806,8 +1825,8 @@ sub read_all_remotes {
my $rs = {
t => $t,
remote => $remote,
- path => Git::SVN::GlobSpec->new($local_ref),
- ref => Git::SVN::GlobSpec->new($remote_ref) };
+ path => Git::SVN::GlobSpec->new($local_ref, 1),
+ ref => Git::SVN::GlobSpec->new($remote_ref, 0) };
if (length($rs->{ref}->{right}) != 0) {
die "The '*' glob character must be the last ",
"character of '$remote_ref'\n";
@@ -2189,6 +2208,10 @@ sub svnsync {
die "Can't have both 'useSvnsyncProps' and 'rewriteRoot' ",
"options set!\n";
}
+ if ($self->rewrite_uuid) {
+ die "Can't have both 'useSvnsyncProps' and 'rewriteUUID' ",
+ "options set!\n";
+ }
my $svnsync;
# see if we have it in our config, first:
@@ -2470,6 +2493,20 @@ sub rewrite_root {
$self->{-rewrite_root} = $rwr;
}
+sub rewrite_uuid {
+ my ($self) = @_;
+ return $self->{-rewrite_uuid} if exists $self->{-rewrite_uuid};
+ my $k = "svn-remote.$self->{repo_id}.rewriteUUID";
+ my $rwid = eval { command_oneline(qw/config --get/, $k) };
+ if ($rwid) {
+ $rwid =~ s#/+$##;
+ if ($rwid !~ m#^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$#) {
+ die "$rwid is not a valid UUID (key: $k)\n";
+ }
+ }
+ $self->{-rewrite_uuid} = $rwid;
+}
+
sub metadata_url {
my ($self) = @_;
($self->rewrite_root || $self->{url}) .
@@ -3052,12 +3089,36 @@ sub check_cherry_pick {
for my $range ( @ranges ) {
delete @commits{_rev_list($range)};
}
+ for my $commit (keys %commits) {
+ if (has_no_changes($commit)) {
+ delete $commits{$commit};
+ }
+ }
return (keys %commits);
}
+sub has_no_changes {
+ my $commit = shift;
+
+ my @revs = split / /, command_oneline(
+ qw(rev-list --parents -1 -m), $commit);
+
+ # Commits with no parents, e.g. the start of a partial branch,
+ # have changes by definition.
+ return 1 if (@revs < 2);
+
+ # Commits with multiple parents, e.g a merge, have no changes
+ # by definition.
+ return 0 if (@revs > 2);
+
+ return (command_oneline("rev-parse", "$commit^{tree}") eq
+ command_oneline("rev-parse", "$commit~1^{tree}"));
+}
+
BEGIN {
memoize 'lookup_svn_merge';
memoize 'check_cherry_pick';
+ memoize 'has_no_changes';
}
sub parents_exclude {
@@ -3134,10 +3195,21 @@ sub find_extra_svn_parents {
my $ranges = $ranges{$merge_tip};
# check out 'new' tips
- my $merge_base = command_oneline(
- "merge-base",
- @$parents, $merge_tip,
- );
+ my $merge_base;
+ eval {
+ $merge_base = command_oneline(
+ "merge-base",
+ @$parents, $merge_tip,
+ );
+ };
+ if ($@) {
+ die "An error occurred during merge-base"
+ unless $@->isa("Git::Error::Command");
+
+ warn "W: Cannot find common ancestor between ".
+ "@$parents and $merge_tip. Ignoring merge info.\n";
+ next;
+ }
# double check that there are no missing non-merge commits
my (@incomplete) = check_cherry_pick(
@@ -3253,6 +3325,10 @@ sub make_log_entry {
die "Can't have both 'useSvmProps' and 'rewriteRoot' ",
"options set!\n";
}
+ if ($self->rewrite_uuid) {
+ die "Can't have both 'useSvmProps' and 'rewriteUUID' ",
+ "options set!\n";
+ }
my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}i;
# we don't want "SVM: initializing mirror for junk" ...
return undef if $r == 0;
@@ -3283,10 +3359,10 @@ sub make_log_entry {
} else {
my $url = $self->metadata_url;
remove_username($url);
- $log_entry{metadata} = "$url\@$rev " .
- $self->ra->get_uuid;
- $email ||= "$author\@" . $self->ra->get_uuid;
- $commit_email ||= "$author\@" . $self->ra->get_uuid;
+ my $uuid = $self->rewrite_uuid || $self->ra->get_uuid;
+ $log_entry{metadata} = "$url\@$rev " . $uuid;
+ $email ||= "$author\@" . $uuid;
+ $commit_email ||= "$author\@" . $uuid;
}
$log_entry{name} = $name;
$log_entry{email} = $email;
@@ -3368,7 +3444,7 @@ sub rebuild {
'--');
my $metadata_url = $self->metadata_url;
remove_username($metadata_url);
- my $svn_uuid = $self->ra_uuid;
+ my $svn_uuid = $self->rewrite_uuid || $self->ra_uuid;
my $c;
while (<$log>) {
if ( m{^commit ($::sha1)$} ) {
@@ -5157,6 +5233,7 @@ sub match_globs {
next if (length $g->{path}->{right} &&
($self->check_path($p, $r) !=
$SVN::Node::dir));
+ next unless $p =~ /$g->{path}->{regex}/;
$exists->{$p} = Git::SVN->init($self->{url}, $p, undef,
$g->{ref}->full_path($de), 1);
}
@@ -5930,29 +6007,48 @@ use strict;
use warnings;
sub new {
- my ($class, $glob) = @_;
+ my ($class, $glob, $pattern_ok) = @_;
my $re = $glob;
$re =~ s!/+$!!g; # no need for trailing slashes
- $re =~ m!^([^*]*)(\*(?:/\*)*)(.*)$!;
- my $temp = $re;
- my ($left, $right) = ($1, $3);
- $re = $2;
- my $depth = $re =~ tr/*/*/;
- if ($depth != $temp =~ tr/*/*/) {
- die "Only one set of wildcard directories " .
- "(e.g. '*' or '*/*/*') is supported: '$glob'\n";
+ my (@left, @right, @patterns);
+ my $state = "left";
+ my $die_msg = "Only one set of wildcard directories " .
+ "(e.g. '*' or '*/*/*') is supported: '$glob'\n";
+ for my $part (split(m|/|, $glob)) {
+ if ($part =~ /\*/ && $part ne "*") {
+ die "Invalid pattern in '$glob': $part\n";
+ } elsif ($pattern_ok && $part =~ /[{}]/ &&
+ $part !~ /^\{[^{}]+\}/) {
+ die "Invalid pattern in '$glob': $part\n";
+ }
+ if ($part eq "*") {
+ die $die_msg if $state eq "right";
+ $state = "pattern";
+ push(@patterns, "[^/]*");
+ } elsif ($pattern_ok && $part =~ /^\{(.*)\}$/) {
+ die $die_msg if $state eq "right";
+ $state = "pattern";
+ my $p = quotemeta($1);
+ $p =~ s/\\,/|/g;
+ push(@patterns, "(?:$p)");
+ } else {
+ if ($state eq "left") {
+ push(@left, $part);
+ } else {
+ push(@right, $part);
+ $state = "right";
+ }
+ }
}
+ my $depth = @patterns;
if ($depth == 0) {
- die "One '*' is needed for glob: '$glob'\n";
- }
- $re =~ s!\*!\[^/\]*!g;
- $re = quotemeta($left) . "($re)" . quotemeta($right);
- if (length $left && !($left =~ s!/+$!!g)) {
- die "Missing trailing '/' on left side of: '$glob' ($left)\n";
- }
- if (length $right && !($right =~ s!^/+!!g)) {
- die "Missing leading '/' on right side of: '$glob' ($right)\n";
+ die "One '*' is needed in glob: '$glob'\n";
}
+ my $left = join('/', @left);
+ my $right = join('/', @right);
+ $re = join('/', @patterns);
+ $re = join('\/',
+ grep(length, quotemeta($left), "($re)", quotemeta($right)));
my $left_re = qr/^\/\Q$left\E(\/|$)/;
bless { left => $left, right => $right, left_regex => $left_re,
regex => qr/$re/, glob => $glob, depth => $depth }, $class;
diff --git a/git.c b/git.c
index 11544cdb40..b3e23f1044 100644
--- a/git.c
+++ b/git.c
@@ -317,8 +317,10 @@ static void handle_internal_command(int argc, const char **argv)
{ "fsck-objects", cmd_fsck, RUN_SETUP },
{ "gc", cmd_gc, RUN_SETUP },
{ "get-tar-commit-id", cmd_get_tar_commit_id },
- { "grep", cmd_grep, RUN_SETUP | USE_PAGER },
+ { "grep", cmd_grep, USE_PAGER },
+ { "hash-object", cmd_hash_object },
{ "help", cmd_help },
+ { "index-pack", cmd_index_pack },
{ "init", cmd_init_db },
{ "init-db", cmd_init_db },
{ "log", cmd_log, RUN_SETUP | USE_PAGER },
@@ -330,13 +332,20 @@ static void handle_internal_command(int argc, const char **argv)
{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file },
+ { "merge-index", cmd_merge_index, RUN_SETUP },
{ "merge-ours", cmd_merge_ours, RUN_SETUP },
{ "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+ { "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+ { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+ { "merge-tree", cmd_merge_tree, RUN_SETUP },
+ { "mktag", cmd_mktag, RUN_SETUP },
{ "mktree", cmd_mktree, RUN_SETUP },
{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
{ "name-rev", cmd_name_rev, RUN_SETUP },
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
+ { "pack-redundant", cmd_pack_redundant, RUN_SETUP },
+ { "patch-id", cmd_patch_id },
{ "peek-remote", cmd_ls_remote },
{ "pickaxe", cmd_blame, RUN_SETUP },
{ "prune", cmd_prune, RUN_SETUP },
@@ -363,11 +372,13 @@ static void handle_internal_command(int argc, const char **argv)
{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
{ "tag", cmd_tag, RUN_SETUP },
{ "tar-tree", cmd_tar_tree },
+ { "unpack-file", cmd_unpack_file, RUN_SETUP },
{ "unpack-objects", cmd_unpack_objects, RUN_SETUP },
{ "update-index", cmd_update_index, RUN_SETUP },
{ "update-ref", cmd_update_ref, RUN_SETUP },
{ "update-server-info", cmd_update_server_info, RUN_SETUP },
{ "upload-archive", cmd_upload_archive },
+ { "var", cmd_var },
{ "verify-tag", cmd_verify_tag, RUN_SETUP },
{ "version", cmd_version },
{ "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
diff --git a/grep.c b/grep.c
index bdadf2c0cc..8e1f7de771 100644
--- a/grep.c
+++ b/grep.c
@@ -29,13 +29,6 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat,
p->next = NULL;
}
-static int is_fixed(const char *s)
-{
- while (*s && !is_regex_special(*s))
- s++;
- return !*s;
-}
-
static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
{
int err;
@@ -43,7 +36,7 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
p->word_regexp = opt->word_regexp;
p->ignore_case = opt->ignore_case;
- if (opt->fixed || is_fixed(p->pattern))
+ if (opt->fixed)
p->fixed = 1;
if (opt->regflags & REG_ICASE)
p->fixed = 0;
@@ -615,6 +608,65 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
}
}
+static int should_lookahead(struct grep_opt *opt)
+{
+ struct grep_pat *p;
+
+ if (opt->extended)
+ return 0; /* punt for too complex stuff */
+ if (opt->invert)
+ return 0;
+ for (p = opt->pattern_list; p; p = p->next) {
+ if (p->token != GREP_PATTERN)
+ return 0; /* punt for "header only" and stuff */
+ }
+ return 1;
+}
+
+static int look_ahead(struct grep_opt *opt,
+ unsigned long *left_p,
+ unsigned *lno_p,
+ char **bol_p)
+{
+ unsigned lno = *lno_p;
+ char *bol = *bol_p;
+ struct grep_pat *p;
+ char *sp, *last_bol;
+ regoff_t earliest = -1;
+
+ for (p = opt->pattern_list; p; p = p->next) {
+ int hit;
+ regmatch_t m;
+
+ if (p->fixed)
+ hit = !fixmatch(p->pattern, bol, p->ignore_case, &m);
+ else
+ hit = !regexec(&p->regexp, bol, 1, &m, 0);
+ if (!hit || m.rm_so < 0 || m.rm_eo < 0)
+ continue;
+ if (earliest < 0 || m.rm_so < earliest)
+ earliest = m.rm_so;
+ }
+
+ if (earliest < 0) {
+ *bol_p = bol + *left_p;
+ *left_p = 0;
+ return 1;
+ }
+ for (sp = bol + earliest; bol < sp && sp[-1] != '\n'; sp--)
+ ; /* find the beginning of the line */
+ last_bol = sp;
+
+ for (sp = bol; sp < last_bol; sp++) {
+ if (*sp == '\n')
+ lno++;
+ }
+ *left_p -= last_bol - bol;
+ *bol_p = last_bol;
+ *lno_p = lno;
+ return 0;
+}
+
static int grep_buffer_1(struct grep_opt *opt, const char *name,
char *buf, unsigned long size, int collect_hits)
{
@@ -624,6 +676,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
unsigned last_hit = 0;
int binary_match_only = 0;
unsigned count = 0;
+ int try_lookahead = 0;
enum grep_context ctx = GREP_CONTEXT_HEAD;
xdemitconf_t xecfg;
@@ -652,11 +705,26 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
opt->priv = &xecfg;
}
}
+ try_lookahead = should_lookahead(opt);
while (left) {
char *eol, ch;
int hit;
+ /*
+ * look_ahead() skips quicly to the line that possibly
+ * has the next hit; don't call it if we need to do
+ * something more than just skipping the current line
+ * in response to an unmatch for the current line. E.g.
+ * inside a post-context window, we will show the current
+ * line as a context around the previous hit when it
+ * doesn't hit.
+ */
+ if (try_lookahead
+ && !(last_hit
+ && lno <= last_hit + opt->post_context)
+ && look_ahead(opt, &left, &lno, &bol))
+ break;
eol = end_of_line(bol, &left);
ch = *eol;
*eol = 0;
diff --git a/grep.h b/grep.h
index 75370f60d7..0c61b00cbf 100644
--- a/grep.h
+++ b/grep.h
@@ -85,7 +85,6 @@ struct grep_opt {
int max_depth;
int funcname;
char color_match[COLOR_MAXLEN];
- const char *color_external;
int regflags;
unsigned pre_context;
unsigned post_context;
diff --git a/hex.c b/hex.c
new file mode 100644
index 0000000000..bb402fbaa2
--- /dev/null
+++ b/hex.c
@@ -0,0 +1,67 @@
+#include "cache.h"
+
+const signed char hexval_table[256] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */
+ 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */
+ 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */
+ -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */
+ -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */
+};
+
+int get_sha1_hex(const char *hex, unsigned char *sha1)
+{
+ int i;
+ for (i = 0; i < 20; i++) {
+ unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
+ if (val & ~0xff)
+ return -1;
+ *sha1++ = val;
+ hex += 2;
+ }
+ return 0;
+}
+
+char *sha1_to_hex(const unsigned char *sha1)
+{
+ static int bufno;
+ static char hexbuffer[4][50];
+ static const char hex[] = "0123456789abcdef";
+ char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
+ int i;
+
+ for (i = 0; i < 20; i++) {
+ unsigned int val = *sha1++;
+ *buf++ = hex[val >> 4];
+ *buf++ = hex[val & 0xf];
+ }
+ *buf = '\0';
+
+ return buffer;
+}
diff --git a/http-backend.c b/http-backend.c
index f729488fc5..345c12b790 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/http.c b/http.c
index 455732fb24..deab59551d 100644
--- a/http.c
+++ b/http.c
@@ -7,6 +7,12 @@ int active_requests;
int http_is_verbose;
size_t http_post_buffer = 16 * LARGE_PACKET_MAX;
+#if LIBCURL_VERSION_NUM >= 0x070a06
+#define LIBCURL_CAN_HANDLE_AUTH_ANY
+#endif
+
+static int min_curl_sessions = 1;
+static int curl_session_count;
#ifdef USE_CURL_MULTI
static int max_requests = -1;
static CURLM *curlm;
@@ -152,6 +158,14 @@ static int http_options(const char *var, const char *value, void *cb)
ssl_cert_password_required = 1;
return 0;
}
+ if (!strcmp("http.minsessions", var)) {
+ min_curl_sessions = git_config_int(var, value);
+#ifndef USE_CURL_MULTI
+ if (min_curl_sessions > 1)
+ min_curl_sessions = 1;
+#endif
+ return 0;
+ }
#ifdef USE_CURL_MULTI
if (!strcmp("http.maxrequests", var)) {
max_requests = git_config_int(var, value);
@@ -230,6 +244,9 @@ static CURL *get_curl_handle(void)
#if LIBCURL_VERSION_NUM >= 0x070907
curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
#endif
+#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
+ curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
+#endif
init_curl_http_auth(result);
@@ -372,6 +389,7 @@ void http_init(struct remote *remote)
if (curl_ssl_verify == -1)
curl_ssl_verify = 1;
+ curl_session_count = 0;
#ifdef USE_CURL_MULTI
if (max_requests < 1)
max_requests = DEFAULT_MAX_REQUESTS;
@@ -480,6 +498,7 @@ struct active_request_slot *get_active_slot(void)
#else
slot->curl = curl_easy_duphandle(curl_default);
#endif
+ curl_session_count++;
}
active_requests++;
@@ -558,9 +577,11 @@ void fill_active_slots(void)
}
while (slot != NULL) {
- if (!slot->in_use && slot->curl != NULL) {
+ if (!slot->in_use && slot->curl != NULL
+ && curl_session_count > min_curl_sessions) {
curl_easy_cleanup(slot->curl);
slot->curl = NULL;
+ curl_session_count--;
}
slot = slot->next;
}
@@ -630,15 +651,16 @@ static void closedown_active_slot(struct active_request_slot *slot)
slot->in_use = 0;
}
-void release_active_slot(struct active_request_slot *slot)
+static void release_active_slot(struct active_request_slot *slot)
{
closedown_active_slot(slot);
- if (slot->curl) {
+ if (slot->curl && curl_session_count > min_curl_sessions) {
#ifdef USE_CURL_MULTI
curl_multi_remove_handle(curlm, slot->curl);
#endif
curl_easy_cleanup(slot->curl);
slot->curl = NULL;
+ curl_session_count--;
}
#ifdef USE_CURL_MULTI
fill_active_slots();
@@ -812,7 +834,13 @@ int http_get_strbuf(const char *url, struct strbuf *result, int options)
return http_request(url, result, HTTP_REQUEST_STRBUF, options);
}
-int http_get_file(const char *url, const char *filename, int options)
+/*
+ * Downloads an url and stores the result in the given file.
+ *
+ * If a previous interrupted download is detected (i.e. a previous temporary
+ * file is still around) the download is resumed.
+ */
+static int http_get_file(const char *url, const char *filename, int options)
{
int ret;
struct strbuf tmpfile = STRBUF_INIT;
diff --git a/http.h b/http.h
index f828e1d806..5c9441c10c 100644
--- a/http.h
+++ b/http.h
@@ -81,7 +81,6 @@ extern int start_active_slot(struct active_request_slot *slot);
extern void run_active_slot(struct active_request_slot *slot);
extern void finish_active_slot(struct active_request_slot *slot);
extern void finish_all_active_slots(void);
-extern void release_active_slot(struct active_request_slot *slot);
#ifdef USE_CURL_MULTI
extern void fill_active_slots(void);
@@ -136,14 +135,6 @@ extern char *get_remote_object_url(const char *url, const char *hex,
int http_get_strbuf(const char *url, struct strbuf *result, int options);
/*
- * Downloads an url and stores the result in the given file.
- *
- * If a previous interrupted download is detected (i.e. a previous temporary
- * file is still around) the download is resumed.
- */
-int http_get_file(const char *url, const char *filename, int options);
-
-/*
* Prints an error message using error() containing url and curl_errorstr,
* and returns ret.
*/
diff --git a/ident.c b/ident.c
index 26409b2a1b..9e2438826d 100644
--- a/ident.c
+++ b/ident.c
@@ -85,10 +85,11 @@ static void setup_ident(void)
if (!git_default_email[0]) {
const char *email = getenv("EMAIL");
- if (email && email[0])
+ if (email && email[0]) {
strlcpy(git_default_email, email,
sizeof(git_default_email));
- else {
+ user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
+ } else {
if (!pw)
pw = getpwuid(getuid());
if (!pw)
@@ -168,8 +169,6 @@ static int copy(char *buf, size_t size, int offset, const char *src)
return offset;
}
-static const char au_env[] = "GIT_AUTHOR_NAME";
-static const char co_env[] = "GIT_COMMITTER_NAME";
static const char *env_hint =
"\n"
"*** Please tell me who you are.\n"
@@ -204,7 +203,7 @@ const char *fmt_ident(const char *name, const char *email,
if ((warn_on_no_name || error_on_no_name) &&
name == git_default_name && env_hint) {
- fprintf(stderr, env_hint, au_env, co_env);
+ fputs(env_hint, stderr);
env_hint = NULL; /* warn only once */
}
if (error_on_no_name)
@@ -251,11 +250,21 @@ const char *git_author_info(int flag)
const char *git_committer_info(int flag)
{
- if (getenv("GIT_COMMITTER_NAME") &&
- getenv("GIT_COMMITTER_EMAIL"))
- user_ident_explicitly_given = 1;
+ if (getenv("GIT_COMMITTER_NAME"))
+ user_ident_explicitly_given |= IDENT_NAME_GIVEN;
+ if (getenv("GIT_COMMITTER_EMAIL"))
+ user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
return fmt_ident(getenv("GIT_COMMITTER_NAME"),
getenv("GIT_COMMITTER_EMAIL"),
getenv("GIT_COMMITTER_DATE"),
flag);
}
+
+int user_ident_sufficiently_given(void)
+{
+#ifndef WINDOWS
+ return (user_ident_explicitly_given & IDENT_MAIL_GIVEN);
+#else
+ return (user_ident_explicitly_given == IDENT_ALL_GIVEN);
+#endif
+}
diff --git a/imap-send.c b/imap-send.c
index de8114bac0..51f371ba9f 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 2d6b6d6cb1..4c7f11ba84 100644
--- a/ll-merge.c
+++ b/ll-merge.c
@@ -18,7 +18,8 @@ typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
mmfile_t *orig,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int virtual_ancestor);
+ int flag,
+ int marker_size);
struct ll_merge_driver {
const char *name;
@@ -38,14 +39,14 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
mmfile_t *orig,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int virtual_ancestor)
+ int flag, int marker_size)
{
/*
* The tentative merge result is "ours" for the final round,
* or common ancestor for an internal merge. Still return
* "conflicted merge" status.
*/
- mmfile_t *stolen = virtual_ancestor ? orig : src1;
+ mmfile_t *stolen = (flag & 01) ? orig : src1;
result->ptr = stolen->ptr;
result->size = stolen->size;
@@ -59,10 +60,11 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
mmfile_t *orig,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int virtual_ancestor)
+ int flag, int marker_size)
{
- xpparam_t xpp;
+ xmparam_t xmp;
int style = 0;
+ int favor = (flag >> 1) & 03;
if (buffer_is_binary(orig->ptr, orig->size) ||
buffer_is_binary(src1->ptr, src1->size) ||
@@ -73,16 +75,18 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
path,
orig, src1, name1,
src2, name2,
- virtual_ancestor);
+ flag, marker_size);
}
- memset(&xpp, 0, sizeof(xpp));
+ memset(&xmp, 0, sizeof(xmp));
if (git_xmerge_style >= 0)
style = git_xmerge_style;
+ if (marker_size > 0)
+ xmp.marker_size = marker_size;
return xdl_merge(orig,
src1, name1,
src2, name2,
- &xpp, XDL_MERGE_ZEALOUS | style,
+ &xmp, XDL_MERGE_FLAGS(XDL_MERGE_ZEALOUS, style, favor),
result);
}
@@ -92,11 +96,10 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
mmfile_t *orig,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int virtual_ancestor)
+ int flag, int marker_size)
{
char *src, *dst;
long size;
- const int marker_size = 7;
int status, saved_style;
/* We have to force the RCS "merge" style */
@@ -104,7 +107,7 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
git_xmerge_style = 0;
status = ll_xdl_merge(drv_unused, result, path_unused,
orig, src1, NULL, src2, NULL,
- virtual_ancestor);
+ flag, marker_size);
git_xmerge_style = saved_style;
if (status <= 0)
return status;
@@ -165,17 +168,18 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
mmfile_t *orig,
mmfile_t *src1, const char *name1,
mmfile_t *src2, const char *name2,
- int virtual_ancestor)
+ int flag, int marker_size)
{
- char temp[3][50];
+ char temp[4][50];
struct strbuf cmd = STRBUF_INIT;
struct strbuf_expand_dict_entry dict[] = {
{ "O", temp[0] },
{ "A", temp[1] },
{ "B", temp[2] },
+ { "L", temp[3] },
{ NULL }
};
- const char *args[] = { "sh", "-c", NULL, NULL };
+ const char *args[] = { NULL, NULL };
int status, fd, i;
struct stat st;
@@ -187,11 +191,12 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
create_temp(orig, temp[0]);
create_temp(src1, temp[1]);
create_temp(src2, temp[2]);
+ sprintf(temp[3], "%d", marker_size);
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;
@@ -279,6 +284,7 @@ static int read_merge_config(const char *var, const char *value, void *cb)
* %O - temporary file name for the merge base.
* %A - temporary file name for our version.
* %B - temporary file name for the other branches' version.
+ * %L - conflict marker length
*
* The external merge driver should write the results in the
* file named by %A, and signal that it has done with zero exit
@@ -339,16 +345,13 @@ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr
return &ll_merge_drv[LL_TEXT_MERGE];
}
-static const char *git_path_check_merge(const char *path)
+static int git_path_check_merge(const char *path, struct git_attr_check check[2])
{
- static struct git_attr_check attr_merge_check;
-
- if (!attr_merge_check.attr)
- attr_merge_check.attr = git_attr("merge", 5);
-
- if (git_checkattr(path, 1, &attr_merge_check))
- return NULL;
- return attr_merge_check.value;
+ if (!check[0].attr) {
+ check[0].attr = git_attr("merge");
+ check[1].attr = git_attr("conflict-marker-size");
+ }
+ return git_checkattr(path, 2, check);
}
int ll_merge(mmbuffer_t *result_buf,
@@ -356,18 +359,41 @@ int ll_merge(mmbuffer_t *result_buf,
mmfile_t *ancestor,
mmfile_t *ours, const char *our_label,
mmfile_t *theirs, const char *their_label,
- int virtual_ancestor)
+ int flag)
{
- const char *ll_driver_name;
+ static struct git_attr_check check[2];
+ const char *ll_driver_name = NULL;
+ int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
const struct ll_merge_driver *driver;
-
- ll_driver_name = git_path_check_merge(path);
+ int virtual_ancestor = flag & 01;
+
+ if (!git_path_check_merge(path, check)) {
+ ll_driver_name = check[0].value;
+ if (check[1].value) {
+ marker_size = atoi(check[1].value);
+ if (marker_size <= 0)
+ marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+ }
+ }
driver = find_ll_merge_driver(ll_driver_name);
-
if (virtual_ancestor && driver->recursive)
driver = find_ll_merge_driver(driver->recursive);
- return driver->fn(driver, result_buf, path,
- ancestor,
- ours, our_label,
- theirs, their_label, virtual_ancestor);
+ return driver->fn(driver, result_buf, path, ancestor,
+ ours, our_label, theirs, their_label,
+ flag, marker_size);
+}
+
+int ll_merge_marker_size(const char *path)
+{
+ static struct git_attr_check check;
+ int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+
+ if (!check.attr)
+ check.attr = git_attr("conflict-marker-size");
+ if (!git_checkattr(path, 1, &check) && check.value) {
+ marker_size = atoi(check.value);
+ if (marker_size <= 0)
+ marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+ }
+ return marker_size;
}
diff --git a/ll-merge.h b/ll-merge.h
index 5388422d09..57889227b1 100644
--- a/ll-merge.h
+++ b/ll-merge.h
@@ -10,6 +10,8 @@ int ll_merge(mmbuffer_t *result_buf,
mmfile_t *ancestor,
mmfile_t *ours, const char *our_label,
mmfile_t *theirs, const char *their_label,
- int virtual_ancestor);
+ int flag);
+
+int ll_merge_marker_size(const char *path);
#endif
diff --git a/lockfile.c b/lockfile.c
index 6851fa55a5..b0d74cddde 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/log-tree.c b/log-tree.c
index 0fdf159f80..27afcf6972 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -284,6 +284,7 @@ void show_log(struct rev_info *opt)
struct pretty_print_context ctx = {0};
opt->loginfo = NULL;
+ ctx.show_notes = opt->show_notes;
if (!opt->verbose_header) {
graph_show_commit(opt->graph);
diff --git a/mailmap.c b/mailmap.c
index f167c005bf..b68c1fec9c 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -243,8 +243,3 @@ int map_user(struct string_list *map,
debug_mm("map_user: --\n");
return 0;
}
-
-int map_email(struct string_list *map, const char *email, char *name, int maxlen)
-{
- return map_user(map, (char *)email, 0, name, maxlen);
-}
diff --git a/mailmap.h b/mailmap.h
index 4b2ca3a7de..d5c3664322 100644
--- a/mailmap.h
+++ b/mailmap.h
@@ -4,7 +4,6 @@
int read_mailmap(struct string_list *map, char **repo_abbrev);
void clear_mailmap(struct string_list *map);
-int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen);
int map_user(struct string_list *mailmap,
char *email, int maxlen_email, char *name, int maxlen_name);
diff --git a/match-trees.c b/match-trees.c
index 0fd6df7d6e..26f7ed143e 100644
--- a/match-trees.c
+++ b/match-trees.c
@@ -185,7 +185,7 @@ static void match_trees(const unsigned char *hash1,
* tree object by replacing it with another tree "hash2".
*/
static int splice_tree(const unsigned char *hash1,
- char *prefix,
+ const char *prefix,
const unsigned char *hash2,
unsigned char *result)
{
@@ -264,6 +264,13 @@ void shift_tree(const unsigned char *hash1,
char *del_prefix;
int add_score, del_score;
+ /*
+ * NEEDSWORK: this limits the recursion depth to hardcoded
+ * value '2' to avoid excessive overhead.
+ */
+ if (!depth_limit)
+ depth_limit = 2;
+
add_score = del_score = score_trees(hash1, hash2);
add_prefix = xcalloc(1, 1);
del_prefix = xcalloc(1, 1);
@@ -301,3 +308,63 @@ void shift_tree(const unsigned char *hash1,
splice_tree(hash1, add_prefix, hash2, shifted);
}
+
+/*
+ * The user says the trees will be shifted by this much.
+ * Unfortunately we cannot fundamentally tell which one to
+ * be prefixed, as recursive merge can work in either direction.
+ */
+void shift_tree_by(const unsigned char *hash1,
+ const unsigned char *hash2,
+ unsigned char *shifted,
+ const char *shift_prefix)
+{
+ unsigned char sub1[20], sub2[20];
+ unsigned mode1, mode2;
+ unsigned candidate = 0;
+
+ /* Can hash2 be a tree at shift_prefix in tree hash1? */
+ if (!get_tree_entry(hash1, shift_prefix, sub1, &mode1) &&
+ S_ISDIR(mode1))
+ candidate |= 1;
+
+ /* Can hash1 be a tree at shift_prefix in tree hash2? */
+ if (!get_tree_entry(hash2, shift_prefix, sub2, &mode2) &&
+ S_ISDIR(mode2))
+ candidate |= 2;
+
+ if (candidate == 3) {
+ /* Both are plausible -- we need to evaluate the score */
+ int best_score = score_trees(hash1, hash2);
+ int score;
+
+ candidate = 0;
+ score = score_trees(sub1, hash2);
+ if (score > best_score) {
+ candidate = 1;
+ best_score = score;
+ }
+ score = score_trees(sub2, hash1);
+ if (score > best_score)
+ candidate = 2;
+ }
+
+ if (!candidate) {
+ /* Neither is plausible -- do not shift */
+ hashcpy(shifted, hash2);
+ return;
+ }
+
+ if (candidate == 1)
+ /*
+ * shift tree2 down by adding shift_prefix above it
+ * to match tree1.
+ */
+ splice_tree(hash1, shift_prefix, hash2, shifted);
+ else
+ /*
+ * shift tree2 up by removing shift_prefix from it
+ * to match tree1.
+ */
+ hashcpy(shifted, sub2);
+}
diff --git a/merge-file.c b/merge-file.c
index 3120a95f78..fd34d76e15 100644
--- a/merge-file.c
+++ b/merge-file.c
@@ -1,6 +1,7 @@
#include "cache.h"
#include "run-command.h"
#include "xdiff-interface.h"
+#include "ll-merge.h"
#include "blob.h"
static int fill_mmfile_blob(mmfile_t *f, struct blob *obj)
@@ -24,16 +25,13 @@ static void free_mmfile(mmfile_t *f)
free(f->ptr);
}
-static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size)
+static void *three_way_filemerge(const char *path, mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size)
{
- mmbuffer_t res;
- xpparam_t xpp;
int merge_status;
+ mmbuffer_t res;
- memset(&xpp, 0, sizeof(xpp));
- merge_status = xdl_merge(base, our, ".our", their, ".their",
- &xpp, XDL_MERGE_ZEALOUS, &res);
-
+ merge_status = ll_merge(&res, path, base,
+ our, ".our", their, ".their", 0);
if (merge_status < 0)
return NULL;
@@ -75,7 +73,7 @@ static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2)
return xdi_diff(f1, f2, &xpp, &xecfg, &ecb);
}
-void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsigned long *size)
+void *merge_file(const char *path, struct blob *base, struct blob *our, struct blob *their, unsigned long *size)
{
void *res = NULL;
mmfile_t f1, f2, common;
@@ -108,7 +106,7 @@ void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsign
if (generate_common_file(&common, &f1, &f2) < 0)
goto out_free_f2_f1;
}
- res = three_way_filemerge(&common, &f1, &f2, size);
+ res = three_way_filemerge(path, &common, &f1, &f2, size);
free_mmfile(&common);
out_free_f2_f1:
free_mmfile(&f2);
diff --git a/merge-recursive.c b/merge-recursive.c
index dd4fbd0e6b..cb53b01c19 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -21,15 +21,17 @@
#include "merge-recursive.h"
#include "dir.h"
-static struct tree *shift_tree_object(struct tree *one, struct tree *two)
+static struct tree *shift_tree_object(struct tree *one, struct tree *two,
+ const char *subtree_shift)
{
unsigned char shifted[20];
- /*
- * NEEDSWORK: this limits the recursion depth to hardcoded
- * value '2' to avoid excessive overhead.
- */
- shift_tree(one->object.sha1, two->object.sha1, shifted, 2);
+ if (!*subtree_shift) {
+ shift_tree(one->object.sha1, two->object.sha1, shifted, 0);
+ } else {
+ shift_tree_by(one->object.sha1, two->object.sha1, shifted,
+ subtree_shift);
+ }
if (!hashcmp(two->object.sha1, shifted))
return two;
return lookup_tree(shifted);
@@ -200,14 +202,14 @@ struct tree *write_tree_from_memory(struct merge_options *o)
if (unmerged_cache()) {
int i;
- output(o, 0, "There are unmerged index entries:");
+ fprintf(stderr, "BUG: There are unmerged index entries:\n");
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if (ce_stage(ce))
- output(o, 0, "%d %.*s", ce_stage(ce),
- (int)ce_namelen(ce), ce->name);
+ fprintf(stderr, "BUG: %d %.*s", ce_stage(ce),
+ (int)ce_namelen(ce), ce->name);
}
- return NULL;
+ die("Bug in merge-recursive.c");
}
if (!active_cache_tree)
@@ -625,6 +627,23 @@ static int merge_3way(struct merge_options *o,
mmfile_t orig, src1, src2;
char *name1, *name2;
int merge_status;
+ int favor;
+
+ if (o->call_depth)
+ favor = 0;
+ else {
+ switch (o->recursive_variant) {
+ case MERGE_RECURSIVE_OURS:
+ favor = XDL_MERGE_FAVOR_OURS;
+ break;
+ case MERGE_RECURSIVE_THEIRS:
+ favor = XDL_MERGE_FAVOR_THEIRS;
+ break;
+ default:
+ favor = 0;
+ break;
+ }
+ }
if (strcmp(a->path, b->path)) {
name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
@@ -640,7 +659,7 @@ static int merge_3way(struct merge_options *o,
merge_status = ll_merge(result_buf, a->path, &orig,
&src1, name1, &src2, name2,
- o->call_depth);
+ (!!o->call_depth) | (favor << 1));
free(name1);
free(name2);
@@ -1201,9 +1220,9 @@ int merge_trees(struct merge_options *o,
{
int code, clean;
- if (o->subtree_merge) {
- merge = shift_tree_object(head, merge);
- common = shift_tree_object(head, common);
+ if (o->subtree_shift) {
+ merge = shift_tree_object(head, merge, o->subtree_shift);
+ common = shift_tree_object(head, common, o->subtree_shift);
}
if (sha_eq(common->object.sha1, merge->object.sha1)) {
diff --git a/merge-recursive.h b/merge-recursive.h
index d8bc7299ee..be8410ad18 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -6,7 +6,12 @@
struct merge_options {
const char *branch1;
const char *branch2;
- unsigned subtree_merge : 1;
+ enum {
+ MERGE_RECURSIVE_NORMAL = 0,
+ MERGE_RECURSIVE_OURS,
+ MERGE_RECURSIVE_THEIRS,
+ } recursive_variant;
+ const char *subtree_shift;
unsigned buffer_output : 1;
int verbosity;
int diff_rename_limit;
diff --git a/object.c b/object.c
index fe8eaaf19f..3ca92c4c4d 100644
--- a/object.c
+++ b/object.c
@@ -217,27 +217,6 @@ struct object_list *object_list_insert(struct object *item,
return new_list;
}
-void object_list_append(struct object *item,
- struct object_list **list_p)
-{
- while (*list_p) {
- list_p = &((*list_p)->next);
- }
- *list_p = xmalloc(sizeof(struct object_list));
- (*list_p)->next = NULL;
- (*list_p)->item = item;
-}
-
-unsigned object_list_length(struct object_list *list)
-{
- unsigned ret = 0;
- while (list) {
- list = list->next;
- ret++;
- }
- return ret;
-}
-
int object_list_contains(struct object_list *list, struct object *obj)
{
while (list) {
diff --git a/object.h b/object.h
index 89dd0c47a6..82877c831c 100644
--- a/object.h
+++ b/object.h
@@ -72,11 +72,6 @@ struct object *lookup_unknown_object(const unsigned char *sha1);
struct object_list *object_list_insert(struct object *item,
struct object_list **list_p);
-void object_list_append(struct object *item,
- struct object_list **list_p);
-
-unsigned object_list_length(struct object_list *list);
-
int object_list_contains(struct object_list *list, struct object *obj);
/* Object array handling .. */
diff --git a/pack-write.c b/pack-write.c
index 741efcd93b..9f47cf9961 100644
--- a/pack-write.c
+++ b/pack-write.c
@@ -17,8 +17,8 @@ static int sha1_compare(const void *_a, const void *_b)
* the SHA1 hash of sorted object names. The objects array passed in
* will be sorted by SHA1 on exit.
*/
-char *write_idx_file(char *index_name, struct pack_idx_entry **objects,
- int nr_objects, unsigned char *sha1)
+const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects,
+ int nr_objects, unsigned char *sha1)
{
struct sha1file *f;
struct pack_idx_entry **sorted_by_sha, **list, **last;
diff --git a/pack.h b/pack.h
index a883334b26..b759a23d4d 100644
--- a/pack.h
+++ b/pack.h
@@ -55,7 +55,7 @@ struct pack_idx_entry {
off_t offset;
};
-extern char *write_idx_file(char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
+extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr);
extern int verify_pack(struct packed_git *);
extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t);
diff --git a/pager.c b/pager.c
index 92c03f654a..2c7e8ecb3c 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 f5594114ed..d218122af5 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -3,6 +3,9 @@
#include "cache.h"
#include "commit.h"
+static int parse_options_usage(const char * const *usagestr,
+ const struct option *opts);
+
#define OPT_SHORT 1
#define OPT_UNSET 2
@@ -560,8 +563,8 @@ void usage_msg_opt(const char *msg,
usage_with_options(usagestr, options);
}
-int parse_options_usage(const char * const *usagestr,
- const struct option *opts)
+static int parse_options_usage(const char * const *usagestr,
+ const struct option *opts)
{
return usage_with_options_internal(usagestr, opts, 0);
}
@@ -633,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 f295a2cf85..0c996916b6 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 }
@@ -171,9 +173,6 @@ struct parse_opt_ctx_t {
const char *prefix;
};
-extern int parse_options_usage(const char * const *usagestr,
- const struct option *opts);
-
extern void parse_options_start(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, const char *prefix,
int flags);
@@ -190,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/path.c b/path.c
index 2ec950b27f..79aa104712 100644
--- a/path.c
+++ b/path.c
@@ -394,17 +394,38 @@ int set_shared_perm(const char *path, int mode)
const char *make_relative_path(const char *abs, const char *base)
{
static char buf[PATH_MAX + 1];
- int baselen;
- if (!base)
- return abs;
- baselen = strlen(base);
- if (prefixcmp(abs, base))
+ int i = 0, j = 0;
+
+ if (!base || !base[0])
return abs;
- if (abs[baselen] == '/')
- baselen++;
- else if (base[baselen - 1] != '/')
+ while (base[i]) {
+ if (is_dir_sep(base[i])) {
+ if (!is_dir_sep(abs[j]))
+ return abs;
+ while (is_dir_sep(base[i]))
+ i++;
+ while (is_dir_sep(abs[j]))
+ j++;
+ continue;
+ } else if (abs[j] != base[i]) {
+ return abs;
+ }
+ i++;
+ j++;
+ }
+ if (
+ /* "/foo" is a prefix of "/foo" */
+ abs[j] &&
+ /* "/foo" is not a prefix of "/foobar" */
+ !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j])
+ )
return abs;
- strcpy(buf, abs + baselen);
+ while (is_dir_sep(abs[j]))
+ j++;
+ if (!abs[j])
+ strcpy(buf, ".");
+ else
+ strcpy(buf, abs + j);
return buf;
}
diff --git a/pretty.c b/pretty.c
index 8f5bd1ab7f..d493cade26 100644
--- a/pretty.c
+++ b/pretty.c
@@ -83,7 +83,7 @@ static int get_one_line(const char *msg)
}
/* High bit set, or ISO-2022-INT */
-int non_ascii(int ch)
+static int non_ascii(int ch)
{
return !isascii(ch) || ch == '\033';
}
@@ -1094,7 +1094,7 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
strbuf_addch(sb, '\n');
- if (fmt != CMIT_FMT_ONELINE)
+ if (context->show_notes)
get_commit_notes(commit, sb, encoding,
NOTES_SHOW_HEADER | NOTES_INDENT);
diff --git a/quote.c b/quote.c
index 848d174cc5..acb6bf929f 100644
--- a/quote.c
+++ b/quote.c
@@ -72,7 +72,7 @@ void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen)
}
}
-char *sq_dequote_step(char *arg, char **next)
+static char *sq_dequote_step(char *arg, char **next)
{
char *dst = arg;
char *src = arg;
diff --git a/quote.h b/quote.h
index 66730f2bff..f83eb233c4 100644
--- a/quote.h
+++ b/quote.h
@@ -45,7 +45,6 @@ extern char *sq_dequote(char *);
* next argument that should be passed as first parameter. When there
* is no more argument to be dequoted, "next" is updated to point to NULL.
*/
-extern char *sq_dequote_step(char *arg, char **next);
extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc);
extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp);
diff --git a/read-cache.c b/read-cache.c
index 9033dd3ab9..79938bf09a 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -10,10 +10,10 @@
#include "dir.h"
#include "tree.h"
#include "commit.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "revision.h"
#include "blob.h"
+#include "resolve-undo.h"
+
+static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
/* Index extensions.
*
@@ -26,6 +26,7 @@
#define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) )
#define CACHE_EXT_TREE 0x54524545 /* "TREE" */
+#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUN" */
struct index_state the_index;
@@ -156,7 +157,7 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
return 0;
}
-int is_empty_blob_sha1(const unsigned char *sha1)
+static int is_empty_blob_sha1(const unsigned char *sha1)
{
static const unsigned char empty_blob_sha1[20] = {
0xe6,0x9d,0xe2,0x9b,0xb2,0xd1,0xd6,0x43,0x4b,0x8b,
@@ -259,12 +260,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;
@@ -449,6 +455,7 @@ int remove_index_entry_at(struct index_state *istate, int pos)
{
struct cache_entry *ce = istate->cache[pos];
+ record_resolve_undo(istate, ce);
remove_name_hash(ce);
istate->cache_changed = 1;
istate->cache_nr--;
@@ -564,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;
@@ -1000,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;
@@ -1141,7 +1154,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
return has_errors;
}
-struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
+static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
{
return refresh_cache_ent(&the_index, ce, really, NULL);
}
@@ -1170,6 +1183,9 @@ static int read_index_extension(struct index_state *istate,
case CACHE_EXT_TREE:
istate->cache_tree = cache_tree_read(data, sz);
break;
+ case CACHE_EXT_RESOLVE_UNDO:
+ istate->resolve_undo = resolve_undo_read(data, sz);
+ break;
default:
if (*ext < 'A' || 'Z' < *ext)
return error("index uses %.4s extension, which we do not understand",
@@ -1349,6 +1365,7 @@ int is_index_unborn(struct index_state *istate)
int discard_index(struct index_state *istate)
{
+ resolve_undo_clear_index(istate);
istate->cache_nr = 0;
istate->cache_changed = 0;
istate->timestamp.sec = 0;
@@ -1574,6 +1591,17 @@ int write_index(struct index_state *istate, int newfd)
if (err)
return -1;
}
+ if (istate->resolve_undo) {
+ struct strbuf sb = STRBUF_INIT;
+
+ resolve_undo_write(&sb, istate->resolve_undo);
+ err = write_index_ext_header(&c, newfd, CACHE_EXT_RESOLVE_UNDO,
+ sb.len) < 0
+ || ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ strbuf_release(&sb);
+ if (err)
+ return -1;
+ }
if (ce_flush(&c, newfd) || fstat(newfd, &st))
return -1;
@@ -1606,9 +1634,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",
@@ -1618,81 +1645,6 @@ int read_index_unmerged(struct index_state *istate)
return unmerged;
}
-struct update_callback_data
-{
- int flags;
- int add_errors;
-};
-
-static void update_callback(struct diff_queue_struct *q,
- struct diff_options *opt, void *cbdata)
-{
- int i;
- struct update_callback_data *data = cbdata;
-
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- const char *path = p->one->path;
- switch (p->status) {
- default:
- die("unexpected diff status %c", p->status);
- case DIFF_STATUS_UNMERGED:
- /*
- * ADD_CACHE_IGNORE_REMOVAL is unset if "git
- * add -u" is calling us, In such a case, a
- * missing work tree file needs to be removed
- * if there is an unmerged entry at stage #2,
- * but such a diff record is followed by
- * another with DIFF_STATUS_DELETED (and if
- * there is no stage #2, we won't see DELETED
- * nor MODIFIED). We can simply continue
- * either way.
- */
- if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL))
- continue;
- /*
- * Otherwise, it is "git add path" is asking
- * to explicitly add it; we fall through. A
- * missing work tree file is an error and is
- * caught by add_file_to_index() in such a
- * case.
- */
- case DIFF_STATUS_MODIFIED:
- case DIFF_STATUS_TYPE_CHANGED:
- if (add_file_to_index(&the_index, path, data->flags)) {
- if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
- die("updating files failed");
- data->add_errors++;
- }
- break;
- case DIFF_STATUS_DELETED:
- if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
- break;
- if (!(data->flags & ADD_CACHE_PRETEND))
- remove_file_from_index(&the_index, path);
- if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
- printf("remove '%s'\n", path);
- break;
- }
- }
-}
-
-int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
-{
- struct update_callback_data data;
- struct rev_info rev;
- init_revisions(&rev, prefix);
- setup_revisions(0, NULL, &rev, NULL);
- rev.prune_data = pathspec;
- rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
- rev.diffopt.format_callback = update_callback;
- data.flags = flags;
- data.add_errors = 0;
- rev.diffopt.format_callback_data = &data;
- run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
- return !!data.add_errors;
-}
-
/*
* Returns 1 if the path is an "other" path with respect to
* the index; that is, the path is not mentioned in the index at all,
diff --git a/refs.c b/refs.c
index 3e73a0a36d..503a8c2bd0 100644
--- a/refs.c
+++ b/refs.c
@@ -519,6 +519,13 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
return ref;
}
+/* The argument to filter_refs */
+struct ref_filter {
+ const char *pattern;
+ each_ref_fn *fn;
+ void *cb_data;
+};
+
int read_ref(const char *ref, unsigned char *sha1)
{
if (resolve_ref(ref, sha1, 1, NULL))
@@ -545,6 +552,15 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
}
+static int filter_refs(const char *ref, const unsigned char *sha, int flags,
+ void *data)
+{
+ struct ref_filter *filter = (struct ref_filter *)data;
+ if (fnmatch(filter->pattern, ref, 0))
+ return 0;
+ return filter->fn(ref, sha, flags, filter->cb_data);
+}
+
int peel_ref(const char *ref, unsigned char *sha1)
{
int flag;
@@ -674,6 +690,43 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data);
}
+int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
+ const char *prefix, void *cb_data)
+{
+ struct strbuf real_pattern = STRBUF_INIT;
+ struct ref_filter filter;
+ const char *has_glob_specials;
+ int ret;
+
+ if (!prefix && prefixcmp(pattern, "refs/"))
+ strbuf_addstr(&real_pattern, "refs/");
+ else if (prefix)
+ strbuf_addstr(&real_pattern, prefix);
+ strbuf_addstr(&real_pattern, pattern);
+
+ has_glob_specials = strpbrk(pattern, "?*[");
+ if (!has_glob_specials) {
+ /* Append impiled '/' '*' if not present. */
+ if (real_pattern.buf[real_pattern.len - 1] != '/')
+ strbuf_addch(&real_pattern, '/');
+ /* No need to check for '*', there is none. */
+ strbuf_addch(&real_pattern, '*');
+ }
+
+ filter.pattern = real_pattern.buf;
+ filter.fn = fn;
+ filter.cb_data = cb_data;
+ ret = for_each_ref(filter_refs, &filter);
+
+ strbuf_release(&real_pattern);
+ return ret;
+}
+
+int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
+{
+ return for_each_glob_ref_in(fn, pattern, NULL, cb_data);
+}
+
int for_each_rawref(each_ref_fn fn, void *cb_data)
{
return do_for_each_ref("refs/", fn, 0,
diff --git a/refs.h b/refs.h
index e141991851..f7648b9bd3 100644
--- a/refs.h
+++ b/refs.h
@@ -25,6 +25,8 @@ extern int for_each_tag_ref(each_ref_fn, void *);
extern int for_each_branch_ref(each_ref_fn, void *);
extern int for_each_remote_ref(each_ref_fn, void *);
extern int for_each_replace_ref(each_ref_fn, void *);
+extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);
+extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);
/* can be used to learn about broken ref and symref */
extern int for_each_rawref(each_ref_fn, void *);
diff --git a/remote-curl.c b/remote-curl.c
index a331bae6c8..a904164e42 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -102,7 +102,7 @@ static struct discovery* discover_refs(const char *service)
struct strbuf buffer = STRBUF_INIT;
struct discovery *last = last_discovery;
char *refs_url;
- int http_ret, is_http = 0;
+ int http_ret, is_http = 0, proto_git_candidate = 1;
if (last && !strcmp(service, last->service))
return last;
@@ -121,6 +121,19 @@ static struct discovery* discover_refs(const char *service)
init_walker();
http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
+
+ /* try again with "plain" url (no ? or & appended) */
+ if (http_ret != HTTP_OK) {
+ free(refs_url);
+ strbuf_reset(&buffer);
+
+ proto_git_candidate = 0;
+ strbuf_addf(&buffer, "%s/info/refs", url);
+ refs_url = strbuf_detach(&buffer, NULL);
+
+ http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
+ }
+
switch (http_ret) {
case HTTP_OK:
break;
@@ -137,7 +150,8 @@ static struct discovery* discover_refs(const char *service)
last->buf_alloc = strbuf_detach(&buffer, &last->len);
last->buf = last->buf_alloc;
- if (is_http && 5 <= last->len && last->buf[4] == '#') {
+ if (is_http && proto_git_candidate
+ && 5 <= last->len && last->buf[4] == '#') {
/* smart HTTP response; validate that the service
* pkt-line matches our request.
*/
@@ -290,6 +304,7 @@ struct rpc_state {
int out;
struct strbuf result;
unsigned gzip_request : 1;
+ unsigned initial_buffer : 1;
};
static size_t rpc_out(void *ptr, size_t eltsize,
@@ -300,6 +315,7 @@ static size_t rpc_out(void *ptr, size_t eltsize,
size_t avail = rpc->len - rpc->pos;
if (!avail) {
+ rpc->initial_buffer = 0;
avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
if (!avail)
return 0;
@@ -314,6 +330,29 @@ static size_t rpc_out(void *ptr, size_t eltsize,
return avail;
}
+#ifndef NO_CURL_IOCTL
+static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
+{
+ struct rpc_state *rpc = clientp;
+
+ switch (cmd) {
+ case CURLIOCMD_NOP:
+ return CURLIOE_OK;
+
+ case CURLIOCMD_RESTARTREAD:
+ if (rpc->initial_buffer) {
+ rpc->pos = 0;
+ return CURLIOE_OK;
+ }
+ fprintf(stderr, "Unable to rewind rpc post data - try increasing http.postBuffer\n");
+ return CURLIOE_FAILRESTART;
+
+ default:
+ return CURLIOE_UNKNOWNCMD;
+ }
+}
+#endif
+
static size_t rpc_in(const void *ptr, size_t eltsize,
size_t nmemb, void *buffer_)
{
@@ -370,8 +409,13 @@ static int post_rpc(struct rpc_state *rpc)
*/
headers = curl_slist_append(headers, "Expect: 100-continue");
headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
+ rpc->initial_buffer = 1;
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc);
+#ifndef NO_CURL_IOCTL
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, rpc_ioctl);
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, rpc);
+#endif
if (options.verbosity > 1) {
fprintf(stderr, "POST %s (chunked)\n", rpc->service_name);
fflush(stderr);
@@ -480,7 +524,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/remote.c b/remote.c
index e3afecdb10..c70181cdc6 100644
--- a/remote.c
+++ b/remote.c
@@ -1247,6 +1247,56 @@ int match_refs(struct ref *src, struct ref **dst,
return 0;
}
+void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
+ int force_update)
+{
+ struct ref *ref;
+
+ for (ref = remote_refs; ref; ref = ref->next) {
+ if (ref->peer_ref)
+ hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
+ else if (!send_mirror)
+ continue;
+
+ ref->deletion = is_null_sha1(ref->new_sha1);
+ if (!ref->deletion &&
+ !hashcmp(ref->old_sha1, ref->new_sha1)) {
+ ref->status = REF_STATUS_UPTODATE;
+ continue;
+ }
+
+ /* This part determines what can overwrite what.
+ * The rules are:
+ *
+ * (0) you can always use --force or +A:B notation to
+ * selectively force individual ref pairs.
+ *
+ * (1) if the old thing does not exist, it is OK.
+ *
+ * (2) if you do not have the old thing, you are not allowed
+ * to overwrite it; you would not know what you are losing
+ * otherwise.
+ *
+ * (3) if both new and old are commit-ish, and new is a
+ * descendant of old, it is OK.
+ *
+ * (4) regardless of all of the above, removing :B is
+ * always allowed.
+ */
+
+ ref->nonfastforward =
+ !ref->deletion &&
+ !is_null_sha1(ref->old_sha1) &&
+ (!has_sha1_file(ref->old_sha1)
+ || !ref_newer(ref->new_sha1, ref->old_sha1));
+
+ if (ref->nonfastforward && !ref->force && !force_update) {
+ ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
+ continue;
+ }
+ }
+}
+
struct branch *branch_get(const char *name)
{
struct branch *ret;
diff --git a/remote.h b/remote.h
index 8b7ecf9197..6e13643cab 100644
--- a/remote.h
+++ b/remote.h
@@ -98,6 +98,8 @@ char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
int match_refs(struct ref *src, struct ref **dst,
int nr_refspec, const char **refspec, int all);
+void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
+ int force_update);
/*
* Given a list of the remote refs and the specification of things to
diff --git a/rerere.c b/rerere.c
index 29f95f657d..a86d73d9dc 100644
--- a/rerere.c
+++ b/rerere.c
@@ -1,8 +1,11 @@
#include "cache.h"
#include "string-list.h"
#include "rerere.h"
-#include "xdiff/xdiff.h"
#include "xdiff-interface.h"
+#include "dir.h"
+#include "resolve-undo.h"
+#include "ll-merge.h"
+#include "attr.h"
/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
static int rerere_enabled = -1;
@@ -83,61 +86,106 @@ static inline void ferr_puts(const char *s, FILE *fp, int *err)
ferr_write(s, strlen(s), fp, err);
}
-static int handle_file(const char *path,
- unsigned char *sha1, const char *output)
+struct rerere_io {
+ int (*getline)(struct strbuf *, struct rerere_io *);
+ FILE *output;
+ int wrerror;
+ /* some more stuff */
+};
+
+static void rerere_io_putstr(const char *str, struct rerere_io *io)
+{
+ if (io->output)
+ ferr_puts(str, io->output, &io->wrerror);
+}
+
+static void rerere_io_putconflict(int ch, int size, struct rerere_io *io)
+{
+ char buf[64];
+
+ while (size) {
+ if (size < sizeof(buf) - 2) {
+ memset(buf, ch, size);
+ buf[size] = '\n';
+ buf[size + 1] = '\0';
+ size = 0;
+ } else {
+ int sz = sizeof(buf) - 1;
+ if (size <= sz)
+ sz -= (sz - size) + 1;
+ memset(buf, ch, sz);
+ buf[sz] = '\0';
+ size -= sz;
+ }
+ rerere_io_putstr(buf, io);
+ }
+}
+
+static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io)
+{
+ if (io->output)
+ ferr_write(mem, sz, io->output, &io->wrerror);
+}
+
+struct rerere_io_file {
+ struct rerere_io io;
+ FILE *input;
+};
+
+static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_)
+{
+ struct rerere_io_file *io = (struct rerere_io_file *)io_;
+ return strbuf_getwholeline(sb, io->input, '\n');
+}
+
+static int is_cmarker(char *buf, int marker_char, int marker_size, int want_sp)
+{
+ while (marker_size--)
+ if (*buf++ != marker_char)
+ return 0;
+ if (want_sp && *buf != ' ')
+ return 0;
+ return isspace(*buf);
+}
+
+static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size)
{
git_SHA_CTX ctx;
- char buf[1024];
int hunk_no = 0;
enum {
RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL,
} hunk = RR_CONTEXT;
struct strbuf one = STRBUF_INIT, two = STRBUF_INIT;
- FILE *f = fopen(path, "r");
- FILE *out = NULL;
- int wrerror = 0;
-
- if (!f)
- return error("Could not open %s", path);
-
- if (output) {
- out = fopen(output, "w");
- if (!out) {
- fclose(f);
- return error("Could not write %s", output);
- }
- }
+ struct strbuf buf = STRBUF_INIT;
if (sha1)
git_SHA1_Init(&ctx);
- while (fgets(buf, sizeof(buf), f)) {
- if (!prefixcmp(buf, "<<<<<<< ")) {
+ while (!io->getline(&buf, io)) {
+ if (is_cmarker(buf.buf, '<', marker_size, 1)) {
if (hunk != RR_CONTEXT)
goto bad;
hunk = RR_SIDE_1;
- } else if (!prefixcmp(buf, "|||||||") && isspace(buf[7])) {
+ } else if (is_cmarker(buf.buf, '|', marker_size, 0)) {
if (hunk != RR_SIDE_1)
goto bad;
hunk = RR_ORIGINAL;
- } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) {
+ } else if (is_cmarker(buf.buf, '=', marker_size, 0)) {
if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
goto bad;
hunk = RR_SIDE_2;
- } else if (!prefixcmp(buf, ">>>>>>> ")) {
+ } else if (is_cmarker(buf.buf, '>', marker_size, 1)) {
if (hunk != RR_SIDE_2)
goto bad;
if (strbuf_cmp(&one, &two) > 0)
strbuf_swap(&one, &two);
hunk_no++;
hunk = RR_CONTEXT;
- if (out) {
- ferr_puts("<<<<<<<\n", out, &wrerror);
- ferr_write(one.buf, one.len, out, &wrerror);
- ferr_puts("=======\n", out, &wrerror);
- ferr_write(two.buf, two.len, out, &wrerror);
- ferr_puts(">>>>>>>\n", out, &wrerror);
- }
+ rerere_io_putconflict('<', marker_size, io);
+ rerere_io_putmem(one.buf, one.len, io);
+ rerere_io_putconflict('=', marker_size, io);
+ rerere_io_putmem(two.buf, two.len, io);
+ rerere_io_putconflict('>', marker_size, io);
if (sha1) {
git_SHA1_Update(&ctx, one.buf ? one.buf : "",
one.len + 1);
@@ -147,13 +195,13 @@ static int handle_file(const char *path,
strbuf_reset(&one);
strbuf_reset(&two);
} else if (hunk == RR_SIDE_1)
- strbuf_addstr(&one, buf);
+ strbuf_addstr(&one, buf.buf);
else if (hunk == RR_ORIGINAL)
; /* discard */
else if (hunk == RR_SIDE_2)
- strbuf_addstr(&two, buf);
- else if (out)
- ferr_puts(buf, out, &wrerror);
+ strbuf_addstr(&two, buf.buf);
+ else
+ rerere_io_putstr(buf.buf, io);
continue;
bad:
hunk = 99; /* force error exit */
@@ -161,23 +209,135 @@ static int handle_file(const char *path,
}
strbuf_release(&one);
strbuf_release(&two);
+ strbuf_release(&buf);
- fclose(f);
- if (wrerror)
- error("There were errors while writing %s (%s)",
- path, strerror(wrerror));
- if (out && fclose(out))
- wrerror = error("Failed to flush %s: %s",
- path, strerror(errno));
if (sha1)
git_SHA1_Final(sha1, &ctx);
- if (hunk != RR_CONTEXT) {
+ if (hunk != RR_CONTEXT)
+ return -1;
+ return hunk_no;
+}
+
+static int handle_file(const char *path, unsigned char *sha1, const char *output)
+{
+ int hunk_no = 0;
+ struct rerere_io_file io;
+ int marker_size = ll_merge_marker_size(path);
+
+ memset(&io, 0, sizeof(io));
+ io.io.getline = rerere_file_getline;
+ io.input = fopen(path, "r");
+ io.io.wrerror = 0;
+ if (!io.input)
+ return error("Could not open %s", path);
+
+ if (output) {
+ io.io.output = fopen(output, "w");
+ if (!io.io.output) {
+ fclose(io.input);
+ return error("Could not write %s", output);
+ }
+ }
+
+ hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
+
+ fclose(io.input);
+ if (io.io.wrerror)
+ error("There were errors while writing %s (%s)",
+ path, strerror(io.io.wrerror));
+ if (io.io.output && fclose(io.io.output))
+ io.io.wrerror = error("Failed to flush %s: %s",
+ path, strerror(errno));
+
+ if (hunk_no < 0) {
if (output)
unlink_or_warn(output);
return error("Could not parse conflict hunks in %s", path);
}
- if (wrerror)
+ if (io.io.wrerror)
+ return -1;
+ return hunk_no;
+}
+
+struct rerere_io_mem {
+ struct rerere_io io;
+ struct strbuf input;
+};
+
+static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)
+{
+ struct rerere_io_mem *io = (struct rerere_io_mem *)io_;
+ char *ep;
+ size_t len;
+
+ strbuf_release(sb);
+ if (!io->input.len)
+ return -1;
+ ep = strchrnul(io->input.buf, '\n');
+ if (*ep == '\n')
+ ep++;
+ len = ep - io->input.buf;
+ strbuf_add(sb, io->input.buf, len);
+ strbuf_remove(&io->input, 0, len);
+ return 0;
+}
+
+static int handle_cache(const char *path, unsigned char *sha1, const char *output)
+{
+ mmfile_t mmfile[3];
+ mmbuffer_t result = {NULL, 0};
+ struct cache_entry *ce;
+ int pos, len, i, hunk_no;
+ struct rerere_io_mem io;
+ int marker_size = ll_merge_marker_size(path);
+
+ /*
+ * Reproduce the conflicted merge in-core
+ */
+ len = strlen(path);
+ pos = cache_name_pos(path, len);
+ if (0 <= pos)
return -1;
+ pos = -pos - 1;
+
+ for (i = 0; i < 3; i++) {
+ enum object_type type;
+ unsigned long size;
+
+ mmfile[i].size = 0;
+ mmfile[i].ptr = NULL;
+ if (active_nr <= pos)
+ break;
+ ce = active_cache[pos++];
+ if (ce_namelen(ce) != len || memcmp(ce->name, path, len)
+ || ce_stage(ce) != i + 1)
+ break;
+ mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);
+ mmfile[i].size = size;
+ }
+ for (i = 0; i < 3; i++) {
+ if (!mmfile[i].ptr && !mmfile[i].size)
+ mmfile[i].ptr = xstrdup("");
+ }
+ ll_merge(&result, path, &mmfile[0],
+ &mmfile[1], "ours",
+ &mmfile[2], "theirs", 0);
+ for (i = 0; i < 3; i++)
+ free(mmfile[i].ptr);
+
+ memset(&io, 0, sizeof(&io));
+ io.io.getline = rerere_mem_getline;
+ if (output)
+ io.io.output = fopen(output, "w");
+ else
+ io.io.output = NULL;
+ strbuf_init(&io.input, 0);
+ strbuf_attach(&io.input, result.ptr, result.size, result.size);
+
+ hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
+ strbuf_release(&io.input);
+ if (io.io.output)
+ fclose(io.io.output);
return hunk_no;
}
@@ -206,7 +366,6 @@ static int merge(const char *name, const char *path)
int ret;
mmfile_t cur, base, other;
mmbuffer_t result = {NULL, 0};
- xpparam_t xpp = {XDF_NEED_MINIMAL};
if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0)
return 1;
@@ -215,8 +374,7 @@ static int merge(const char *name, const char *path)
read_mmfile(&base, rerere_path(name, "preimage")) ||
read_mmfile(&other, rerere_path(name, "postimage")))
return 1;
- ret = xdl_merge(&base, &cur, "", &other, "",
- &xpp, XDL_MERGE_ZEALOUS, &result);
+ ret = ll_merge(&result, path, &base, &cur, "", &other, "", 0);
if (!ret) {
FILE *f = fopen(path, "w");
if (!f)
@@ -367,7 +525,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 +533,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,13 +542,62 @@ 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);
}
+
+static int rerere_forget_one_path(const char *path, struct string_list *rr)
+{
+ const char *filename;
+ char *hex;
+ unsigned char sha1[20];
+ int ret;
+
+ ret = handle_cache(path, sha1, NULL);
+ if (ret < 1)
+ return error("Could not parse conflict hunks in '%s'", path);
+ hex = xstrdup(sha1_to_hex(sha1));
+ filename = rerere_path(hex, "postimage");
+ if (unlink(filename))
+ return (errno == ENOENT
+ ? error("no remembered resolution for %s", path)
+ : error("cannot unlink %s: %s", filename, strerror(errno)));
+
+ handle_cache(path, sha1, rerere_path(hex, "preimage"));
+ fprintf(stderr, "Updated preimage for '%s'\n", path);
+
+
+ string_list_insert(path, rr)->util = hex;
+ fprintf(stderr, "Forgot resolution for %s\n", path);
+ return 0;
+}
+
+int rerere_forget(const char **pathspec)
+{
+ int i, fd;
+ struct string_list conflict = { NULL, 0, 0, 1 };
+ struct string_list merge_rr = { NULL, 0, 0, 1 };
+
+ if (read_cache() < 0)
+ return error("Could not read index");
+
+ fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE);
+
+ unmerge_cache(pathspec);
+ find_conflict(&conflict);
+ for (i = 0; i < conflict.nr; i++) {
+ struct string_list_item *it = &conflict.items[i];
+ if (!match_pathspec(pathspec, it->string, strlen(it->string),
+ 0, NULL))
+ continue;
+ rerere_forget_one_path(it->string, &merge_rr);
+ }
+ return write_rr(&merge_rr, fd);
+}
diff --git a/rerere.h b/rerere.h
index 13313f3f2b..eaa9004dcd 100644
--- a/rerere.h
+++ b/rerere.h
@@ -3,9 +3,16 @@
#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);
+extern int rerere_forget(const char **);
+
+#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \
+ "update the index with reused conflict resolution if possible")
#endif
diff --git a/resolve-undo.c b/resolve-undo.c
new file mode 100644
index 0000000000..37d73cd949
--- /dev/null
+++ b/resolve-undo.c
@@ -0,0 +1,176 @@
+#include "cache.h"
+#include "dir.h"
+#include "resolve-undo.h"
+#include "string-list.h"
+
+/* The only error case is to run out of memory in string-list */
+void record_resolve_undo(struct index_state *istate, struct cache_entry *ce)
+{
+ struct string_list_item *lost;
+ struct resolve_undo_info *ui;
+ struct string_list *resolve_undo;
+ int stage = ce_stage(ce);
+
+ if (!stage)
+ return;
+
+ if (!istate->resolve_undo) {
+ resolve_undo = xcalloc(1, sizeof(*resolve_undo));
+ resolve_undo->strdup_strings = 1;
+ istate->resolve_undo = resolve_undo;
+ }
+ resolve_undo = istate->resolve_undo;
+ lost = string_list_insert(ce->name, resolve_undo);
+ if (!lost->util)
+ lost->util = xcalloc(1, sizeof(*ui));
+ ui = lost->util;
+ hashcpy(ui->sha1[stage - 1], ce->sha1);
+ ui->mode[stage - 1] = ce->ce_mode;
+}
+
+static int write_one(struct string_list_item *item, void *cbdata)
+{
+ struct strbuf *sb = cbdata;
+ struct resolve_undo_info *ui = item->util;
+ int i;
+
+ if (!ui)
+ return 0;
+ strbuf_addstr(sb, item->string);
+ strbuf_addch(sb, 0);
+ for (i = 0; i < 3; i++)
+ strbuf_addf(sb, "%o%c", ui->mode[i], 0);
+ for (i = 0; i < 3; i++) {
+ if (!ui->mode[i])
+ continue;
+ strbuf_add(sb, ui->sha1[i], 20);
+ }
+ return 0;
+}
+
+void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo)
+{
+ for_each_string_list(write_one, resolve_undo, sb);
+}
+
+struct string_list *resolve_undo_read(void *data, unsigned long size)
+{
+ struct string_list *resolve_undo;
+ size_t len;
+ char *endptr;
+ int i;
+
+ resolve_undo = xcalloc(1, sizeof(*resolve_undo));
+ resolve_undo->strdup_strings = 1;
+
+ while (size) {
+ struct string_list_item *lost;
+ struct resolve_undo_info *ui;
+
+ len = strlen(data) + 1;
+ if (size <= len)
+ goto error;
+ lost = string_list_insert(data, resolve_undo);
+ if (!lost->util)
+ lost->util = xcalloc(1, sizeof(*ui));
+ ui = lost->util;
+ size -= len;
+ data += len;
+
+ for (i = 0; i < 3; i++) {
+ ui->mode[i] = strtoul(data, &endptr, 8);
+ if (!endptr || endptr == data || *endptr)
+ goto error;
+ len = (endptr + 1) - (char*)data;
+ if (size <= len)
+ goto error;
+ size -= len;
+ data += len;
+ }
+
+ for (i = 0; i < 3; i++) {
+ if (!ui->mode[i])
+ continue;
+ if (size < 20)
+ goto error;
+ hashcpy(ui->sha1[i], data);
+ size -= 20;
+ data += 20;
+ }
+ }
+ return resolve_undo;
+
+error:
+ string_list_clear(resolve_undo, 1);
+ error("Index records invalid resolve-undo information");
+ return NULL;
+}
+
+void resolve_undo_clear_index(struct index_state *istate)
+{
+ struct string_list *resolve_undo = istate->resolve_undo;
+ if (!resolve_undo)
+ return;
+ string_list_clear(resolve_undo, 1);
+ free(resolve_undo);
+ istate->resolve_undo = NULL;
+ istate->cache_changed = 1;
+}
+
+int unmerge_index_entry_at(struct index_state *istate, int pos)
+{
+ struct cache_entry *ce;
+ struct string_list_item *item;
+ struct resolve_undo_info *ru;
+ int i, err = 0;
+
+ if (!istate->resolve_undo)
+ return pos;
+
+ ce = istate->cache[pos];
+ if (ce_stage(ce)) {
+ /* already unmerged */
+ while ((pos < istate->cache_nr) &&
+ ! strcmp(istate->cache[pos]->name, ce->name))
+ pos++;
+ return pos - 1; /* return the last entry processed */
+ }
+ item = string_list_lookup(ce->name, istate->resolve_undo);
+ if (!item)
+ return pos;
+ ru = item->util;
+ if (!ru)
+ return pos;
+ remove_index_entry_at(istate, pos);
+ for (i = 0; i < 3; i++) {
+ struct cache_entry *nce;
+ if (!ru->mode[i])
+ continue;
+ nce = make_cache_entry(ru->mode[i], ru->sha1[i],
+ ce->name, i + 1, 0);
+ if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) {
+ err = 1;
+ error("cannot unmerge '%s'", ce->name);
+ }
+ }
+ if (err)
+ return pos;
+ free(ru);
+ item->util = NULL;
+ return unmerge_index_entry_at(istate, pos);
+}
+
+void unmerge_index(struct index_state *istate, const char **pathspec)
+{
+ int i;
+
+ if (!istate->resolve_undo)
+ return;
+
+ for (i = 0; i < istate->cache_nr; i++) {
+ struct cache_entry *ce = istate->cache[i];
+ if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL))
+ continue;
+ i = unmerge_index_entry_at(istate, i);
+ }
+}
diff --git a/resolve-undo.h b/resolve-undo.h
new file mode 100644
index 0000000000..e4e5c1b1ad
--- /dev/null
+++ b/resolve-undo.h
@@ -0,0 +1,16 @@
+#ifndef RESOLVE_UNDO_H
+#define RESOLVE_UNDO_H
+
+struct resolve_undo_info {
+ unsigned int mode[3];
+ unsigned char sha1[3][20];
+};
+
+extern void record_resolve_undo(struct index_state *, struct cache_entry *);
+extern void resolve_undo_write(struct strbuf *, struct string_list *);
+extern struct string_list *resolve_undo_read(void *, unsigned long);
+extern void resolve_undo_clear_index(struct index_state *);
+extern int unmerge_index_entry_at(struct index_state *, int);
+extern void unmerge_index(struct index_state *, const char **);
+
+#endif
diff --git a/revision.c b/revision.c
index 25fa14d93e..f54d43ffb3 100644
--- a/revision.c
+++ b/revision.c
@@ -268,7 +268,7 @@ static int tree_difference = REV_TREE_SAME;
static void file_add_remove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
- const char *fullpath)
+ const char *fullpath, unsigned dirty_submodule)
{
int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD;
@@ -281,7 +281,8 @@ static void file_change(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
- const char *fullpath)
+ const char *fullpath,
+ unsigned old_dirty_submodule, unsigned new_dirty_submodule)
{
tree_difference = REV_TREE_DIFFERENT;
DIFF_OPT_SET(options, HAS_CHANGES);
@@ -699,12 +700,18 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag,
return 0;
}
+static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,
+ unsigned flags)
+{
+ cb->all_revs = revs;
+ cb->all_flags = flags;
+}
+
static void handle_refs(struct rev_info *revs, unsigned flags,
int (*for_each)(each_ref_fn, void *))
{
struct all_refs_cb cb;
- cb.all_revs = revs;
- cb.all_flags = flags;
+ init_all_refs_cb(&cb, revs, flags);
for_each(handle_one_ref, &cb);
}
@@ -1161,13 +1168,22 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->verbose_header = 1;
} else if (!strcmp(arg, "--pretty")) {
revs->verbose_header = 1;
+ revs->pretty_given = 1;
get_commit_format(arg+8, revs);
} else if (!prefixcmp(arg, "--pretty=") || !prefixcmp(arg, "--format=")) {
revs->verbose_header = 1;
+ revs->pretty_given = 1;
get_commit_format(arg+9, revs);
+ } else if (!strcmp(arg, "--show-notes")) {
+ revs->show_notes = 1;
+ revs->show_notes_given = 1;
+ } else if (!strcmp(arg, "--no-notes")) {
+ revs->show_notes = 0;
+ revs->show_notes_given = 1;
} else if (!strcmp(arg, "--oneline")) {
revs->verbose_header = 1;
get_commit_format("oneline", revs);
+ revs->pretty_given = 1;
revs->abbrev_commit = 1;
} else if (!strcmp(arg, "--graph")) {
revs->topo_order = 1;
@@ -1352,6 +1368,30 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
handle_refs(revs, flags, for_each_remote_ref);
continue;
}
+ if (!prefixcmp(arg, "--glob=")) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, flags);
+ for_each_glob_ref(handle_one_ref, arg + 7, &cb);
+ continue;
+ }
+ if (!prefixcmp(arg, "--branches=")) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, flags);
+ for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb);
+ continue;
+ }
+ if (!prefixcmp(arg, "--tags=")) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, flags);
+ for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb);
+ continue;
+ }
+ if (!prefixcmp(arg, "--remotes=")) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, flags);
+ for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb);
+ continue;
+ }
if (!strcmp(arg, "--reflog")) {
handle_reflog(revs, flags);
continue;
diff --git a/revision.h b/revision.h
index d368003159..a14deefc25 100644
--- a/revision.h
+++ b/revision.h
@@ -80,6 +80,9 @@ struct rev_info {
/* Format info */
unsigned int shown_one:1,
show_merge:1,
+ show_notes:1,
+ show_notes_given:1,
+ pretty_given:1,
abbrev_commit:1,
use_terminator:1,
missing_newline:1,
diff --git a/run-command.c b/run-command.c
index cf2d8f7fae..2feb493951 100644
--- a/run-command.c
+++ b/run-command.c
@@ -8,12 +8,130 @@ 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
+
+#ifndef WIN32
+static int child_err = 2;
+static int child_notifier = -1;
+
+static void notify_parent(void)
+{
+ write(child_notifier, "", 1);
+}
+
+static NORETURN void die_child(const char *err, va_list params)
+{
+ char msg[4096];
+ int len = vsnprintf(msg, sizeof(msg), err, params);
+ if (len > sizeof(msg))
+ len = sizeof(msg);
+
+ write(child_err, "fatal: ", 7);
+ write(child_err, msg, len);
+ write(child_err, "\n", 1);
+ exit(128);
+}
+
+static inline void set_cloexec(int fd)
+{
+ int flags = fcntl(fd, F_GETFD);
+ if (flags >= 0)
+ fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
+}
+#endif
+
+static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
+{
+ int status, code = -1;
+ pid_t waiting;
+ int failed_errno = 0;
+
+ while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
+ ; /* nothing */
+
+ if (waiting < 0) {
+ failed_errno = errno;
+ error("waitpid for %s failed: %s", argv0, strerror(errno));
+ } else if (waiting != pid) {
+ error("waitpid is confused (%s)", argv0);
+ } else if (WIFSIGNALED(status)) {
+ code = WTERMSIG(status);
+ error("%s died of signal %d", argv0, code);
+ /*
+ * This return value is chosen so that code & 0xff
+ * mimics the exit code that a POSIX shell would report for
+ * a program that died from this signal.
+ */
+ code -= 128;
+ } else if (WIFEXITED(status)) {
+ code = WEXITSTATUS(status);
+ /*
+ * Convert special exit code when execvp failed.
+ */
+ if (code == 127) {
+ code = -1;
+ failed_errno = ENOENT;
+ if (!silent_exec_failure)
+ error("cannot run %s: %s", argv0,
+ strerror(ENOENT));
+ }
+ } else {
+ error("waitpid is confused (%s)", argv0);
+ }
+ errno = failed_errno;
+ return code;
+}
int start_command(struct child_process *cmd)
{
@@ -76,9 +194,30 @@ fail_pipe:
trace_argv_printf(cmd->argv, "trace: run_command:");
#ifndef WIN32
+{
+ int notify_pipe[2];
+ if (pipe(notify_pipe))
+ notify_pipe[0] = notify_pipe[1] = -1;
+
fflush(NULL);
cmd->pid = fork();
if (!cmd->pid) {
+ /*
+ * Redirect the channel to write syscall error messages to
+ * before redirecting the process's stderr so that all die()
+ * in subsequent call paths use the parent's stderr.
+ */
+ if (cmd->no_stderr || need_err) {
+ child_err = dup(2);
+ set_cloexec(child_err);
+ }
+ set_die_routine(die_child);
+
+ close(notify_pipe[0]);
+ set_cloexec(notify_pipe[1]);
+ child_notifier = notify_pipe[1];
+ atexit(notify_parent);
+
if (cmd->no_stdin)
dup_devnull(0);
else if (need_in) {
@@ -119,58 +258,82 @@ fail_pipe:
unsetenv(*cmd->env);
}
}
- if (cmd->preexec_cb)
+ if (cmd->preexec_cb) {
+ /*
+ * We cannot predict what the pre-exec callback does.
+ * Forgo parent notification.
+ */
+ close(child_notifier);
+ child_notifier = -1;
+
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);
}
- trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0],
- strerror(errno));
- exit(127);
+ /*
+ * Do not check for cmd->silent_exec_failure; the parent
+ * process will check it when it sees this exit code.
+ */
+ if (errno == ENOENT)
+ exit(127);
+ else
+ die_errno("cannot exec '%s'", cmd->argv[0]);
}
if (cmd->pid < 0)
error("cannot fork() for %s: %s", cmd->argv[0],
strerror(failed_errno = errno));
+
+ /*
+ * Wait for child's execvp. If the execvp succeeds (or if fork()
+ * failed), EOF is seen immediately by the parent. Otherwise, the
+ * child process sends a single byte.
+ * Note that use of this infrastructure is completely advisory,
+ * therefore, we keep error checks minimal.
+ */
+ close(notify_pipe[1]);
+ if (read(notify_pipe[0], &notify_pipe[1], 1) == 1) {
+ /*
+ * At this point we know that fork() succeeded, but execvp()
+ * failed. Errors have been reported to our stderr.
+ */
+ wait_or_whine(cmd->pid, cmd->argv[0],
+ cmd->silent_exec_failure);
+ failed_errno = errno;
+ cmd->pid = -1;
+ }
+ close(notify_pipe[0]);
+}
#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 +342,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 +358,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
@@ -232,48 +398,6 @@ fail_pipe:
return 0;
}
-static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
-{
- int status, code = -1;
- pid_t waiting;
- int failed_errno = 0;
-
- while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
- ; /* nothing */
-
- if (waiting < 0) {
- failed_errno = errno;
- error("waitpid for %s failed: %s", argv0, strerror(errno));
- } else if (waiting != pid) {
- error("waitpid is confused (%s)", argv0);
- } else if (WIFSIGNALED(status)) {
- code = WTERMSIG(status);
- error("%s died of signal %d", argv0, code);
- /*
- * This return value is chosen so that code & 0xff
- * mimics the exit code that a POSIX shell would report for
- * a program that died from this signal.
- */
- code -= 128;
- } else if (WIFEXITED(status)) {
- code = WEXITSTATUS(status);
- /*
- * Convert special exit code when execvp failed.
- */
- if (code == 127) {
- code = -1;
- failed_errno = ENOENT;
- if (!silent_exec_failure)
- error("cannot run %s: %s", argv0,
- strerror(ENOENT));
- }
- } else {
- error("waitpid is confused (%s)", argv0);
- }
- errno = failed_errno;
- return code;
-}
-
int finish_command(struct child_process *cmd)
{
return wait_or_whine(cmd->pid, cmd->argv[0], cmd->silent_exec_failure);
@@ -297,6 +421,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 fb342090e3..967ba8cc09 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/setup.c b/setup.c
index 2cf0f19937..710e2f3008 100644
--- a/setup.c
+++ b/setup.c
@@ -77,6 +77,18 @@ int check_filename(const char *prefix, const char *arg)
die_errno("failed to stat '%s'", arg);
}
+static void NORETURN die_verify_filename(const char *prefix, const char *arg)
+{
+ unsigned char sha1[20];
+ unsigned mode;
+ /* try a detailed diagnostic ... */
+ get_sha1_with_mode_1(arg, sha1, &mode, 0, prefix);
+ /* ... or fall back the most general message. */
+ die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
+ "Use '--' to separate paths from revisions", arg);
+
+}
+
/*
* Verify a filename that we got as an argument for a pathspec
* entry. Note that a filename that begins with "-" never verifies
@@ -90,8 +102,7 @@ void verify_filename(const char *prefix, const char *arg)
die("bad flag '%s' used after filename", arg);
if (check_filename(prefix, arg))
return;
- die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
- "Use '--' to separate paths from revisions", arg);
+ die_verify_filename(prefix, arg);
}
/*
@@ -252,6 +263,8 @@ static int check_repository_format_gently(int *nongit_ok)
const char *read_gitfile_gently(const char *path)
{
char *buf;
+ char *dir;
+ const char *slash;
struct stat st;
int fd;
size_t len;
@@ -276,9 +289,23 @@ const char *read_gitfile_gently(const char *path)
if (len < 9)
die("No path in gitfile: %s", path);
buf[len] = '\0';
- if (!is_git_directory(buf + 8))
- die("Not a git repository: %s", buf + 8);
- path = make_absolute_path(buf + 8);
+ dir = buf + 8;
+
+ if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) {
+ size_t pathlen = slash+1 - path;
+ size_t dirlen = pathlen + len - 8;
+ dir = xmalloc(dirlen + 1);
+ strncpy(dir, path, pathlen);
+ strncpy(dir + pathlen, buf + 8, len - 8);
+ dir[dirlen] = '\0';
+ free(buf);
+ buf = dir;
+ }
+
+ if (!is_git_directory(dir))
+ die("Not a git repository: %s", dir);
+ path = make_absolute_path(dir);
+
free(buf);
return path;
}
diff --git a/sha1_file.c b/sha1_file.c
index 63981fb3fd..12478a3652 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -35,54 +35,6 @@ static size_t sz_fmt(size_t s) { return s; }
const unsigned char null_sha1[20];
-const signed char hexval_table[256] = {
- -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */
- 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */
- 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */
- -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */
- -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */
- -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */
- -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */
- -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */
- -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */
- -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */
- -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */
- -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */
-};
-
-int get_sha1_hex(const char *hex, unsigned char *sha1)
-{
- int i;
- for (i = 0; i < 20; i++) {
- unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
- if (val & ~0xff)
- return -1;
- *sha1++ = val;
- hex += 2;
- }
- return 0;
-}
-
static inline int offset_1st_component(const char *path)
{
if (has_dos_drive_prefix(path))
@@ -133,24 +85,6 @@ int safe_create_leading_directories_const(const char *path)
return result;
}
-char *sha1_to_hex(const unsigned char *sha1)
-{
- static int bufno;
- static char hexbuffer[4][50];
- static const char hex[] = "0123456789abcdef";
- char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
- int i;
-
- for (i = 0; i < 20; i++) {
- unsigned int val = *sha1++;
- *buf++ = hex[val >> 4];
- *buf++ = hex[val & 0xf];
- }
- *buf = '\0';
-
- return buffer;
-}
-
static void fill_sha1_path(char *pathbuf, const unsigned char *sha1)
{
int i;
@@ -2458,14 +2392,6 @@ int has_pack_index(const unsigned char *sha1)
return 1;
}
-int has_pack_file(const unsigned char *sha1)
-{
- struct stat st;
- if (stat(sha1_pack_name(sha1), &st))
- return 0;
- return 1;
-}
-
int has_sha1_pack(const unsigned char *sha1)
{
struct pack_entry e;
diff --git a/sha1_name.c b/sha1_name.c
index 44bb62d270..9215ad1d05 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -5,6 +5,7 @@
#include "blob.h"
#include "tree-walk.h"
#include "refs.h"
+#include "remote.h"
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
@@ -240,7 +241,8 @@ static int ambiguous_path(const char *path, int len)
/*
* *string and *len will only be substituted, and *string returned (for
- * later free()ing) if the string passed in is of the form @{-<n>}.
+ * later free()ing) if the string passed in is a magic short-hand form
+ * to name a branch.
*/
static char *substitute_branch_name(const char **string, int *len)
{
@@ -323,6 +325,20 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
return logs_found;
}
+static inline int upstream_mark(const char *string, int len)
+{
+ const char *suffix[] = { "@{upstream}", "@{u}" };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(suffix); i++) {
+ int suffix_len = strlen(suffix[i]);
+ if (suffix_len <= len
+ && !memcmp(string, suffix[i], suffix_len))
+ return suffix_len;
+ }
+ return 0;
+}
+
static int get_sha1_1(const char *name, int len, unsigned char *sha1);
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
@@ -340,8 +356,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
if (len && str[len-1] == '}') {
for (at = len-2; at >= 0; at--) {
if (str[at] == '@' && str[at+1] == '{') {
- reflog_len = (len-1) - (at+2);
- len = at;
+ if (!upstream_mark(str + at, len - at)) {
+ reflog_len = (len-1) - (at+2);
+ len = at;
+ }
break;
}
}
@@ -740,17 +758,10 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
}
/*
- * This reads "@{-N}" syntax, finds the name of the Nth previous
- * branch we were on, and places the name of the branch in the given
- * buf and returns the number of characters parsed if successful.
- *
- * If the input is not of the accepted format, it returns a negative
- * number to signal an error.
- *
- * If the input was ok but there are not N branch switches in the
- * reflog, it returns 0.
+ * Parse @{-N} syntax, return the number of characters parsed
+ * if successful; otherwise signal an error with negative value.
*/
-int interpret_branch_name(const char *name, struct strbuf *buf)
+static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
{
long nth;
int i, retval;
@@ -794,6 +805,102 @@ 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 reads short-hand syntax that not only evaluates to a commit
+ * object name, but also can act as if the end user spelled the name
+ * of the branch from the command line.
+ *
+ * - "@{-N}" finds the name of the Nth previous branch we were on, and
+ * places the name of the branch in the given buf and returns the
+ * number of characters parsed if successful.
+ *
+ * - "<branch>@{upstream}" finds the name of the other ref that
+ * <branch> is configured to merge with (missing <branch> defaults
+ * to the current branch), and places the name of the branch in the
+ * given buf and returns the number of characters parsed if
+ * successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
+ */
+int interpret_branch_name(const char *name, struct strbuf *buf)
+{
+ char *cp;
+ struct branch *upstream;
+ int namelen = strlen(name);
+ int len = interpret_nth_prior_checkout(name, buf);
+ int tmp_len;
+
+ if (!len)
+ return len; /* syntax Ok, not enough switches */
+ if (0 < len)
+ return len; /* consumed from the front */
+ cp = strchr(name, '@');
+ if (!cp)
+ return -1;
+ tmp_len = upstream_mark(cp, namelen - (cp - name));
+ if (!tmp_len)
+ return -1;
+ len = cp + tmp_len - name;
+ cp = xstrndup(name, cp - name);
+ upstream = branch_get(*cp ? cp : NULL);
+ if (!upstream
+ || !upstream->merge
+ || !upstream->merge[0]->dst)
+ return error("No upstream branch found for '%s'", cp);
+ free(cp);
+ cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
+ strbuf_reset(buf);
+ strbuf_addstr(buf, cp);
+ free(cp);
+ return len;
+}
+
/*
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
* notably "xyz^" for "parent of xyz"
@@ -804,7 +911,96 @@ int get_sha1(const char *name, unsigned char *sha1)
return get_sha1_with_mode(name, sha1, &unused);
}
-int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
+/* Must be called only when object_name:filename doesn't exist. */
+static void diagnose_invalid_sha1_path(const char *prefix,
+ const char *filename,
+ const unsigned char *tree_sha1,
+ const char *object_name)
+{
+ struct stat st;
+ unsigned char sha1[20];
+ unsigned mode;
+
+ if (!prefix)
+ prefix = "";
+
+ if (!lstat(filename, &st))
+ die("Path '%s' exists on disk, but not in '%s'.",
+ filename, object_name);
+ if (errno == ENOENT || errno == ENOTDIR) {
+ char *fullname = xmalloc(strlen(filename)
+ + strlen(prefix) + 1);
+ strcpy(fullname, prefix);
+ strcat(fullname, filename);
+
+ if (!get_tree_entry(tree_sha1, fullname,
+ sha1, &mode)) {
+ die("Path '%s' exists, but not '%s'.\n"
+ "Did you mean '%s:%s'?",
+ fullname,
+ filename,
+ object_name,
+ fullname);
+ }
+ die("Path '%s' does not exist in '%s'",
+ filename, object_name);
+ }
+}
+
+/* Must be called only when :stage:filename doesn't exist. */
+static void diagnose_invalid_index_path(int stage,
+ const char *prefix,
+ const char *filename)
+{
+ struct stat st;
+ struct cache_entry *ce;
+ int pos;
+ unsigned namelen = strlen(filename);
+ unsigned fullnamelen;
+ char *fullname;
+
+ if (!prefix)
+ prefix = "";
+
+ /* Wrong stage number? */
+ pos = cache_name_pos(filename, namelen);
+ if (pos < 0)
+ pos = -pos - 1;
+ ce = active_cache[pos];
+ if (ce_namelen(ce) == namelen &&
+ !memcmp(ce->name, filename, namelen))
+ die("Path '%s' is in the index, but not at stage %d.\n"
+ "Did you mean ':%d:%s'?",
+ filename, stage,
+ ce_stage(ce), filename);
+
+ /* Confusion between relative and absolute filenames? */
+ fullnamelen = namelen + strlen(prefix);
+ fullname = xmalloc(fullnamelen + 1);
+ strcpy(fullname, prefix);
+ strcat(fullname, filename);
+ pos = cache_name_pos(fullname, fullnamelen);
+ if (pos < 0)
+ pos = -pos - 1;
+ ce = active_cache[pos];
+ if (ce_namelen(ce) == fullnamelen &&
+ !memcmp(ce->name, fullname, fullnamelen))
+ die("Path '%s' is in the index, but not '%s'.\n"
+ "Did you mean ':%d:%s'?",
+ fullname, filename,
+ ce_stage(ce), fullname);
+
+ if (!lstat(filename, &st))
+ die("Path '%s' exists on disk, but not in the index.", filename);
+ if (errno == ENOENT || errno == ENOTDIR)
+ die("Path '%s' does not exist (neither on disk nor in the index).",
+ filename);
+
+ free(fullname);
+}
+
+
+int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix)
{
int ret, bracket_depth;
int namelen = strlen(name);
@@ -850,6 +1046,8 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
}
pos++;
}
+ if (!gently)
+ diagnose_invalid_index_path(stage, prefix, cp);
return -1;
}
for (cp = name, bracket_depth = 0; *cp; cp++) {
@@ -862,9 +1060,25 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
}
if (*cp == ':') {
unsigned char tree_sha1[20];
- if (!get_sha1_1(name, cp-name, tree_sha1))
- return get_tree_entry(tree_sha1, cp+1, sha1,
- mode);
+ char *object_name = NULL;
+ if (!gently) {
+ object_name = xmalloc(cp-name+1);
+ strncpy(object_name, name, cp-name);
+ object_name[cp-name] = '\0';
+ }
+ if (!get_sha1_1(name, cp-name, tree_sha1)) {
+ const char *filename = cp+1;
+ ret = get_tree_entry(tree_sha1, filename, sha1, mode);
+ if (!gently) {
+ diagnose_invalid_sha1_path(prefix, filename,
+ tree_sha1, object_name);
+ free(object_name);
+ }
+ return ret;
+ } else {
+ if (!gently)
+ die("Invalid object name '%s'.", object_name);
+ }
}
return ret;
}
diff --git a/show-index.c b/show-index.c
index 63f9da5323..4c0ac138af 100644
--- a/show-index.c
+++ b/show-index.c
@@ -48,7 +48,7 @@ int main(int argc, char **argv)
unsigned char sha1[20];
uint32_t crc;
uint32_t off;
- } *entries = xmalloc(nr * sizeof(entries[0]));
+ } *entries = malloc(nr * sizeof(entries[0]));
for (i = 0; i < nr; i++)
if (fread(entries[i].sha1, 20, 1, stdin) != 1)
die("unable to read sha1 %u/%u", i, nr);
diff --git a/strbuf.c b/strbuf.c
index a6153dca27..bc3a0802ea 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -10,6 +10,15 @@ int prefixcmp(const char *str, const char *prefix)
return (unsigned char)*prefix - (unsigned char)*str;
}
+int suffixcmp(const char *str, const char *suffix)
+{
+ int len = strlen(str), suflen = strlen(suffix);
+ if (len < suflen)
+ return -1;
+ else
+ return strcmp(str + len - suflen, suffix);
+}
+
/*
* Used as the default ->buf value, so that people can always assume
* buf is non NULL and ->buf is NUL terminated even for a freshly
@@ -91,13 +100,6 @@ void strbuf_ltrim(struct strbuf *sb)
sb->buf[sb->len] = '\0';
}
-void strbuf_tolower(struct strbuf *sb)
-{
- int i;
- for (i = 0; i < sb->len; i++)
- sb->buf[i] = tolower(sb->buf[i]);
-}
-
struct strbuf **strbuf_split(const struct strbuf *sb, int delim)
{
int alloc = 2, pos = 0;
@@ -227,6 +229,12 @@ void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
break;
format = percent + 1;
+ if (*format == '%') {
+ strbuf_addch(sb, '%');
+ format++;
+ continue;
+ }
+
consumed = fn(sb, format, context);
if (consumed)
format += consumed;
@@ -251,6 +259,17 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder,
return 0;
}
+void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src)
+{
+ int i, len = src->len;
+
+ for (i = 0; i < len; i++) {
+ if (src->buf[i] == '%')
+ strbuf_addch(dst, '%');
+ strbuf_addch(dst, src->buf[i]);
+ }
+}
+
size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
{
size_t res;
diff --git a/strbuf.h b/strbuf.h
index fa07ecf094..fac2dbc24f 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -81,7 +81,6 @@ extern void strbuf_trim(struct strbuf *);
extern void strbuf_rtrim(struct strbuf *);
extern void strbuf_ltrim(struct strbuf *);
extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
-extern void strbuf_tolower(struct strbuf *);
extern struct strbuf **strbuf_split(const struct strbuf *, int delim);
extern void strbuf_list_free(struct strbuf **);
@@ -105,6 +104,7 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
strbuf_add(sb, s, strlen(s));
}
static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
+ strbuf_grow(sb, sb2->len);
strbuf_add(sb, sb2->buf, sb2->len);
}
extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
@@ -116,6 +116,7 @@ struct strbuf_expand_dict_entry {
const char *value;
};
extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context);
+extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
__attribute__((format (printf,2,3)))
extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
diff --git a/string-list.h b/string-list.h
index 14bbc477de..6569cf607b 100644
--- a/string-list.h
+++ b/string-list.h
@@ -1,5 +1,5 @@
-#ifndef PATH_LIST_H
-#define PATH_LIST_H
+#ifndef STRING_LIST_H
+#define STRING_LIST_H
struct string_list_item {
char *string;
@@ -39,4 +39,4 @@ struct string_list_item *string_list_append(const char *string, struct string_li
void sort_string_list(struct string_list *list);
int unsorted_string_list_has_string(struct string_list *list, const char *string);
-#endif /* PATH_LIST_H */
+#endif /* STRING_LIST_H */
diff --git a/submodule.c b/submodule.c
index 86aad653b7..f657bee379 100644
--- a/submodule.c
+++ b/submodule.c
@@ -4,8 +4,9 @@
#include "diff.h"
#include "commit.h"
#include "revision.h"
+#include "run-command.h"
-int add_submodule_odb(const char *path)
+static int add_submodule_odb(const char *path)
{
struct strbuf objects_directory = STRBUF_INIT;
struct alternate_object_database *alt_odb;
@@ -112,3 +113,51 @@ void show_submodule_summary(FILE *f, const char *path,
}
strbuf_release(&sb);
}
+
+int is_submodule_modified(const char *path)
+{
+ int len;
+ struct child_process cp;
+ const char *argv[] = {
+ "status",
+ "--porcelain",
+ NULL,
+ };
+ char *env[3];
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addf(&buf, "%s/.git/", path);
+ if (!is_directory(buf.buf)) {
+ strbuf_release(&buf);
+ /* The submodule is not checked out, so it is not modified */
+ return 0;
+
+ }
+ strbuf_reset(&buf);
+
+ strbuf_addf(&buf, "GIT_WORK_TREE=%s", path);
+ env[0] = strbuf_detach(&buf, NULL);
+ strbuf_addf(&buf, "GIT_DIR=%s/.git", path);
+ env[1] = strbuf_detach(&buf, NULL);
+ env[2] = NULL;
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.env = (const char *const *)env;
+ cp.git_cmd = 1;
+ cp.no_stdin = 1;
+ cp.out = -1;
+ if (start_command(&cp))
+ die("Could not run git status --porcelain");
+
+ len = strbuf_read(&buf, cp.out, 1024);
+ close(cp.out);
+
+ if (finish_command(&cp))
+ die("git status --porcelain failed");
+
+ free(env[0]);
+ free(env[1]);
+ strbuf_release(&buf);
+ return len != 0;
+}
diff --git a/submodule.h b/submodule.h
index 4c0269d679..0773121eb5 100644
--- a/submodule.h
+++ b/submodule.h
@@ -4,5 +4,6 @@
void show_submodule_summary(FILE *f, const char *path,
unsigned char one[20], unsigned char two[20],
const char *del, const char *add, const char *reset);
+int is_submodule_modified(const char *path);
#endif
diff --git a/symlinks.c b/symlinks.c
index 7b0a86d357..8860120011 100644
--- a/symlinks.c
+++ b/symlinks.c
@@ -179,37 +179,6 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
return ret_flags;
}
-/*
- * Invalidate the given 'name' from the cache, if 'name' matches
- * completely with the cache.
- */
-void invalidate_lstat_cache(const char *name, int len)
-{
- int match_len, previous_slash;
- struct cache_def *cache = &default_cache; /* FIXME */
-
- match_len = longest_path_match(name, len, cache->path, cache->len,
- &previous_slash);
- if (len == match_len) {
- if ((cache->track_flags & FL_DIR) && previous_slash > 0) {
- cache->path[previous_slash] = '\0';
- cache->len = previous_slash;
- cache->flags = FL_DIR;
- } else {
- reset_lstat_cache(cache);
- }
- }
-}
-
-/*
- * Completely clear the contents of the cache
- */
-void clear_lstat_cache(void)
-{
- struct cache_def *cache = &default_cache; /* FIXME */
- reset_lstat_cache(cache);
-}
-
#define USE_ONLY_LSTAT 0
/*
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
index 6765b08065..28aff887b5 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 0fe3fd0d01..4961505d1d 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/lib-rebase.sh b/t/lib-rebase.sh
index 62f452c8ea..6aefe27593 100644
--- a/t/lib-rebase.sh
+++ b/t/lib-rebase.sh
@@ -2,21 +2,33 @@
# After setting the fake editor with this function, you can
#
-# - override the commit message with $FAKE_COMMIT_MESSAGE,
+# - override the commit message with $FAKE_COMMIT_MESSAGE
# - amend the commit message with $FAKE_COMMIT_AMEND
# - check that non-commit messages have a certain line count with $EXPECT_COUNT
-# - rewrite a rebase -i script with $FAKE_LINES in the form
+# - check the commit count in the commit message header with $EXPECT_HEADER_COUNT
+# - rewrite a rebase -i script as directed by $FAKE_LINES.
+# $FAKE_LINES consists of a sequence of words separated by spaces.
+# The following word combinations are possible:
#
-# "[<lineno1>] [<lineno2>]..."
+# "<lineno>" -- add a "pick" line with the SHA1 taken from the
+# specified line.
#
-# If a line number is prefixed with "squash", "edit", or "reword", the
-# respective line's command will be replaced with the specified one.
+# "<cmd> <lineno>" -- add a line with the specified command
+# ("squash", "fixup", "edit", or "reword") and the SHA1 taken
+# from the specified line.
+#
+# "#" -- Add a comment line.
+#
+# ">" -- Add a blank line.
set_fake_editor () {
echo "#!$SHELL_PATH" >fake-editor.sh
cat >> fake-editor.sh <<\EOF
case "$1" in
*/COMMIT_EDITMSG)
+ test -z "$EXPECT_HEADER_COUNT" ||
+ test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1")" ||
+ exit
test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
exit
@@ -28,19 +40,24 @@ test -z "$EXPECT_COUNT" ||
test -z "$FAKE_LINES" && exit
grep -v '^#' < "$1" > "$1".tmp
rm -f "$1"
+echo 'rebase -i script before editing:'
cat "$1".tmp
action=pick
for line in $FAKE_LINES; do
case $line in
- squash|edit|reword)
+ squash|fixup|edit|reword)
action="$line";;
+ "#")
+ echo '# comment' >> "$1";;
+ ">")
+ echo >> "$1";;
*)
- echo sed -n "${line}s/^pick/$action/p"
- sed -n "${line}p" < "$1".tmp
sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
action=pick;;
esac
done
+echo 'rebase -i script after editing:'
+cat "$1"
EOF
test_set_editor "$(pwd)/fake-editor.sh"
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index 8fc39d77ce..6cb8d60ea2 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/t0061-run-command.sh b/t/t0061-run-command.sh
new file mode 100755
index 0000000000..10b26e4d8e
--- /dev/null
+++ b/t/t0061-run-command.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Ilari Liusvaara
+#
+
+test_description='Test run command'
+
+. ./test-lib.sh
+
+test_expect_success 'start_command reports ENOENT' '
+ test-run-command start-command-ENOENT ./does-not-exist
+'
+
+test_done
diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
new file mode 100755
index 0000000000..62246dbf95
--- /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/t1300-repo-config.sh b/t/t1300-repo-config.sh
index 83b7294010..f89d7e9e49 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
@@ -683,6 +683,34 @@ test_expect_success 'set --bool-or-int' '
rm .git/config
+cat >expect <<\EOF
+[path]
+ home = ~/
+ normal = /dev/null
+ trailingtilde = foo~
+EOF
+
+test_expect_success 'set --path' '
+ git config --path path.home "~/" &&
+ git config --path path.normal "/dev/null" &&
+ git config --path path.trailingtilde "foo~" &&
+ test_cmp expect .git/config'
+
+cat >expect <<EOF
+$HOME/
+/dev/null
+foo~
+EOF
+
+test_expect_success 'get --path' '
+ git config --get --path path.home > result &&
+ git config --get --path path.normal >> result &&
+ git config --get --path path.trailingtilde >> result &&
+ test_cmp expect result
+'
+
+rm .git/config
+
git config quote.leading " test"
git config quote.ending "test "
git config quote.semicolon "test;test"
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
index 74e6443664..9df301211c 100755
--- a/t/t1501-worktree.sh
+++ b/t/t1501-worktree.sh
@@ -189,4 +189,10 @@ test_expect_success 'absolute pathspec should fail gracefully' '
)
'
+test_expect_success 'make_relative_path handles double slashes in GIT_DIR' '
+ : > dummy_file
+ echo git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file &&
+ git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file
+'
+
test_done
diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh
new file mode 100755
index 0000000000..af721f9719
--- /dev/null
+++ b/t/t1506-rev-parse-diagnosis.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='test git rev-parse diagnosis for invalid argument'
+
+exec </dev/null
+
+. ./test-lib.sh
+
+HASH_file=
+
+test_expect_success 'set up basic repo' '
+ echo one > file.txt &&
+ mkdir subdir &&
+ echo two > subdir/file.txt &&
+ echo three > subdir/file2.txt &&
+ git add . &&
+ git commit -m init &&
+ echo four > index-only.txt &&
+ git add index-only.txt &&
+ echo five > disk-only.txt
+'
+
+test_expect_success 'correct file objects' '
+ HASH_file=$(git rev-parse HEAD:file.txt) &&
+ git rev-parse HEAD:subdir/file.txt &&
+ git rev-parse :index-only.txt &&
+ (cd subdir &&
+ git rev-parse HEAD:subdir/file2.txt &&
+ test $HASH_file = $(git rev-parse HEAD:file.txt) &&
+ test $HASH_file = $(git rev-parse :file.txt) &&
+ test $HASH_file = $(git rev-parse :0:file.txt) )
+'
+
+test_expect_success 'incorrect revision id' '
+ test_must_fail git rev-parse foobar:file.txt 2>error &&
+ grep "Invalid object name '"'"'foobar'"'"'." error &&
+ test_must_fail git rev-parse foobar 2> error &&
+ grep "unknown revision or path not in the working tree." error
+'
+
+test_expect_success 'incorrect file in sha1:path' '
+ test_must_fail git rev-parse HEAD:nothing.txt 2> error &&
+ grep "fatal: Path '"'"'nothing.txt'"'"' does not exist in '"'"'HEAD'"'"'" error &&
+ test_must_fail git rev-parse HEAD:index-only.txt 2> error &&
+ grep "fatal: Path '"'"'index-only.txt'"'"' exists on disk, but not in '"'"'HEAD'"'"'." error &&
+ (cd subdir &&
+ test_must_fail git rev-parse HEAD:file2.txt 2> error &&
+ grep "Did you mean '"'"'HEAD:subdir/file2.txt'"'"'?" error )
+'
+
+test_expect_success 'incorrect file in :path and :N:path' '
+ test_must_fail git rev-parse :nothing.txt 2> error &&
+ grep "fatal: Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
+ test_must_fail git rev-parse :1:nothing.txt 2> error &&
+ grep "Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
+ test_must_fail git rev-parse :1:file.txt 2> error &&
+ grep "Did you mean '"'"':0:file.txt'"'"'?" error &&
+ (cd subdir &&
+ test_must_fail git rev-parse :1:file.txt 2> error &&
+ grep "Did you mean '"'"':0:file.txt'"'"'?" error &&
+ test_must_fail git rev-parse :file2.txt 2> error &&
+ grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error &&
+ test_must_fail git rev-parse :2:file2.txt 2> error &&
+ grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error) &&
+ test_must_fail git rev-parse :disk-only.txt 2> error &&
+ grep "fatal: Path '"'"'disk-only.txt'"'"' exists on disk, but not in the index." error
+'
+
+test_done
diff --git a/t/t1506-rev-parse-upstream.sh b/t/t1506-rev-parse-upstream.sh
new file mode 100755
index 0000000000..95c9b0923f
--- /dev/null
+++ b/t/t1506-rev-parse-upstream.sh
@@ -0,0 +1,110 @@
+#!/bin/sh
+
+test_description='test <branch>@{upstream} syntax'
+
+. ./test-lib.sh
+
+
+test_expect_success 'setup' '
+
+ test_commit 1 &&
+ git checkout -b side &&
+ test_commit 2 &&
+ git checkout master &&
+ git clone . clone &&
+ test_commit 3 &&
+ (cd clone &&
+ test_commit 4 &&
+ git branch --track my-side origin/side)
+
+'
+
+full_name () {
+ (cd clone &&
+ git rev-parse --symbolic-full-name "$@")
+}
+
+commit_subject () {
+ (cd clone &&
+ git show -s --pretty=format:%s "$@")
+}
+
+test_expect_success '@{upstream} resolves to correct full name' '
+ test refs/remotes/origin/master = "$(full_name @{upstream})"
+'
+
+test_expect_success '@{u} resolves to correct full name' '
+ test refs/remotes/origin/master = "$(full_name @{u})"
+'
+
+test_expect_success 'my-side@{upstream} resolves to correct full name' '
+ test refs/remotes/origin/side = "$(full_name my-side@{u})"
+'
+
+test_expect_success 'my-side@{u} resolves to correct commit' '
+ git checkout side &&
+ test_commit 5 &&
+ (cd clone && git fetch) &&
+ test 2 = "$(commit_subject my-side)" &&
+ test 5 = "$(commit_subject my-side@{u})"
+'
+
+test_expect_success 'not-tracking@{u} fails' '
+ test_must_fail full_name non-tracking@{u} &&
+ (cd clone && git checkout --no-track -b non-tracking) &&
+ test_must_fail full_name non-tracking@{u}
+'
+
+test_expect_success '<branch>@{u}@{1} resolves correctly' '
+ test_commit 6 &&
+ (cd clone && git fetch) &&
+ test 5 = $(commit_subject my-side@{u}@{1})
+'
+
+test_expect_success '@{u} without specifying branch fails on a detached HEAD' '
+ git checkout HEAD^0 &&
+ test_must_fail git rev-parse @{u}
+'
+
+test_expect_success 'checkout -b new my-side@{u} forks from the same' '
+(
+ cd clone &&
+ git checkout -b new my-side@{u} &&
+ git rev-parse --symbolic-full-name my-side@{u} >expect &&
+ git rev-parse --symbolic-full-name new@{u} >actual &&
+ test_cmp expect actual
+)
+'
+
+test_expect_success 'merge my-side@{u} records the correct name' '
+(
+ sq="'\''" &&
+ cd clone || exit
+ git checkout master || exit
+ git branch -D new ;# can fail but is ok
+ git branch -t new my-side@{u} &&
+ git merge -s ours new@{u} &&
+ git show -s --pretty=format:%s >actual &&
+ echo "Merge remote branch ${sq}origin/side${sq}" >expect &&
+ test_cmp expect actual
+)
+'
+
+test_expect_success 'branch -d other@{u}' '
+ git checkout -t -b other master &&
+ git branch -d @{u} &&
+ git for-each-ref refs/heads/master >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'checkout other@{u}' '
+ git branch -f master HEAD &&
+ git checkout -t -b another master &&
+ git checkout @{u} &&
+ git symbolic-ref HEAD >actual &&
+ echo refs/heads/master >expect &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
index 87b30a268c..b44de9dc62 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/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh
new file mode 100755
index 0000000000..cb7effe0a3
--- /dev/null
+++ b/t/t2030-unresolve-info.sh
@@ -0,0 +1,170 @@
+#!/bin/sh
+
+test_description='undoing resolution'
+
+. ./test-lib.sh
+
+check_resolve_undo () {
+ msg=$1
+ shift
+ while case $# in
+ 0) break ;;
+ 1|2|3) die "Bug in check-resolve-undo test" ;;
+ esac
+ do
+ path=$1
+ shift
+ for stage in 1 2 3
+ do
+ sha1=$1
+ shift
+ case "$sha1" in
+ '') continue ;;
+ esac
+ sha1=$(git rev-parse --verify "$sha1")
+ printf "100644 %s %s\t%s\n" $sha1 $stage $path
+ done
+ done >"$msg.expect" &&
+ git ls-files --resolve-undo >"$msg.actual" &&
+ test_cmp "$msg.expect" "$msg.actual"
+}
+
+prime_resolve_undo () {
+ git reset --hard &&
+ git checkout second^0 &&
+ test_tick &&
+ test_must_fail git merge third^0 &&
+ echo merge does not leave anything &&
+ check_resolve_undo empty &&
+ echo different >fi/le &&
+ git add fi/le &&
+ echo resolving records &&
+ check_resolve_undo recorded fi/le initial:fi/le second:fi/le third:fi/le
+}
+
+test_expect_success setup '
+ mkdir fi &&
+ test_commit initial fi/le first &&
+ git branch side &&
+ git branch another &&
+ test_commit second fi/le second &&
+ git checkout side &&
+ test_commit third fi/le third &&
+ git checkout another &&
+ test_commit fourth fi/le fourth &&
+ git checkout master
+'
+
+test_expect_success 'add records switch clears' '
+ prime_resolve_undo &&
+ test_tick &&
+ git commit -m merged &&
+ echo committing keeps &&
+ check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+ git checkout second^0 &&
+ echo switching clears &&
+ check_resolve_undo cleared
+'
+
+test_expect_success 'rm records reset clears' '
+ prime_resolve_undo &&
+ test_tick &&
+ git commit -m merged &&
+ echo committing keeps &&
+ check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+
+ echo merge clears upfront &&
+ test_must_fail git merge fourth^0 &&
+ check_resolve_undo nuked &&
+
+ git rm -f fi/le &&
+ echo resolving records &&
+ check_resolve_undo recorded fi/le initial:fi/le HEAD:fi/le fourth:fi/le &&
+
+ git reset --hard &&
+ echo resetting discards &&
+ check_resolve_undo discarded
+'
+
+test_expect_success 'plumbing clears' '
+ prime_resolve_undo &&
+ test_tick &&
+ git commit -m merged &&
+ echo committing keeps &&
+ check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+
+ echo plumbing clear &&
+ git update-index --clear-resolve-undo &&
+ check_resolve_undo cleared
+'
+
+test_expect_success 'add records checkout -m undoes' '
+ prime_resolve_undo &&
+ git diff HEAD &&
+ git checkout --conflict=merge fi/le &&
+ echo checkout used the record and removed it &&
+ check_resolve_undo removed &&
+ echo the index and the work tree is unmerged again &&
+ git diff >actual &&
+ grep "^++<<<<<<<" actual
+'
+
+test_expect_success 'unmerge with plumbing' '
+ prime_resolve_undo &&
+ git update-index --unresolve fi/le &&
+ git ls-files -u >actual &&
+ test $(wc -l <actual) = 3
+'
+
+test_expect_success 'rerere and rerere forget' '
+ mkdir .git/rr-cache &&
+ prime_resolve_undo &&
+ echo record the resolution &&
+ git rerere &&
+ rerere_id=$(cd .git/rr-cache && echo */postimage) &&
+ rerere_id=${rerere_id%/postimage} &&
+ test -f .git/rr-cache/$rerere_id/postimage &&
+ git checkout -m fi/le &&
+ echo resurrect the conflict &&
+ grep "^=======" fi/le &&
+ echo reresolve the conflict &&
+ git rerere &&
+ test "z$(cat fi/le)" = zdifferent &&
+ echo register the resolution again &&
+ git add fi/le &&
+ check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+ test -z "$(git ls-files -u)" &&
+ git rerere forget fi/le &&
+ ! test -f .git/rr-cache/$rerere_id/postimage &&
+ tr "\0" "\n" <.git/MERGE_RR >actual &&
+ echo "$rerere_id fi/le" >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rerere and rerere forget (subdirectory)' '
+ rm -fr .git/rr-cache &&
+ mkdir .git/rr-cache &&
+ prime_resolve_undo &&
+ echo record the resolution &&
+ (cd fi && git rerere) &&
+ rerere_id=$(cd .git/rr-cache && echo */postimage) &&
+ rerere_id=${rerere_id%/postimage} &&
+ test -f .git/rr-cache/$rerere_id/postimage &&
+ (cd fi && git checkout -m le) &&
+ echo resurrect the conflict &&
+ grep "^=======" fi/le &&
+ echo reresolve the conflict &&
+ (cd fi && git rerere) &&
+ test "z$(cat fi/le)" = zdifferent &&
+ echo register the resolution again &&
+ (cd fi && git add le) &&
+ check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+ test -z "$(git ls-files -u)" &&
+ (cd fi && git rerere forget le) &&
+ ! test -f .git/rr-cache/$rerere_id/postimage &&
+ tr "\0" "\n" <.git/MERGE_RR >actual &&
+ echo "$rerere_id fi/le" >expect &&
+ test_cmp expect actual
+'
+
+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 0000000000..1d0879be06
--- /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/t2105-update-index-gitfile.sh b/t/t2105-update-index-gitfile.sh
new file mode 100755
index 0000000000..641607d89a
--- /dev/null
+++ b/t/t2105-update-index-gitfile.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Brad King
+#
+
+test_description='git update-index for gitlink to .git file.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'submodule with absolute .git file' '
+ mkdir sub1 &&
+ (cd sub1 &&
+ git init &&
+ REAL="$(pwd)/.real" &&
+ mv .git "$REAL"
+ echo "gitdir: $REAL" >.git &&
+ test_commit first)
+'
+
+test_expect_success 'add gitlink to absolute .git file' '
+ git update-index --add -- sub1
+'
+
+test_expect_success 'submodule with relative .git file' '
+ mkdir sub2 &&
+ (cd sub2 &&
+ git init &&
+ mv .git .real &&
+ echo "gitdir: .real" >.git &&
+ test_commit first)
+'
+
+test_expect_success 'add gitlink to relative .git file' '
+ git update-index --add -- sub2
+'
+
+test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index c65bca8388..6d2f2b67ee 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*
@@ -153,4 +175,43 @@ test_expect_success 'negated exclude matches can override previous ones' '
grep "^a.1" output
'
+test_expect_success 'subdirectory ignore (setup)' '
+ mkdir -p top/l1/l2 &&
+ (
+ cd top &&
+ git init &&
+ echo /.gitignore >.gitignore &&
+ echo l1 >>.gitignore &&
+ echo l2 >l1/.gitignore &&
+ >l1/l2/l1
+ )
+'
+
+test_expect_success 'subdirectory ignore (toplevel)' '
+ (
+ cd top &&
+ git ls-files -o --exclude-standard
+ ) >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'subdirectory ignore (l1/l2)' '
+ (
+ cd top/l1/l2 &&
+ git ls-files -o --exclude-standard
+ ) >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'subdirectory ignore (l1)' '
+ (
+ cd top/l1 &&
+ git ls-files -o --exclude-standard
+ ) >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh
index 9b3fa2bdcd..9929f82021 100755
--- a/t/t3030-merge-recursive.sh
+++ b/t/t3030-merge-recursive.sh
@@ -276,11 +276,13 @@ test_expect_success 'fail if the index has unresolved entries' '
test_must_fail git merge "$c5" &&
test_must_fail git merge "$c5" 2> out &&
+ grep "not possible because you have unmerged files" out &&
+ git add -u &&
+ test_must_fail git merge "$c5" 2> out &&
grep "You have not concluded your merge" out &&
rm -f .git/MERGE_HEAD &&
test_must_fail git merge "$c5" 2> out &&
- grep "You are in the middle of a conflicted merge" out
-
+ grep "Your local changes to .* would be overwritten by merge." out
'
test_expect_success 'merge-recursive remove conflict' '
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index d59a9b4aef..e0b760513c 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -468,4 +468,30 @@ test_expect_success 'detect misconfigured autosetuprebase (no value)' '
git config --unset branch.autosetuprebase
'
+test_expect_success 'attempt to delete a branch without base and unmerged to HEAD' '
+ git checkout my9 &&
+ git config --unset branch.my8.merge &&
+ test_must_fail git branch -d my8
+'
+
+test_expect_success 'attempt to delete a branch merged to its base' '
+ # we are on my9 which is the initial commit; traditionally
+ # we would not have allowed deleting my8 that is not merged
+ # to my9, but it is set to track master that already has my8
+ git config branch.my8.merge refs/heads/master &&
+ git branch -d my8
+'
+
+test_expect_success 'attempt to delete a branch merged to its base' '
+ git checkout master &&
+ echo Third >>A &&
+ git commit -m "Third commit" A &&
+ git branch -t my10 my9 &&
+ git branch -f my10 HEAD^ &&
+ # we are on master which is at the third commit, and my10
+ # is behind us, so traditionally we would have allowed deleting
+ # it; but my10 is set to track my9 that is further behind.
+ test_must_fail git branch -d my10
+'
+
test_done
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 1e34f4836f..5d9604b815 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -147,4 +147,63 @@ test_expect_success 'show -m and -F notes' '
test_cmp expect-m-and-F output
'
+cat >expect << EOF
+commit 15023535574ded8b1a89052b32673f84cf9582b8
+tree e070e3af51011e47b183c33adf9736736a525709
+parent 1584215f1d29c65e99c6c6848626553fdd07fd75
+author A U Thor <author@example.com> 1112912173 -0700
+committer C O Mitter <committer@example.com> 1112912173 -0700
+
+ 4th
+EOF
+test_expect_success 'git log --pretty=raw does not show notes' '
+ git log -1 --pretty=raw >output &&
+ test_cmp expect output
+'
+
+cat >>expect <<EOF
+
+Notes:
+ spam
+$whitespace
+ xyzzy
+$whitespace
+ foo
+ bar
+ baz
+EOF
+test_expect_success 'git log --show-notes' '
+ git log -1 --pretty=raw --show-notes >output &&
+ test_cmp expect output
+'
+
+test_expect_success 'git log --no-notes' '
+ git log -1 --no-notes >output &&
+ ! grep spam output
+'
+
+test_expect_success 'git format-patch does not show notes' '
+ git format-patch -1 --stdout >output &&
+ ! grep spam output
+'
+
+test_expect_success 'git format-patch --show-notes does show notes' '
+ git format-patch --show-notes -1 --stdout >output &&
+ grep spam output
+'
+
+for pretty in \
+ "" --pretty --pretty=raw --pretty=short --pretty=medium \
+ --pretty=full --pretty=fuller --pretty=format:%s --oneline
+do
+ case "$pretty" in
+ "") p= not= negate="" ;;
+ ?*) p="$pretty" not=" not" negate="!" ;;
+ esac
+ test_expect_success "git show $pretty does$not show notes" '
+ git show $p >output &&
+ eval "$negate grep spam output"
+ '
+done
+
test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 3a37793c0d..4e3513709e 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -14,58 +14,47 @@ that the result still makes sense.
set_fake_editor
-# set up two branches like this:
+# Set up the repository like this:
#
-# A - B - C - D - E
+# one - two - three - four (conflict-branch)
+# /
+# A - B - C - D - E (master)
+# | \
+# | F - G - H (branch1)
+# | \
+# \ I (branch2)
# \
-# F - G - H
-# \
-# I
+# J - K - L - M (no-conflict-branch)
#
-# where B, D and G touch the same file.
+# where A, B, D and G all touch file1, and one, two, three, four all
+# touch file "conflict".
test_expect_success 'setup' '
- : > file1 &&
- git add file1 &&
- test_tick &&
- git commit -m A &&
- git tag A &&
- echo 1 > file1 &&
- test_tick &&
- git commit -m B file1 &&
- : > file2 &&
- git add file2 &&
- test_tick &&
- git commit -m C &&
- echo 2 > file1 &&
- test_tick &&
- git commit -m D file1 &&
- : > file3 &&
- git add file3 &&
- test_tick &&
- git commit -m E &&
+ test_commit A file1 &&
+ test_commit B file1 &&
+ test_commit C file2 &&
+ test_commit D file1 &&
+ test_commit E file3 &&
git checkout -b branch1 A &&
- : > file4 &&
- git add file4 &&
- test_tick &&
- git commit -m F &&
- git tag F &&
- echo 3 > file1 &&
- test_tick &&
- git commit -m G file1 &&
- : > file5 &&
- git add file5 &&
- test_tick &&
- git commit -m H &&
+ test_commit F file4 &&
+ test_commit G file1 &&
+ test_commit H file5 &&
git checkout -b branch2 F &&
- : > file6 &&
- git add file6 &&
- test_tick &&
- git commit -m I &&
- git tag I
+ test_commit I file6
+ git checkout -b conflict-branch A &&
+ for n in one two three four
+ do
+ test_commit $n conflict
+ done &&
+ git checkout -b no-conflict-branch A &&
+ for n in J K L M
+ do
+ test_commit $n file$n
+ done
'
test_expect_success 'no changes are a nop' '
+ git checkout branch2 &&
git rebase -i F &&
test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
test $(git rev-parse I) = $(git rev-parse HEAD)
@@ -111,19 +100,20 @@ test_expect_success 'exchange two commits' '
cat > expect << EOF
diff --git a/file1 b/file1
-index e69de29..00750ed 100644
+index f70f10e..fd79235 100644
--- a/file1
+++ b/file1
-@@ -0,0 +1 @@
-+3
+@@ -1 +1 @@
+-A
++G
EOF
cat > expect2 << EOF
<<<<<<< HEAD
-2
+D
=======
-3
->>>>>>> b7ca976... G
+G
+>>>>>>> 51047de... G
EOF
test_expect_success 'stop on conflicting pick' '
@@ -161,7 +151,8 @@ test_expect_success 'squash' '
test_tick &&
GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 &&
echo "******************************" &&
- FAKE_LINES="1 squash 2" git rebase -i --onto master HEAD~2 &&
+ FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \
+ git rebase -i --onto master HEAD~2 &&
test B = $(cat file7) &&
test $(git rev-parse HEAD^) = $(git rev-parse master)
'
@@ -256,30 +247,113 @@ test_expect_success 'verbose flag is heeded, even after --continue' '
test_expect_success 'multi-squash only fires up editor once' '
base=$(git rev-parse HEAD~4) &&
FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \
+ EXPECT_HEADER_COUNT=4 \
git rebase -i $base &&
test $base = $(git rev-parse HEAD^) &&
test 1 = $(git show | grep ONCE | wc -l)
'
+test_expect_success 'multi-fixup does not fire up editor' '
+ git checkout -b multi-fixup E &&
+ base=$(git rev-parse HEAD~4) &&
+ FAKE_COMMIT_AMEND="NEVER" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \
+ git rebase -i $base &&
+ test $base = $(git rev-parse HEAD^) &&
+ test 0 = $(git show | grep NEVER | wc -l) &&
+ git checkout to-be-rebased &&
+ git branch -D multi-fixup
+'
+
+test_expect_success 'commit message used after conflict' '
+ git checkout -b conflict-fixup conflict-branch &&
+ base=$(git rev-parse HEAD~4) &&
+ (
+ FAKE_LINES="1 fixup 3 fixup 4" &&
+ export FAKE_LINES &&
+ test_must_fail git rebase -i $base
+ ) &&
+ echo three > conflict &&
+ git add conflict &&
+ FAKE_COMMIT_AMEND="ONCE" EXPECT_HEADER_COUNT=2 \
+ git rebase --continue &&
+ test $base = $(git rev-parse HEAD^) &&
+ test 1 = $(git show | grep ONCE | wc -l) &&
+ git checkout to-be-rebased &&
+ git branch -D conflict-fixup
+'
+
+test_expect_success 'commit message retained after conflict' '
+ git checkout -b conflict-squash conflict-branch &&
+ base=$(git rev-parse HEAD~4) &&
+ (
+ FAKE_LINES="1 fixup 3 squash 4" &&
+ export FAKE_LINES &&
+ test_must_fail git rebase -i $base
+ ) &&
+ echo three > conflict &&
+ git add conflict &&
+ FAKE_COMMIT_AMEND="TWICE" EXPECT_HEADER_COUNT=2 \
+ git rebase --continue &&
+ test $base = $(git rev-parse HEAD^) &&
+ test 2 = $(git show | grep TWICE | wc -l) &&
+ git checkout to-be-rebased &&
+ git branch -D conflict-squash
+'
+
+cat > expect-squash-fixup << EOF
+B
+
+D
+
+ONCE
+EOF
+
+test_expect_success 'squash and fixup generate correct log messages' '
+ git checkout -b squash-fixup E &&
+ base=$(git rev-parse HEAD~4) &&
+ FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 squash 3 fixup 4" \
+ EXPECT_HEADER_COUNT=4 \
+ git rebase -i $base &&
+ git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup &&
+ test_cmp expect-squash-fixup actual-squash-fixup &&
+ git checkout to-be-rebased &&
+ git branch -D squash-fixup
+'
+
+test_expect_success 'squash ignores comments' '
+ git checkout -b skip-comments E &&
+ base=$(git rev-parse HEAD~4) &&
+ FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="# 1 # squash 2 # squash 3 # squash 4 #" \
+ EXPECT_HEADER_COUNT=4 \
+ git rebase -i $base &&
+ test $base = $(git rev-parse HEAD^) &&
+ test 1 = $(git show | grep ONCE | wc -l) &&
+ git checkout to-be-rebased &&
+ git branch -D skip-comments
+'
+
+test_expect_success 'squash ignores blank lines' '
+ git checkout -b skip-blank-lines E &&
+ base=$(git rev-parse HEAD~4) &&
+ FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="> 1 > squash 2 > squash 3 > squash 4 >" \
+ EXPECT_HEADER_COUNT=4 \
+ git rebase -i $base &&
+ test $base = $(git rev-parse HEAD^) &&
+ test 1 = $(git show | grep ONCE | wc -l) &&
+ git checkout to-be-rebased &&
+ git branch -D skip-blank-lines
+'
+
test_expect_success 'squash works as expected' '
- for n in one two three four
- do
- echo $n >> file$n &&
- git add file$n &&
- git commit -m $n
- done &&
+ git checkout -b squash-works no-conflict-branch &&
one=$(git rev-parse HEAD~3) &&
- FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 &&
+ FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=2 \
+ git rebase -i HEAD~3 &&
test $one = $(git rev-parse HEAD~2)
'
test_expect_success 'interrupted squash works as expected' '
- for n in one two three four
- do
- echo $n >> conflict &&
- git add conflict &&
- git commit -m $n
- done &&
+ git checkout -b interrupted-squash conflict-branch &&
one=$(git rev-parse HEAD~3) &&
(
FAKE_LINES="1 squash 3 2" &&
@@ -296,12 +370,7 @@ test_expect_success 'interrupted squash works as expected' '
'
test_expect_success 'interrupted squash works as expected (case 2)' '
- for n in one two three four
- do
- echo $n >> conflict &&
- git add conflict &&
- git commit -m $n
- done &&
+ git checkout -b interrupted-squash2 conflict-branch &&
one=$(git rev-parse HEAD~3) &&
(
FAKE_LINES="3 squash 1 2" &&
diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh
new file mode 100755
index 0000000000..b63f4e2d67
--- /dev/null
+++ b/t/t3415-rebase-autosquash.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+test_description='auto squash'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ echo 0 >file0 &&
+ git add . &&
+ test_tick &&
+ git commit -m "initial commit" &&
+ echo 0 >file1 &&
+ echo 2 >file2 &&
+ git add . &&
+ test_tick &&
+ git commit -m "first commit" &&
+ echo 3 >file3 &&
+ git add . &&
+ test_tick &&
+ git commit -m "second commit" &&
+ git tag base
+'
+
+test_expect_success 'auto fixup' '
+ git reset --hard base &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "fixup! first"
+
+ git tag final-fixup &&
+ test_tick &&
+ git rebase --autosquash -i HEAD^^^ &&
+ git log --oneline >actual &&
+ test 3 = $(wc -l <actual) &&
+ git diff --exit-code final-fixup &&
+ test 1 = "$(git cat-file blob HEAD^:file1)" &&
+ test 1 = $(git cat-file commit HEAD^ | grep first | wc -l)
+'
+
+test_expect_success 'auto squash' '
+ git reset --hard base &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "squash! first"
+
+ git tag final-squash &&
+ test_tick &&
+ git rebase --autosquash -i HEAD^^^ &&
+ git log --oneline >actual &&
+ test 3 = $(wc -l <actual) &&
+ git diff --exit-code final-squash &&
+ test 1 = "$(git cat-file blob HEAD^:file1)" &&
+ test 2 = $(git cat-file commit HEAD^ | grep first | wc -l)
+'
+
+test_expect_success 'misspelled auto squash' '
+ git reset --hard base &&
+ echo 1 >file1 &&
+ git add -u &&
+ test_tick &&
+ git commit -m "squash! forst"
+ git tag final-missquash &&
+ test_tick &&
+ git rebase --autosquash -i HEAD^^^ &&
+ git log --oneline >actual &&
+ test 4 = $(wc -l <actual) &&
+ git diff --exit-code final-missquash &&
+ test 0 = $(git rev-list final-missquash...HEAD | wc -l)
+'
+
+test_done
diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh
new file mode 100755
index 0000000000..ddf2f64853
--- /dev/null
+++ b/t/t3416-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/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index bb4cf00d78..7f858151d4 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh
@@ -66,7 +66,7 @@ test_expect_success 'revert forbidden on dirty working tree' '
echo content >extra_file &&
git add extra_file &&
test_must_fail git revert HEAD 2>errors &&
- grep "Dirty index" errors
+ grep "Your local changes would be overwritten by " errors
'
diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh
index 5cf8924b21..83c1914771 100755
--- a/t/t4027-diff-submodule.sh
+++ b/t/t4027-diff-submodule.sh
@@ -32,7 +32,8 @@ test_expect_success setup '
cd sub &&
git rev-list HEAD
) &&
- echo ":160000 160000 $3 $_z40 M sub" >expect
+ echo ":160000 160000 $3 $_z40 M sub" >expect &&
+ subtip=$3 subprev=$2
'
test_expect_success 'git diff --raw HEAD' '
@@ -50,6 +51,87 @@ test_expect_success 'git diff-files --raw' '
test_cmp expect actual.files
'
+expect_from_to () {
+ printf "%sSubproject commit %s\n+Subproject commit %s\n" \
+ "-" "$1" "$2"
+}
+
+test_expect_success 'git diff HEAD' '
+ git diff HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subtip $subprev &&
+ test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (work tree)' '
+ echo >>sub/world &&
+ git diff HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subtip $subprev-dirty &&
+ test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (index)' '
+ (
+ cd sub &&
+ git reset --hard &&
+ echo >>world &&
+ git add world
+ ) &&
+ git diff HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subtip $subprev-dirty &&
+ test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (untracked)' '
+ (
+ cd sub &&
+ git reset --hard &&
+ git clean -qfdx &&
+ >cruft
+ ) &&
+ git diff HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subtip $subprev-dirty &&
+ test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' '
+ git commit -m "x" sub &&
+ echo >>sub/world &&
+ git diff HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subprev $subprev-dirty &&
+ test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (index, refs match)' '
+ (
+ cd sub &&
+ git reset --hard &&
+ echo >>world &&
+ git add world
+ ) &&
+ git diff HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subprev $subprev-dirty &&
+ test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' '
+ (
+ cd sub &&
+ git reset --hard &&
+ git clean -qfdx &&
+ >cruft
+ ) &&
+ git diff HEAD >actual &&
+ sed -e "1,/^@@/d" actual >actual.body &&
+ expect_from_to >expect.body $subprev $subprev-dirty &&
+ test_cmp expect.body actual.body
+'
+
test_expect_success 'git diff (empty submodule dir)' '
: >empty &&
rm -rf sub/* sub/.git &&
diff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh
index a3f0897a52..88c5619ae7 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 a894c60622..7e7b307a24 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/t4120-apply-popt.sh b/t/t4120-apply-popt.sh
index 83d4ba6798..b463b4f05c 100755
--- a/t/t4120-apply-popt.sh
+++ b/t/t4120-apply-popt.sh
@@ -22,4 +22,9 @@ test_expect_success 'apply git diff with -p2' '
git apply -p2 patch.file
'
+test_expect_success 'apply with too large -p' '
+ test_must_fail git apply --stat -p3 patch.file 2>err &&
+ grep "removing 3 leading" err
+'
+
test_done
diff --git a/t/t4133-apply-filenames.sh b/t/t4133-apply-filenames.sh
new file mode 100755
index 0000000000..34218071b6
--- /dev/null
+++ b/t/t4133-apply-filenames.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Andreas Gruenbacher
+#
+
+test_description='git apply filename consistency check'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ cat > bad1.patch <<EOF
+diff --git a/f b/f
+new file mode 100644
+index 0000000..d00491f
+--- /dev/null
++++ b/f-blah
+@@ -0,0 +1 @@
++1
+EOF
+ cat > bad2.patch <<EOF
+diff --git a/f b/f
+deleted file mode 100644
+index d00491f..0000000
+--- b/f-blah
++++ /dev/null
+@@ -1 +0,0 @@
+-1
+EOF
+'
+
+test_expect_success 'apply diff with inconsistent filenames in headers' '
+ test_must_fail git apply bad1.patch 2>err
+ grep "inconsistent new filename" err
+ test_must_fail git apply bad2.patch 2>err
+ grep "inconsistent old filename" err
+'
+
+test_done
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index a6bc028a57..bb402c3780 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/t5505-remote.sh b/t/t5505-remote.sh
index fd166d9de3..a82c5ffa1c 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -419,6 +419,20 @@ test_expect_success 'update default (overridden, with funny whitespace)' '
'
+test_expect_success 'update (with remotes.default defined)' '
+
+ (cd one &&
+ for b in $(git branch -r)
+ do
+ git branch -r -d $b || break
+ done &&
+ git config remotes.default "drosophila" &&
+ git remote update &&
+ git branch -r > output &&
+ test_cmp expect output)
+
+'
+
test_expect_success '"remote show" does not show symbolic refs' '
git clone one three &&
@@ -519,5 +533,219 @@ test_expect_success 'show empty remote' '
)
'
-test_done
+test_expect_success 'new remote' '
+(
+ git remote add someremote foo &&
+ echo foo >expect &&
+ git config --get-all remote.someremote.url >actual &&
+ cmp expect actual
+)
+'
+test_expect_success 'remote set-url bar' '
+(
+ git remote set-url someremote bar &&
+ echo bar >expect &&
+ git config --get-all remote.someremote.url >actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url baz bar' '
+(
+ git remote set-url someremote baz bar &&
+ echo baz >expect &&
+ git config --get-all remote.someremote.url >actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url zot bar' '
+(
+ test_must_fail git remote set-url someremote zot bar &&
+ echo baz >expect &&
+ git config --get-all remote.someremote.url >actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push zot baz' '
+(
+ test_must_fail git remote set-url --push someremote zot baz &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push zot' '
+(
+ git remote set-url --push someremote zot &&
+ echo zot >expect &&
+ echo "YYY" >>expect &&
+ echo baz >>expect &&
+ git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push qux zot' '
+(
+ git remote set-url --push someremote qux zot &&
+ echo qux >expect &&
+ echo "YYY" >>expect &&
+ echo baz >>expect &&
+ git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push foo qu+x' '
+(
+ git remote set-url --push someremote foo qu+x &&
+ echo foo >expect &&
+ echo "YYY" >>expect &&
+ echo baz >>expect &&
+ git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push --add aaa' '
+(
+ git remote set-url --push --add someremote aaa &&
+ echo foo >expect &&
+ echo aaa >>expect &&
+ echo "YYY" >>expect &&
+ echo baz >>expect &&
+ git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push bar aaa' '
+(
+ git remote set-url --push someremote bar aaa &&
+ echo foo >expect &&
+ echo bar >>expect &&
+ echo "YYY" >>expect &&
+ echo baz >>expect &&
+ git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push --delete bar' '
+(
+ git remote set-url --push --delete someremote bar &&
+ echo foo >expect &&
+ echo "YYY" >>expect &&
+ echo baz >>expect &&
+ git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push --delete foo' '
+(
+ git remote set-url --push --delete someremote foo &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --add bbb' '
+(
+ git remote set-url --add someremote bbb &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ echo bbb >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --delete .*' '
+(
+ test_must_fail git remote set-url --delete someremote .* &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ echo bbb >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --delete bbb' '
+(
+ git remote set-url --delete someremote bbb &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --delete baz' '
+(
+ test_must_fail git remote set-url --delete someremote baz &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --add ccc' '
+(
+ git remote set-url --add someremote ccc &&
+ echo "YYY" >expect &&
+ echo baz >>expect &&
+ echo ccc >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --delete baz' '
+(
+ git remote set-url --delete someremote baz &&
+ echo "YYY" >expect &&
+ echo ccc >>expect &&
+ test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+ echo "YYY" >>actual &&
+ git config --get-all remote.someremote.url >>actual &&
+ cmp expect actual
+)
+'
+
+test_done
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 516127b539..0f04b2e894 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -547,6 +547,32 @@ test_expect_success 'allow deleting an invalid remote ref' '
'
+test_expect_success 'allow deleting a ref using --delete' '
+ mk_test heads/master &&
+ (cd testrepo && git config receive.denyDeleteCurrent warn) &&
+ git push testrepo --delete master &&
+ (cd testrepo && test_must_fail git rev-parse --verify refs/heads/master)
+'
+
+test_expect_success 'allow deleting a tag using --delete' '
+ mk_test heads/master &&
+ git tag -a -m dummy_message deltag heads/master &&
+ git push testrepo --tags &&
+ (cd testrepo && git rev-parse --verify -q refs/tags/deltag) &&
+ git push testrepo --delete tag deltag &&
+ (cd testrepo && test_must_fail git rev-parse --verify refs/tags/deltag)
+'
+
+test_expect_success 'push --delete without args aborts' '
+ mk_test heads/master &&
+ test_must_fail git push testrepo --delete
+'
+
+test_expect_success 'push --delete refuses src:dest refspecs' '
+ mk_test heads/master &&
+ test_must_fail git push testrepo --delete master:foo
+'
+
test_expect_success 'warn on push to HEAD of non-bare repository' '
mk_test heads/master
(cd testrepo &&
diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh
new file mode 100755
index 0000000000..00da70763b
--- /dev/null
+++ b/t/t5523-push-upstream.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='push with --set-upstream'
+. ./test-lib.sh
+
+test_expect_success 'setup bare parent' '
+ git init --bare parent &&
+ git remote add upstream parent
+'
+
+test_expect_success 'setup local commit' '
+ echo content >file &&
+ git add file &&
+ git commit -m one
+'
+
+check_config() {
+ (echo $2; echo $3) >expect.$1
+ (git config branch.$1.remote
+ git config branch.$1.merge) >actual.$1
+ test_cmp expect.$1 actual.$1
+}
+
+test_expect_success 'push -u master:master' '
+ git push -u upstream master:master &&
+ check_config master upstream refs/heads/master
+'
+
+test_expect_success 'push -u master:other' '
+ git push -u upstream master:other &&
+ check_config master upstream refs/heads/other
+'
+
+test_expect_success 'push -u --dry-run master:otherX' '
+ git push -u --dry-run upstream master:otherX &&
+ check_config master upstream refs/heads/other
+'
+
+test_expect_success 'push -u master2:master2' '
+ git branch master2 &&
+ git push -u upstream master2:master2 &&
+ check_config master2 upstream refs/heads/master2
+'
+
+test_expect_success 'push -u master2:other2' '
+ git push -u upstream master2:other2 &&
+ check_config master2 upstream refs/heads/other2
+'
+
+test_expect_success 'push -u :master2' '
+ git push -u upstream :master2 &&
+ check_config master2 upstream refs/heads/other2
+'
+
+test_expect_success 'push -u --all' '
+ git branch all1 &&
+ git branch all2 &&
+ git push -u --all &&
+ check_config all1 upstream refs/heads/all1 &&
+ check_config all2 upstream refs/heads/all2
+'
+
+test_expect_success 'push -u HEAD' '
+ git checkout -b headbranch &&
+ git push -u upstream HEAD &&
+ check_config headbranch upstream refs/heads/headbranch
+'
+
+test_done
diff --git a/t/t5524-pull-msg.sh b/t/t5524-pull-msg.sh
new file mode 100755
index 0000000000..8cccecc2fc
--- /dev/null
+++ b/t/t5524-pull-msg.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_description='git pull message generation'
+
+. ./test-lib.sh
+
+dollar='$Dollar'
+
+test_expect_success setup '
+ test_commit initial afile original &&
+ git clone . cloned &&
+ (
+ cd cloned &&
+ echo added >bfile &&
+ git add bfile &&
+ test_tick &&
+ git commit -m "add bfile"
+ ) &&
+ test_tick && test_tick &&
+ echo "original $dollar" >afile &&
+ git add afile &&
+ git commit -m "do not clobber $dollar signs"
+'
+
+test_expect_success pull '
+(
+ cd cloned &&
+ git pull --log &&
+ git log -2 &&
+ git cat-file commit HEAD >result &&
+ grep Dollar result
+)
+'
+
+test_done
diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh
index 2a58d0cc9c..83a8e14c6c 100755
--- a/t/t5541-http-push.sh
+++ b/t/t5541-http-push.sh
@@ -88,5 +88,49 @@ test_expect_success 'used receive-pack service' '
test_cmp exp act
'
+test_expect_success 'non-fast-forward push fails' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ git checkout master &&
+ echo "changed" > path2 &&
+ git commit -a -m path2 --amend &&
+
+ HEAD=$(git rev-parse --verify HEAD) &&
+ !(git push -v origin >output 2>&1) &&
+ (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
+ test $HEAD != $(git rev-parse --verify HEAD))
+'
+
+test_expect_success 'non-fast-forward push show ref status' '
+ grep "^ ! \[rejected\][ ]*master -> master (non-fast-forward)$" output
+'
+
+test_expect_success 'non-fast-forward push shows help message' '
+ grep \
+"To prevent you from losing history, non-fast-forward updates were rejected
+Merge the remote changes before pushing again. See the '"'non-fast-forward'"'
+section of '"'git push --help'"' for details." output
+'
+
+test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper' '
+ # create a dissimilarly-named remote ref so that git is unable to match the
+ # two refs (viz. local, remote) unless an explicit refspec is provided.
+ git push origin master:retsam
+
+ echo "change changed" > path2 &&
+ git commit -a -m path2 --amend &&
+
+ # push master too; this ensures there is at least one '"'push'"' command to
+ # the remote helper and triggers interaction with the helper.
+ !(git push -v origin +master master:retsam >output 2>&1) &&
+
+ grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *master -> master (forced update)$" output &&
+ grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output &&
+
+ grep \
+"To prevent you from losing history, non-fast-forward updates were rejected
+Merge the remote changes before pushing again. See the '"'non-fast-forward'"'
+section of '"'git push --help'"' for details." output
+'
+
stop_httpd
test_done
diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh
index c0505ecd7b..7faa31a299 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 0000000000..44885b850c
--- /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 ed034bc980..0000000000
--- 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 0000000000..8c6d0b2f20
--- /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 0000000000..be024e551c
--- /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 27825f5f31..02cb024723 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/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh
index 571931588e..b0047d3c6b 100755
--- a/t/t6006-rev-list-format.sh
+++ b/t/t6006-rev-list-format.sh
@@ -19,6 +19,13 @@ test_cmp expect.$1 output.$1
"
}
+test_format percent %%h <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+%h
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+%h
+EOF
+
test_format hash %H%n%h <<'EOF'
commit 131a310eb913d107dd3c09a65d1651175898735d
131a310eb913d107dd3c09a65d1651175898735d
diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh
new file mode 100755
index 0000000000..8d3fa7d014
--- /dev/null
+++ b/t/t6018-rev-list-glob.sh
@@ -0,0 +1,195 @@
+#!/bin/sh
+
+test_description='rev-list/rev-parse --glob'
+
+. ./test-lib.sh
+
+commit () {
+ test_tick &&
+ echo $1 > foo &&
+ git add foo &&
+ git commit -m "$1"
+}
+
+compare () {
+ # Split arguments on whitespace.
+ git $1 $2 >expected &&
+ git $1 $3 >actual &&
+ test_cmp expected actual
+}
+
+test_expect_success 'setup' '
+
+ commit master &&
+ git checkout -b subspace/one master &&
+ commit one &&
+ git checkout -b subspace/two master &&
+ commit two &&
+ git checkout -b subspace-x master &&
+ commit subspace-x &&
+ git checkout -b other/three master &&
+ commit three &&
+ git checkout -b someref master &&
+ commit some &&
+ git checkout master &&
+ commit master2 &&
+ git tag foo/bar master &&
+ git update-ref refs/remotes/foo/baz master
+'
+
+test_expect_success 'rev-parse --glob=refs/heads/subspace/*' '
+
+ compare rev-parse "subspace/one subspace/two" "--glob=refs/heads/subspace/*"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/subspace/*' '
+
+ compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace/*"
+
+'
+
+test_expect_success 'rev-parse --glob=refs/heads/subspace/' '
+
+ compare rev-parse "subspace/one subspace/two" "--glob=refs/heads/subspace/"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/subspace/' '
+
+ compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace/"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/subspace' '
+
+ compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace"
+
+'
+
+test_expect_success 'rev-parse --branches=subspace/*' '
+
+ compare rev-parse "subspace/one subspace/two" "--branches=subspace/*"
+
+'
+
+test_expect_success 'rev-parse --branches=subspace/' '
+
+ compare rev-parse "subspace/one subspace/two" "--branches=subspace/"
+
+'
+
+test_expect_success 'rev-parse --branches=subspace' '
+
+ compare rev-parse "subspace/one subspace/two" "--branches=subspace"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/subspace/* --glob=heads/other/*' '
+
+ compare rev-parse "subspace/one subspace/two other/three" "--glob=heads/subspace/* --glob=heads/other/*"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/someref/* master' '
+
+ compare rev-parse "master" "--glob=heads/someref/* master"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/*' '
+
+ compare rev-parse "master other/three someref subspace-x subspace/one subspace/two" "--glob=heads/*"
+
+'
+
+test_expect_success 'rev-parse --tags=foo' '
+
+ compare rev-parse "foo/bar" "--tags=foo"
+
+'
+
+test_expect_success 'rev-parse --remotes=foo' '
+
+ compare rev-parse "foo/baz" "--remotes=foo"
+
+'
+
+test_expect_success 'rev-list --glob=refs/heads/subspace/*' '
+
+ compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/*"
+
+'
+
+test_expect_success 'rev-list --glob=heads/subspace/*' '
+
+ compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/*"
+
+'
+
+test_expect_success 'rev-list --glob=refs/heads/subspace/' '
+
+ compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/"
+
+'
+
+test_expect_success 'rev-list --glob=heads/subspace/' '
+
+ compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/"
+
+'
+
+test_expect_success 'rev-list --glob=heads/subspace' '
+
+ compare rev-list "subspace/one subspace/two" "--glob=heads/subspace"
+
+'
+
+test_expect_success 'rev-list --branches=subspace/*' '
+
+ compare rev-list "subspace/one subspace/two" "--branches=subspace/*"
+
+'
+
+test_expect_success 'rev-list --branches=subspace/' '
+
+ compare rev-list "subspace/one subspace/two" "--branches=subspace/"
+
+'
+
+test_expect_success 'rev-list --branches=subspace' '
+
+ compare rev-list "subspace/one subspace/two" "--branches=subspace"
+
+'
+test_expect_success 'rev-list --glob=heads/someref/* master' '
+
+ compare rev-list "master" "--glob=heads/someref/* master"
+
+'
+
+test_expect_success 'rev-list --glob=heads/subspace/* --glob=heads/other/*' '
+
+ compare rev-list "subspace/one subspace/two other/three" "--glob=heads/subspace/* --glob=heads/other/*"
+
+'
+
+test_expect_success 'rev-list --glob=heads/*' '
+
+ compare rev-list "master other/three someref subspace-x subspace/one subspace/two" "--glob=heads/*"
+
+'
+
+test_expect_success 'rev-list --tags=foo' '
+
+ compare rev-list "foo/bar" "--tags=foo"
+
+'
+
+test_expect_success 'rev-list --remotes=foo' '
+
+ compare rev-list "foo/baz" "--remotes=foo"
+
+'
+
+test_done
diff --git a/t/t6026-merge-attr.sh b/t/t6026-merge-attr.sh
index 1ba0a25223..5e439972be 100755
--- a/t/t6026-merge-attr.sh
+++ b/t/t6026-merge-attr.sh
@@ -70,6 +70,18 @@ test_expect_success 'check merge result in working tree' '
'
+test_expect_success 'retry the merge with longer context' '
+ echo text conflict-marker-size=32 >>.gitattributes &&
+ git checkout -m text &&
+ sed -ne "/^\([<=>]\)\1\1\1*/{
+ s/ .*$//
+ p
+ }" >actual text &&
+ grep ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" actual &&
+ grep "================================" actual &&
+ grep "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" actual
+'
+
cat >./custom-merge <<\EOF
#!/bin/sh
diff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh
index 5bbfa44e8d..3900d9f61f 100755
--- a/t/t6029-merge-subtree.sh
+++ b/t/t6029-merge-subtree.sh
@@ -52,6 +52,7 @@ test_expect_success 'initial merge' '
git merge -s ours --no-commit gui/master &&
git read-tree --prefix=git-gui/ -u gui/master &&
git commit -m "Merge git-gui as our subdirectory" &&
+ git checkout -b work &&
git ls-files -s >actual &&
(
echo "100644 $o1 0 git-gui/git-gui.sh"
@@ -65,9 +66,10 @@ test_expect_success 'merge update' '
echo git-gui2 > git-gui.sh &&
o3=$(git hash-object git-gui.sh) &&
git add git-gui.sh &&
+ git checkout -b master2 &&
git commit -m "update git-gui" &&
cd ../git &&
- git pull -s subtree gui master &&
+ git pull -s subtree gui master2 &&
git ls-files -s >actual &&
(
echo "100644 $o3 0 git-gui/git-gui.sh"
@@ -76,4 +78,47 @@ test_expect_success 'merge update' '
test_cmp expected actual
'
+test_expect_success 'initial ambiguous subtree' '
+ cd ../git &&
+ git reset --hard master &&
+ git checkout -b master2 &&
+ git merge -s ours --no-commit gui/master &&
+ git read-tree --prefix=git-gui2/ -u gui/master &&
+ git commit -m "Merge git-gui2 as our subdirectory" &&
+ git checkout -b work2 &&
+ git ls-files -s >actual &&
+ (
+ echo "100644 $o1 0 git-gui/git-gui.sh"
+ echo "100644 $o1 0 git-gui2/git-gui.sh"
+ echo "100644 $o2 0 git.c"
+ ) >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'merge using explicit' '
+ cd ../git &&
+ git reset --hard master2 &&
+ git pull -Xsubtree=git-gui gui master2 &&
+ git ls-files -s >actual &&
+ (
+ echo "100644 $o3 0 git-gui/git-gui.sh"
+ echo "100644 $o1 0 git-gui2/git-gui.sh"
+ echo "100644 $o2 0 git.c"
+ ) >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'merge2 using explicit' '
+ cd ../git &&
+ git reset --hard master2 &&
+ git pull -Xsubtree=git-gui2 gui master2 &&
+ git ls-files -s >actual &&
+ (
+ echo "100644 $o1 0 git-gui/git-gui.sh"
+ echo "100644 $o3 0 git-gui2/git-gui.sh"
+ echo "100644 $o2 0 git.c"
+ ) >expected &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t6037-merge-ours-theirs.sh b/t/t6037-merge-ours-theirs.sh
new file mode 100755
index 0000000000..8ab3d61f44
--- /dev/null
+++ b/t/t6037-merge-ours-theirs.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+test_description='Merge-recursive ours and theirs variants'
+. ./test-lib.sh
+
+test_expect_success setup '
+ for i in 1 2 3 4 5 6 7 8 9
+ do
+ echo "$i"
+ done >file &&
+ git add file &&
+ cp file elif &&
+ git commit -m initial &&
+
+ sed -e "s/1/one/" -e "s/9/nine/" >file <elif &&
+ git commit -a -m ours &&
+
+ git checkout -b side HEAD^ &&
+
+ sed -e "s/9/nueve/" >file <elif &&
+ git commit -a -m theirs &&
+
+ git checkout master^0
+'
+
+test_expect_success 'plain recursive - should conflict' '
+ git reset --hard master &&
+ test_must_fail git merge -s recursive side &&
+ grep nine file &&
+ grep nueve file &&
+ ! grep 9 file &&
+ grep one file &&
+ ! grep 1 file
+'
+
+test_expect_success 'recursive favouring theirs' '
+ git reset --hard master &&
+ git merge -s recursive -Xtheirs side &&
+ ! grep nine file &&
+ grep nueve file &&
+ ! grep 9 file &&
+ grep one file &&
+ ! grep 1 file
+'
+
+test_expect_success 'recursive favouring ours' '
+ git reset --hard master &&
+ git merge -s recursive -X ours side &&
+ grep nine file &&
+ ! grep nueve file &&
+ ! grep 9 file &&
+ grep one file &&
+ ! grep 1 file
+'
+
+test_expect_success 'pull with -X' '
+ git reset --hard master && git pull -s recursive -Xours . side &&
+ git reset --hard master && git pull -s recursive -X ours . side &&
+ git reset --hard master && git pull -s recursive -Xtheirs . side &&
+ git reset --hard master && git pull -s recursive -X theirs . side &&
+ git reset --hard master && ! git pull -s recursive -X bork . side
+'
+
+test_done
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 664b0f8052..1785e178a4 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -89,4 +89,25 @@ test_expect_success 'status when tracking annotated tags' '
grep "set up to track" actual &&
git checkout heavytrack
'
+
+test_expect_success 'setup tracking with branch --set-upstream on existing branch' '
+ git branch from-master master &&
+ test_must_fail git config branch.from-master.merge > actual &&
+ git branch --set-upstream from-master master &&
+ git config branch.from-master.merge > actual &&
+ grep -q "^refs/heads/master$" actual
+'
+
+test_expect_success '--set-upstream does not change branch' '
+ git branch from-master2 master &&
+ test_must_fail git config branch.from-master2.merge > actual &&
+ git rev-list from-master2 &&
+ git update-ref refs/heads/from-master2 from-master2^ &&
+ git rev-parse from-master2 >expect2 &&
+ git branch --set-upstream from-master2 master &&
+ git config branch.from-master.merge > actual &&
+ git rev-parse from-master2 >actual2 &&
+ grep -q "^refs/heads/master$" actual &&
+ cmp expect2 actual2
+'
test_done
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
index 10b8f8c44b..65a35d94a0 100755
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
@@ -189,6 +189,18 @@ test_expect_success 'absolute pathname outside should fail' '(
)'
+test_expect_success 'git mv to move multiple sources into a directory' '
+ rm -fr .git && git init &&
+ mkdir dir other &&
+ >dir/a.txt &&
+ >dir/b.txt &&
+ git add dir/?.txt &&
+ git mv dir/a.txt dir/b.txt other &&
+ git ls-files >actual &&
+ { echo other/a.txt; echo other/b.txt; } >expect &&
+ test_cmp expect actual
+'
+
test_expect_success 'git mv should not change sha1 of moved cache entry' '
rm -fr .git &&
diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh
index abd14bf819..7eceb086be 100755
--- a/t/t7002-grep.sh
+++ b/t/t7002-grep.sh
@@ -302,8 +302,8 @@ test_expect_success 'grep -C1, hunk mark between files' '
test_cmp expected actual
'
-test_expect_success 'grep -C1 --no-ext-grep, hunk mark between files' '
- git grep -C1 --no-ext-grep "^[yz]" >actual &&
+test_expect_success 'grep -C1 hunk mark between files' '
+ git grep -C1 "^[yz]" >actual &&
test_cmp expected actual
'
@@ -359,7 +359,7 @@ test_expect_success 'log grep (6)' '
test_expect_success 'grep with CE_VALID file' '
git update-index --assume-unchanged t/t &&
rm t/t &&
- test "$(git grep --no-ext-grep test)" = "t/t:test" &&
+ test "$(git grep test)" = "t/t:test" &&
git update-index --no-assume-unchanged t/t &&
git checkout t/t
'
@@ -426,4 +426,56 @@ test_expect_success 'grep -Fi' '
test_cmp expected actual
'
+test_expect_success 'outside of git repository' '
+ rm -fr non &&
+ mkdir -p non/git/sub &&
+ echo hello >non/git/file1 &&
+ echo world >non/git/sub/file2 &&
+ echo ".*o*" >non/git/.gitignore &&
+ {
+ echo file1:hello &&
+ echo sub/file2:world
+ } >non/expect.full &&
+ echo file2:world >non/expect.sub
+ (
+ GIT_CEILING_DIRECTORIES="$(pwd)/non/git" &&
+ export GIT_CEILING_DIRECTORIES &&
+ cd non/git &&
+ test_must_fail git grep o &&
+ git grep --no-index o >../actual.full &&
+ test_cmp ../expect.full ../actual.full
+ cd sub &&
+ test_must_fail git grep o &&
+ git grep --no-index o >../../actual.sub &&
+ test_cmp ../../expect.sub ../../actual.sub
+ )
+'
+
+test_expect_success 'inside git repository but with --no-index' '
+ rm -fr is &&
+ mkdir -p is/git/sub &&
+ echo hello >is/git/file1 &&
+ echo world >is/git/sub/file2 &&
+ echo ".*o*" >is/git/.gitignore &&
+ {
+ echo file1:hello &&
+ echo sub/file2:world
+ } >is/expect.full &&
+ : >is/expect.empty &&
+ echo file2:world >is/expect.sub
+ (
+ cd is/git &&
+ git init &&
+ test_must_fail git grep o >../actual.full &&
+ test_cmp ../expect.empty ../actual.full &&
+ git grep --no-index o >../actual.full &&
+ test_cmp ../expect.full ../actual.full &&
+ cd sub &&
+ test_must_fail git grep o >../../actual.sub &&
+ test_cmp ../../expect.empty ../../actual.sub &&
+ git grep --no-index o >../../actual.sub &&
+ test_cmp ../../expect.sub ../../actual.sub
+ )
+'
+
test_done
diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh
new file mode 100755
index 0000000000..bb4066f767
--- /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 0000000000..8d8b1c0e25
--- /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/t7103-reset-bare.sh b/t/t7103-reset-bare.sh
index 68041df5f4..afb55b3a46 100755
--- a/t/t7103-reset-bare.sh
+++ b/t/t7103-reset-bare.sh
@@ -29,6 +29,12 @@ test_expect_success 'soft reset is ok' '
(cd .git && git reset --soft)
'
+test_expect_success 'hard reset works with GIT_WORK_TREE' '
+ mkdir worktree &&
+ GIT_WORK_TREE=$PWD/worktree GIT_DIR=$PWD/.git git reset --hard &&
+ test_cmp file worktree/file
+'
+
test_expect_success 'setup bare' '
git clone --bare . bare.git &&
cd bare.git
diff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh
new file mode 100755
index 0000000000..8704d00196
--- /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 0000000000..de896c948d
--- /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 ebfd34df36..6442f710be 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 118c6ebb18..7d8ed68bef 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 a0cc99ab9f..1a4dc5f893 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/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh
index 6cc16c39fe..d3c039f724 100755
--- a/t/t7401-submodule-summary.sh
+++ b/t/t7401-submodule-summary.sh
@@ -213,7 +213,7 @@ EOF
test_expect_success '--for-status' "
git submodule summary --for-status HEAD^ >actual &&
test_cmp actual - <<EOF
-# Modified submodules:
+# Submodule changes to be committed:
#
# * sm1 $head6...0000000:
#
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index a529701241..7940901d47 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -117,7 +117,11 @@ test_expect_success \
test_expect_success \
"overriding author from command line" \
"echo 'gak' >file && \
- git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a"
+ git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a >output 2>&1"
+
+test_expect_success \
+ "commit --author output mentions author" \
+ "grep Rubber.Duck output"
test_expect_success PERL \
"interactive add" \
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
index fe94552296..844fb43c6d 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/t7506-status-submodule.sh b/t/t7506-status-submodule.sh
index 3ca17abad1..253c334319 100755
--- a/t/t7506-status-submodule.sh
+++ b/t/t7506-status-submodule.sh
@@ -5,34 +5,87 @@ test_description='git status for submodule'
. ./test-lib.sh
test_expect_success 'setup' '
- test_create_repo sub
- cd sub &&
- : >bar &&
- git add bar &&
- git commit -m " Add bar" &&
- cd .. &&
- git add sub &&
+ test_create_repo sub &&
+ (
+ cd sub &&
+ : >bar &&
+ git add bar &&
+ git commit -m " Add bar" &&
+ : >foo &&
+ git add foo &&
+ git commit -m " Add foo"
+ ) &&
+ echo output > .gitignore &&
+ git add sub .gitignore &&
git commit -m "Add submodule sub"
'
test_expect_success 'status clean' '
- git status |
- grep "nothing to commit"
+ git status >output &&
+ grep "nothing to commit" output
'
+
test_expect_success 'commit --dry-run -a clean' '
- git commit --dry-run -a |
- grep "nothing to commit"
+ test_must_fail git commit --dry-run -a >output &&
+ grep "nothing to commit" output
+'
+
+test_expect_success 'status with modified file in submodule' '
+ (cd sub && git reset --hard) &&
+ echo "changed" >sub/foo &&
+ git status >output &&
+ grep "modified: sub" output
+'
+
+test_expect_success 'status with modified file in submodule (porcelain)' '
+ (cd sub && git reset --hard) &&
+ echo "changed" >sub/foo &&
+ git status --porcelain >output &&
+ diff output - <<-\EOF
+ M sub
+ EOF
+'
+
+test_expect_success 'status with added file in submodule' '
+ (cd sub && git reset --hard && echo >foo && git add foo) &&
+ git status >output &&
+ grep "modified: sub" output
+'
+
+test_expect_success 'status with added file in submodule (porcelain)' '
+ (cd sub && git reset --hard && echo >foo && git add foo) &&
+ git status --porcelain >output &&
+ diff output - <<-\EOF
+ M sub
+ EOF
+'
+
+test_expect_success 'status with untracked file in submodule' '
+ (cd sub && git reset --hard) &&
+ echo "content" >sub/new-file &&
+ git status >output &&
+ grep "modified: sub" output
+'
+
+test_expect_success 'status with untracked file in submodule (porcelain)' '
+ git status --porcelain >output &&
+ diff output - <<-\EOF
+ M sub
+ EOF
'
+
test_expect_success 'rm submodule contents' '
rm -rf sub/* sub/.git
'
+
test_expect_success 'status clean (empty submodule dir)' '
- git status |
- grep "nothing to commit"
+ git status >output &&
+ grep "nothing to commit" output
'
+
test_expect_success 'status -a clean (empty submodule dir)' '
- git commit --dry-run -a |
- grep "nothing to commit"
+ test_must_fail git commit --dry-run -a >output &&
+ grep "nothing to commit" output
'
test_done
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index cf67fe3a4a..556d0faa77 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -579,7 +579,7 @@ cat >expect <<EOF
#
# modified: dir1/modified
#
-# Modified submodules:
+# Submodule changes to be committed:
#
# * sm 0000000...$head (1):
# > Add foo
@@ -672,7 +672,7 @@ cat >expect <<EOF
#
# modified: dir1/modified
#
-# Modified submodules:
+# Submodule changes to be committed:
#
# * sm 0000000...$head (1):
# > Add foo
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index fff6a6d0ea..19c72f55bf 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -1,6 +1,6 @@
#!/bin/sh
#
-# Copyright (c) 2009 David Aguilar
+# Copyright (c) 2009, 2010 David Aguilar
#
test_description='git-difftool
@@ -15,14 +15,19 @@ if ! test_have_prereq PERL; then
test_done
fi
+LF='
+'
+
remove_config_vars()
{
# Unset all config variables used by git-difftool
git config --unset diff.tool
+ git config --unset diff.guitool
git config --unset difftool.test-tool.cmd
git config --unset difftool.prompt
git config --unset merge.tool
git config --unset mergetool.test-tool.cmd
+ git config --unset mergetool.prompt
return 0
}
@@ -31,11 +36,11 @@ restore_test_defaults()
# Restores the test defaults used by several tests
remove_config_vars
unset GIT_DIFF_TOOL
- unset GIT_MERGE_TOOL
unset GIT_DIFFTOOL_PROMPT
unset GIT_DIFFTOOL_NO_PROMPT
git config diff.tool test-tool &&
git config difftool.test-tool.cmd 'cat $LOCAL'
+ git config difftool.bogus-tool.cmd false
}
prompt_given()
@@ -71,11 +76,22 @@ test_expect_success 'custom commands' '
# Ensures that git-difftool ignores bogus --tool values
test_expect_success 'difftool ignores bad --tool values' '
- diff=$(git difftool --no-prompt --tool=bogus-tool branch)
+ diff=$(git difftool --no-prompt --tool=bad-tool branch)
test "$?" = 1 &&
test "$diff" = ""
'
+test_expect_success 'difftool honors --gui' '
+ git config merge.tool bogus-tool &&
+ git config diff.tool bogus-tool &&
+ git config diff.guitool test-tool &&
+
+ diff=$(git difftool --no-prompt --gui branch) &&
+ test "$diff" = "branch" &&
+
+ restore_test_defaults
+'
+
# Specify the diff tool using $GIT_DIFF_TOOL
test_expect_success 'GIT_DIFF_TOOL variable' '
git config --unset diff.tool
@@ -94,15 +110,7 @@ test_expect_success 'GIT_DIFF_TOOL overrides' '
git config diff.tool bogus-tool &&
git config merge.tool bogus-tool &&
- GIT_MERGE_TOOL=test-tool &&
- export GIT_MERGE_TOOL &&
- diff=$(git difftool --no-prompt branch) &&
- test "$diff" = "branch" &&
- unset GIT_MERGE_TOOL &&
-
- GIT_MERGE_TOOL=bogus-tool &&
GIT_DIFF_TOOL=test-tool &&
- export GIT_MERGE_TOOL &&
export GIT_DIFF_TOOL &&
diff=$(git difftool --no-prompt branch) &&
@@ -152,6 +160,17 @@ test_expect_success 'difftool.prompt config variable is false' '
restore_test_defaults
'
+# Test that we don't have to pass --no-prompt when mergetool.prompt is false
+test_expect_success 'difftool merge.prompt = false' '
+ git config --unset difftool.prompt
+ git config mergetool.prompt false &&
+
+ diff=$(git difftool branch) &&
+ test "$diff" = "branch" &&
+
+ restore_test_defaults
+'
+
# Test that the -y flag can override difftool.prompt = true
test_expect_success 'difftool.prompt can overridden with -y' '
git config difftool.prompt true &&
@@ -210,7 +229,39 @@ test_expect_success 'difftool.<tool>.path' '
diff=$(git difftool --tool=tkdiff --no-prompt branch) &&
git config --unset difftool.tkdiff.path &&
lines=$(echo "$diff" | grep file | wc -l) &&
- test "$lines" -eq 1
+ test "$lines" -eq 1 &&
+
+ restore_test_defaults
+'
+
+test_expect_success 'difftool --extcmd=cat' '
+ diff=$(git difftool --no-prompt --extcmd=cat branch) &&
+ test "$diff" = branch"$LF"master
+'
+
+test_expect_success 'difftool --extcmd cat' '
+ diff=$(git difftool --no-prompt --extcmd cat branch) &&
+ test "$diff" = branch"$LF"master
+'
+
+test_expect_success 'difftool -x cat' '
+ diff=$(git difftool --no-prompt -x cat branch) &&
+ test "$diff" = branch"$LF"master
+'
+
+test_expect_success 'difftool --extcmd echo arg1' '
+ diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"echo\ \$1\" branch)
+ test "$diff" = file
+'
+
+test_expect_success 'difftool --extcmd cat arg1' '
+ diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$1\" branch)
+ test "$diff" = master
+'
+
+test_expect_success 'difftool --extcmd cat arg2' '
+ diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$2\" branch)
+ test "$diff" = branch
'
test_done
diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh
index 359eeaa738..3569c62096 100755
--- a/t/t9151-svn-mergeinfo.sh
+++ b/t/t9151-svn-mergeinfo.sh
@@ -33,7 +33,7 @@ test_expect_success 'svn non-merge merge commits did not become git merge commit
[ -z "$bad_non_merges" ]
'
-test_expect_success 'everything got merged in the end' '
+test_expect_failure 'everything got merged in the end' '
unmerged=$(git rev-list --all --not master)
[ -z "$unmerged" ]
'
diff --git a/t/t9151/make-svnmerge-dump b/t/t9151/make-svnmerge-dump
index d917717cf3..3d73f140f8 100644
--- a/t/t9151/make-svnmerge-dump
+++ b/t/t9151/make-svnmerge-dump
@@ -26,8 +26,9 @@ i=0
cd foo
mkdir trunk
mkdir branches
-svn add trunk branches
-i=$(commit $i "Setup trunk and branches")
+mkdir tags
+svn add trunk branches tags
+i=$(commit $i "Setup trunk, branches, and tags")
git cat-file blob 6683463e:Makefile > trunk/Makefile
svn add trunk/Makefile
@@ -155,6 +156,66 @@ svn merge ../branches/right --accept postpone
i=$(commit $i "non-merge right to trunk 2")
cd ..
+say "Adding subdirectory to LEFT"
+svn update
+cd branches/left
+mkdir subdir
+echo "Yeehaw" > subdir/cowboy
+svn add subdir
+i=$(commit $i "add subdirectory to left branch")
+cd ../../
+
+say "Merging LEFT to TRUNK"
+svn update
+cd trunk
+svn merge ../branches/left --accept postpone
+i=$(commit $i "merge left to trunk")
+cd ..
+
+say "Make PARTIAL branch"
+svn update
+i=$(commit $i "make partial branch")
+svn cp trunk/subdir branches/partial
+
+say "Make a commit to PARTIAL"
+svn update
+cd branches/partial
+echo "racecar" > palindromes
+svn add palindromes
+i=$(commit $i "partial update")
+cd ../../
+
+say "Merge PARTIAL to TRUNK"
+svn update
+cd trunk/subdir
+svn merge ../../branches/partial --accept postpone
+i=$(commit $i "merge partial to trunk")
+cd ../../
+
+say "Tagging trunk"
+svn update
+i=$(commit $i "tagging v1.0")
+svn cp trunk tags/v1.0
+
+say "Branching BUGFIX from v1.0"
+svn update
+i=$(commit $i "make bugfix branch from tag")
+svn cp tags/v1.0 branches/bugfix
+
+say "Make a commit to BUGFIX"
+svn update
+cd branches/bugfix/
+echo "kayak" >> subdir/palindromes
+i=$(commit $i "commit to bugfix")
+cd ../../
+
+say "Merge BUGFIX to TRUNK"
+svn update
+cd trunk
+svn merge ../branches/bugfix/ --accept postpone
+i=$(commit $i "Merge BUGFIX to TRUNK")
+cd ..
+
cd ..
svnadmin dump foo.svn > svn-mergeinfo.dump
diff --git a/t/t9151/svn-mergeinfo.dump b/t/t9151/svn-mergeinfo.dump
index 9543e31c96..ebf386ebd5 100644
--- a/t/t9151/svn-mergeinfo.dump
+++ b/t/t9151/svn-mergeinfo.dump
@@ -1,6 +1,6 @@
SVN-fs-dump-format-version: 2
-UUID: 64142547-0943-4db2-836a-d1e1eb2f9924
+UUID: d6191530-2693-4a8e-98e7-b194d4c3edd8
Revision-number: 0
Prop-content-length: 56
@@ -9,25 +9,25 @@ Content-length: 56
K 8
svn:date
V 27
-2009-12-19T16:17:51.232640Z
+2010-01-19T04:14:02.832406Z
PROPS-END
Revision-number: 1
-Prop-content-length: 128
-Content-length: 128
+Prop-content-length: 134
+Content-length: 134
K 7
svn:log
-V 29
-(r1) Setup trunk and branches
+V 36
+(r1) Setup trunk, branches, and tags
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:51.831965Z
+2010-01-19T04:14:03.055172Z
PROPS-END
Node-path: branches
@@ -39,6 +39,15 @@ Content-length: 10
PROPS-END
+Node-path: tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
Node-path: trunk
Node-kind: dir
Node-action: add
@@ -49,8 +58,8 @@ PROPS-END
Revision-number: 2
-Prop-content-length: 112
-Content-length: 112
+Prop-content-length: 111
+Content-length: 111
K 7
svn:log
@@ -58,12 +67,12 @@ V 13
(r2) ancestor
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:52.300075Z
+2010-01-19T04:14:04.064506Z
PROPS-END
Node-path: trunk/Makefile
@@ -156,8 +165,8 @@ backup: clean
Revision-number: 3
-Prop-content-length: 120
-Content-length: 120
+Prop-content-length: 119
+Content-length: 119
K 7
svn:log
@@ -165,12 +174,12 @@ V 21
(r3) make left branch
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:52.768800Z
+2010-01-19T04:14:06.040389Z
PROPS-END
Node-path: branches/left
@@ -190,8 +199,8 @@ Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590
Revision-number: 4
-Prop-content-length: 121
-Content-length: 121
+Prop-content-length: 120
+Content-length: 120
K 7
svn:log
@@ -199,12 +208,12 @@ V 22
(r4) make right branch
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:53.177879Z
+2010-01-19T04:14:08.040905Z
PROPS-END
Node-path: branches/right
@@ -224,8 +233,8 @@ Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590
Revision-number: 5
-Prop-content-length: 117
-Content-length: 117
+Prop-content-length: 116
+Content-length: 116
K 7
svn:log
@@ -233,12 +242,12 @@ V 18
(r5) left update 1
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:53.604691Z
+2010-01-19T04:14:09.049169Z
PROPS-END
Node-path: branches/left/Makefile
@@ -329,8 +338,8 @@ backup: clean
Revision-number: 6
-Prop-content-length: 118
-Content-length: 118
+Prop-content-length: 117
+Content-length: 117
K 7
svn:log
@@ -338,12 +347,12 @@ V 19
(r6) right update 1
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:54.063555Z
+2010-01-19T04:14:10.049350Z
PROPS-END
Node-path: branches/right/Makefile
@@ -437,8 +446,8 @@ backup: clean
Revision-number: 7
-Prop-content-length: 117
-Content-length: 117
+Prop-content-length: 116
+Content-length: 116
K 7
svn:log
@@ -446,12 +455,12 @@ V 18
(r7) left update 2
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:54.523904Z
+2010-01-19T04:14:11.049209Z
PROPS-END
Node-path: branches/left/Makefile
@@ -542,8 +551,8 @@ backup: clean
Revision-number: 8
-Prop-content-length: 117
-Content-length: 117
+Prop-content-length: 116
+Content-length: 116
K 7
svn:log
@@ -551,12 +560,12 @@ V 18
(r8) left update 3
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:54.975970Z
+2010-01-19T04:14:12.049234Z
PROPS-END
Node-path: branches/left/Makefile
@@ -647,8 +656,8 @@ backup: clean
Revision-number: 9
-Prop-content-length: 124
-Content-length: 124
+Prop-content-length: 123
+Content-length: 123
K 7
svn:log
@@ -656,12 +665,12 @@ V 25
(r9) make left sub-branch
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:55.459904Z
+2010-01-19T04:14:14.040894Z
PROPS-END
Node-path: branches/left-sub
@@ -687,8 +696,8 @@ Text-copy-source-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10
Revision-number: 10
-Prop-content-length: 129
-Content-length: 129
+Prop-content-length: 128
+Content-length: 128
K 7
svn:log
@@ -696,12 +705,12 @@ V 30
(r10) left sub-branch update 1
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:55.862113Z
+2010-01-19T04:14:15.049935Z
PROPS-END
Node-path: branches/left-sub/README
@@ -718,8 +727,8 @@ crunch
Revision-number: 11
-Prop-content-length: 126
-Content-length: 126
+Prop-content-length: 125
+Content-length: 125
K 7
svn:log
@@ -727,12 +736,12 @@ V 27
(r11) Merge left to trunk 1
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:56.413416Z
+2010-01-19T04:14:18.056594Z
PROPS-END
Node-path: trunk
@@ -836,8 +845,8 @@ backup: clean
Revision-number: 12
-Prop-content-length: 118
-Content-length: 118
+Prop-content-length: 117
+Content-length: 117
K 7
svn:log
@@ -845,12 +854,12 @@ V 19
(r12) left update 4
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:56.831014Z
+2010-01-19T04:14:19.049620Z
PROPS-END
Node-path: branches/left/zlonk
@@ -867,8 +876,8 @@ touche
Revision-number: 13
-Prop-content-length: 119
-Content-length: 119
+Prop-content-length: 118
+Content-length: 118
K 7
svn:log
@@ -876,12 +885,12 @@ V 20
(r13) right update 2
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:57.341143Z
+2010-01-19T04:14:20.049659Z
PROPS-END
Node-path: branches/right/bang
@@ -898,8 +907,8 @@ thwacke
Revision-number: 14
-Prop-content-length: 141
-Content-length: 141
+Prop-content-length: 140
+Content-length: 140
K 7
svn:log
@@ -907,12 +916,12 @@ V 42
(r14) Cherry-pick right 2 commits to trunk
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:57.841851Z
+2010-01-19T04:14:23.041991Z
PROPS-END
Node-path: trunk
@@ -1029,8 +1038,8 @@ Text-copy-source-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062
Revision-number: 15
-Prop-content-length: 127
-Content-length: 127
+Prop-content-length: 126
+Content-length: 126
K 7
svn:log
@@ -1038,12 +1047,12 @@ V 28
(r15) Merge right to trunk 1
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:58.368520Z
+2010-01-19T04:14:26.054456Z
PROPS-END
Node-path: trunk
@@ -1061,8 +1070,8 @@ PROPS-END
Revision-number: 16
-Prop-content-length: 119
-Content-length: 119
+Prop-content-length: 118
+Content-length: 118
K 7
svn:log
@@ -1070,12 +1079,12 @@ V 20
(r16) right update 3
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:58.779056Z
+2010-01-19T04:14:27.049955Z
PROPS-END
Node-path: branches/right/urkkk
@@ -1092,8 +1101,8 @@ whamm
Revision-number: 17
-Prop-content-length: 119
-Content-length: 119
+Prop-content-length: 118
+Content-length: 118
K 7
svn:log
@@ -1101,12 +1110,12 @@ V 20
(r17) trunk update 1
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:59.221851Z
+2010-01-19T04:14:28.049615Z
PROPS-END
Node-path: trunk/vronk
@@ -1123,8 +1132,8 @@ pow
Revision-number: 18
-Prop-content-length: 135
-Content-length: 135
+Prop-content-length: 134
+Content-length: 134
K 7
svn:log
@@ -1132,12 +1141,12 @@ V 36
(r18) Merge right to left sub-branch
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:17:59.781666Z
+2010-01-19T04:14:31.061460Z
PROPS-END
Node-path: branches/left-sub
@@ -1262,8 +1271,8 @@ Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302
Revision-number: 19
-Prop-content-length: 129
-Content-length: 129
+Prop-content-length: 128
+Content-length: 128
K 7
svn:log
@@ -1271,12 +1280,12 @@ V 30
(r19) left sub-branch update 2
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:18:00.200531Z
+2010-01-19T04:14:32.049244Z
PROPS-END
Node-path: branches/left-sub/wham_eth
@@ -1293,8 +1302,8 @@ zowie
Revision-number: 20
-Prop-content-length: 118
-Content-length: 118
+Prop-content-length: 117
+Content-length: 117
K 7
svn:log
@@ -1302,12 +1311,12 @@ V 19
(r20) left update 5
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:18:00.659636Z
+2010-01-19T04:14:33.049332Z
PROPS-END
Node-path: branches/left/glurpp
@@ -1324,8 +1333,8 @@ eee_yow
Revision-number: 21
-Prop-content-length: 147
-Content-length: 147
+Prop-content-length: 146
+Content-length: 146
K 7
svn:log
@@ -1333,12 +1342,12 @@ V 48
(r21) Cherry-pick left sub-branch commit to left
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:18:01.194402Z
+2010-01-19T04:14:36.041839Z
PROPS-END
Node-path: branches/left
@@ -1364,8 +1373,8 @@ Text-copy-source-sha1: b165019b005c199237ba822c4404e771e93b654a
Revision-number: 22
-Prop-content-length: 134
-Content-length: 134
+Prop-content-length: 133
+Content-length: 133
K 7
svn:log
@@ -1373,12 +1382,12 @@ V 35
(r22) Merge left sub-branch to left
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:18:01.679218Z
+2010-01-19T04:14:39.045014Z
PROPS-END
Node-path: branches/left
@@ -1513,8 +1522,8 @@ Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302
Revision-number: 23
-Prop-content-length: 126
-Content-length: 126
+Prop-content-length: 125
+Content-length: 125
K 7
svn:log
@@ -1522,12 +1531,12 @@ V 27
(r23) Merge left to trunk 2
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:18:02.212349Z
+2010-01-19T04:14:42.052798Z
PROPS-END
Node-path: trunk
@@ -1591,8 +1600,8 @@ Text-copy-source-sha1: 9716527ebd70a75c27625cacbeb2d897c6e86178
Revision-number: 24
-Prop-content-length: 131
-Content-length: 131
+Prop-content-length: 130
+Content-length: 130
K 7
svn:log
@@ -1600,12 +1609,12 @@ V 32
(r24) non-merge right to trunk 2
K 10
svn:author
-V 4
-samv
+V 3
+adm
K 8
svn:date
V 27
-2009-12-19T16:18:02.672148Z
+2010-01-19T04:14:44.038434Z
PROPS-END
Node-path: trunk
@@ -1623,3 +1632,291 @@ V 64
PROPS-END
+Revision-number: 25
+Prop-content-length: 135
+Content-length: 135
+
+K 7
+svn:log
+V 37
+(r25) add subdirectory to left branch
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:46.052649Z
+PROPS-END
+
+Node-path: branches/left/subdir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/left/subdir/cowboy
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: f1d6530278ad409e68cc675476ad995f
+Text-content-sha1: 732d9e3e5c391ffd767a98b45ddcc848de778cea
+Content-length: 17
+
+PROPS-END
+Yeehaw
+
+
+Revision-number: 26
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 25
+(r26) merge left to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:49.040783Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 99
+Content-length: 99
+
+K 13
+svn:mergeinfo
+V 64
+/branches/left:2-25
+/branches/left-sub:4-19
+/branches/right:2-22
+PROPS-END
+
+
+Node-path: trunk/subdir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 25
+Node-copyfrom-path: branches/left/subdir
+
+
+Revision-number: 27
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 20
+(r28) partial update
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:53.049037Z
+PROPS-END
+
+Node-path: branches/partial
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 26
+Node-copyfrom-path: trunk/subdir
+
+
+Node-path: branches/partial/palindromes
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: 5d1c2024fb5efc4eef812856df1b080c
+Text-content-sha1: 5f8509ddd14c91a52864dd1447344e706f9bbc69
+Content-length: 18
+
+PROPS-END
+racecar
+
+
+Revision-number: 28
+Prop-content-length: 126
+Content-length: 126
+
+K 7
+svn:log
+V 28
+(r29) merge partial to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:56.041526Z
+PROPS-END
+
+Node-path: trunk/subdir
+Node-kind: dir
+Node-action: change
+Prop-content-length: 142
+Content-length: 142
+
+K 13
+svn:mergeinfo
+V 106
+/branches/left/subdir:2-25
+/branches/left-sub/subdir:4-19
+/branches/partial:27
+/branches/right/subdir:2-22
+PROPS-END
+
+
+Node-path: trunk/subdir/palindromes
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 27
+Node-copyfrom-path: branches/partial/palindromes
+Text-copy-source-md5: 5d1c2024fb5efc4eef812856df1b080c
+Text-copy-source-sha1: 5f8509ddd14c91a52864dd1447344e706f9bbc69
+
+
+Revision-number: 29
+Prop-content-length: 131
+Content-length: 131
+
+K 7
+svn:log
+V 33
+(r31) make bugfix branch from tag
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:15:00.039761Z
+PROPS-END
+
+Node-path: tags/v1.0
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 28
+Node-copyfrom-path: trunk
+
+
+Revision-number: 30
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 22
+(r32) commit to bugfix
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:15:03.043218Z
+PROPS-END
+
+Node-path: branches/bugfix
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 29
+Node-copyfrom-path: tags/v1.0
+
+
+Node-path: branches/bugfix/subdir/palindromes
+Node-kind: file
+Node-action: change
+Text-content-length: 14
+Text-content-md5: 3b12d98578a3f4320ba97e66da54fe5f
+Text-content-sha1: 672931c9e8ac2c408209efab2f015638b6d64042
+Content-length: 14
+
+racecar
+kayak
+
+
+Revision-number: 31
+Prop-content-length: 125
+Content-length: 125
+
+K 7
+svn:log
+V 27
+(r33) Merge BUGFIX to TRUNK
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:15:06.043723Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 133
+Content-length: 133
+
+K 13
+svn:mergeinfo
+V 98
+/branches/bugfix:30
+/branches/left:2-25
+/branches/left-sub:4-19
+/branches/right:2-22
+/tags/v1.0:29
+PROPS-END
+
+
+Node-path: trunk/subdir
+Node-kind: dir
+Node-action: change
+Prop-content-length: 190
+Content-length: 190
+
+K 13
+svn:mergeinfo
+V 154
+/branches/bugfix/subdir:30
+/branches/left/subdir:2-25
+/branches/left-sub/subdir:4-19
+/branches/partial:27
+/branches/right/subdir:2-22
+/tags/v1.0/subdir:29
+PROPS-END
+
+
+Node-path: trunk/subdir/palindromes
+Node-kind: file
+Node-action: change
+Text-content-length: 14
+Text-content-md5: 3b12d98578a3f4320ba97e66da54fe5f
+Text-content-sha1: 672931c9e8ac2c408209efab2f015638b6d64042
+Content-length: 14
+
+racecar
+kayak
+
+
diff --git a/t/t9153-git-svn-rewrite-uuid.sh b/t/t9153-git-svn-rewrite-uuid.sh
new file mode 100755
index 0000000000..88a2cfa233
--- /dev/null
+++ b/t/t9153-git-svn-rewrite-uuid.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Jay Soffian
+#
+
+test_description='git svn --rewrite-uuid test'
+
+. ./lib-git-svn.sh
+
+uuid=6cc8ada4-5932-4b4a-8242-3534ed8a3232
+
+test_expect_success 'load svn repo' "
+ svnadmin load -q '$rawsvnrepo' < '$TEST_DIRECTORY/t9153/svn.dump' &&
+ git svn init --minimize-url --rewrite-uuid='$uuid' '$svnrepo' &&
+ git svn fetch
+ "
+
+test_expect_success 'verify uuid' "
+ git cat-file commit refs/remotes/git-svn~0 | \
+ grep '^${git_svn_id}: .*@2 $uuid$' &&
+ git cat-file commit refs/remotes/git-svn~1 | \
+ grep '^${git_svn_id}: .*@1 $uuid$'
+ "
+
+test_done
diff --git a/t/t9153/svn.dump b/t/t9153/svn.dump
new file mode 100644
index 0000000000..0ddfe7025d
--- /dev/null
+++ b/t/t9153/svn.dump
@@ -0,0 +1,75 @@
+SVN-fs-dump-format-version: 2
+
+UUID: b4885626-c94f-4a6c-b179-00c030fc68e8
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-01-23T06:41:03.908576Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 11
+initial foo
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T06:41:48.353776Z
+PROPS-END
+
+Node-path: foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Text-content-sha1: f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 2
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 12
+now with bar
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T06:42:14.214640Z
+PROPS-END
+
+Node-path: foo
+Node-kind: file
+Node-action: change
+Text-content-length: 8
+Text-content-md5: f47c75614087a8dd938ba4acff252494
+Text-content-sha1: 4e48e2c9a3d2ca8a708cb0cc545700544efb5021
+Content-length: 8
+
+foo
+bar
+
+
diff --git a/t/t9154-git-svn-fancy-glob.sh b/t/t9154-git-svn-fancy-glob.sh
new file mode 100755
index 0000000000..a6a56a6cb9
--- /dev/null
+++ b/t/t9154-git-svn-fancy-glob.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Jay Soffian
+#
+
+test_description='git svn fancy glob test'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load svn repo' "
+ svnadmin load -q '$rawsvnrepo' < '$TEST_DIRECTORY/t9154/svn.dump' &&
+ git svn init --minimize-url -T trunk '$svnrepo' &&
+ git svn fetch
+ "
+
+test_expect_success 'add red branch' "
+ git config svn-remote.svn.branches 'branches/{red}:refs/remotes/*' &&
+ git svn fetch &&
+ git rev-parse refs/remotes/red &&
+ test_must_fail git rev-parse refs/remotes/green &&
+ test_must_fail git rev-parse refs/remotes/blue
+ "
+
+test_expect_success 'add green branch' "
+ GIT_CONFIG=.git/svn/.metadata git config --unset svn-remote.svn.branches-maxRev &&
+ git config svn-remote.svn.branches 'branches/{red,green}:refs/remotes/*' &&
+ git svn fetch &&
+ git rev-parse refs/remotes/red &&
+ git rev-parse refs/remotes/green &&
+ test_must_fail git rev-parse refs/remotes/blue
+ "
+
+test_expect_success 'add all branches' "
+ GIT_CONFIG=.git/svn/.metadata git config --unset svn-remote.svn.branches-maxRev &&
+ git config svn-remote.svn.branches 'branches/*:refs/remotes/*' &&
+ git svn fetch &&
+ git rev-parse refs/remotes/red &&
+ git rev-parse refs/remotes/green &&
+ git rev-parse refs/remotes/blue
+ "
+
+test_done
diff --git a/t/t9154/svn.dump b/t/t9154/svn.dump
new file mode 100644
index 0000000000..3dfabb67db
--- /dev/null
+++ b/t/t9154/svn.dump
@@ -0,0 +1,222 @@
+SVN-fs-dump-format-version: 2
+
+UUID: a18093a0-5f0b-44e0-8d88-8911ac7078db
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-01-23T07:40:25.660053Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 104
+Content-length: 104
+
+K 7
+svn:log
+V 7
+initial
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:41:33.636365Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Text-content-sha1: f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 2
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 12
+add branches
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:42:37.290694Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/blue
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/green
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/red
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 3
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 10
+red change
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:43:02.208918Z
+PROPS-END
+
+Node-path: branches/red/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 8
+Text-content-md5: 64c3c8cf7d0233ab7627623a68888bd1
+Text-content-sha1: 95a0492027876adfd3891ec71ee37b79ee44d640
+Content-length: 8
+
+foo
+red
+
+
+Revision-number: 4
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 12
+green change
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:43:15.746586Z
+PROPS-END
+
+Node-path: branches/green/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 0209b6450891abc033d5eaaa9d3a8023
+Text-content-sha1: 87fc3bef9faeec48c0cd61dfc9851db377fdccf7
+Content-length: 10
+
+foo
+green
+
+
+Revision-number: 5
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 11
+blue change
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:43:29.364811Z
+PROPS-END
+
+Node-path: branches/blue/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 9
+Text-content-md5: 9fbe4c13d0bae86386ae5209b2e6b275
+Text-content-sha1: cc4575083459a16f9aaef796c4a2456d64691ba0
+Content-length: 9
+
+foo
+blue
+
+
+Revision-number: 6
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 12
+trunk change
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:44:01.313130Z
+PROPS-END
+
+Node-path: trunk/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 1c4db977d7a57c3bae582aab87948516
+Text-content-sha1: 469c08df449e702cf2a1fe746244a9ef3f837fad
+Content-length: 10
+
+foo
+trunk
+
+
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
index b49815d108..60d6f5d1ba 100755
--- a/t/t9300-fast-import.sh
+++ b/t/t9300-fast-import.sh
@@ -1092,9 +1092,12 @@ test_expect_success 'P: fail on blob mark in gitlink' '
### series Q (notes)
###
-note1_data="Note for the first commit"
-note2_data="Note for the second commit"
-note3_data="Note for the third commit"
+note1_data="The first note for the first commit"
+note2_data="The first note for the second commit"
+note3_data="The first note for the third commit"
+note1b_data="The second note for the first commit"
+note1c_data="The third note for the first commit"
+note2b_data="The second note for the second commit"
test_tick
cat >input <<INPUT_END
@@ -1169,7 +1172,45 @@ data <<EOF
$note3_data
EOF
+commit refs/notes/foobar
+mark :10
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes (:10)
+COMMIT
+
+N inline :3
+data <<EOF
+$note1b_data
+EOF
+
+commit refs/notes/foobar2
+mark :11
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes (:11)
+COMMIT
+
+N inline :3
+data <<EOF
+$note1c_data
+EOF
+
+commit refs/notes/foobar
+mark :12
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes (:12)
+COMMIT
+
+deleteall
+N inline :5
+data <<EOF
+$note2b_data
+EOF
+
INPUT_END
+
test_expect_success \
'Q: commit notes' \
'git fast-import <input &&
@@ -1224,8 +1265,8 @@ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
notes (:9)
EOF
test_expect_success \
- 'Q: verify notes commit' \
- 'git cat-file commit refs/notes/foobar | sed 1d >actual &&
+ 'Q: verify first notes commit' \
+ 'git cat-file commit refs/notes/foobar~2 | sed 1d >actual &&
test_cmp expect actual'
cat >expect.unsorted <<EOF
@@ -1235,23 +1276,264 @@ cat >expect.unsorted <<EOF
EOF
cat expect.unsorted | sort >expect
test_expect_success \
- 'Q: verify notes tree' \
- 'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
+ 'Q: verify first notes tree' \
+ 'git cat-file -p refs/notes/foobar~2^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
test_cmp expect actual'
echo "$note1_data" >expect
test_expect_success \
- 'Q: verify note for first commit' \
- 'git cat-file blob refs/notes/foobar:$commit1 >actual && test_cmp expect actual'
+ 'Q: verify first note for first commit' \
+ 'git cat-file blob refs/notes/foobar~2:$commit1 >actual && test_cmp expect actual'
echo "$note2_data" >expect
test_expect_success \
- 'Q: verify note for second commit' \
- 'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'
+ 'Q: verify first note for second commit' \
+ 'git cat-file blob refs/notes/foobar~2:$commit2 >actual && test_cmp expect actual'
echo "$note3_data" >expect
test_expect_success \
- 'Q: verify note for third commit' \
- 'git cat-file blob refs/notes/foobar:$commit3 >actual && test_cmp expect actual'
+ 'Q: verify first note for third commit' \
+ 'git cat-file blob refs/notes/foobar~2:$commit3 >actual && test_cmp expect actual'
+
+cat >expect <<EOF
+parent `git rev-parse --verify refs/notes/foobar~2`
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+notes (:10)
+EOF
+test_expect_success \
+ 'Q: verify second notes commit' \
+ 'git cat-file commit refs/notes/foobar^ | sed 1d >actual &&
+ test_cmp expect actual'
+
+cat >expect.unsorted <<EOF
+100644 blob $commit1
+100644 blob $commit2
+100644 blob $commit3
+EOF
+cat expect.unsorted | sort >expect
+test_expect_success \
+ 'Q: verify second notes tree' \
+ 'git cat-file -p refs/notes/foobar^^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
+ test_cmp expect actual'
+
+echo "$note1b_data" >expect
+test_expect_success \
+ 'Q: verify second note for first commit' \
+ 'git cat-file blob refs/notes/foobar^:$commit1 >actual && test_cmp expect actual'
+
+echo "$note2_data" >expect
+test_expect_success \
+ 'Q: verify first note for second commit' \
+ 'git cat-file blob refs/notes/foobar^:$commit2 >actual && test_cmp expect actual'
+
+echo "$note3_data" >expect
+test_expect_success \
+ 'Q: verify first note for third commit' \
+ 'git cat-file blob refs/notes/foobar^:$commit3 >actual && test_cmp expect actual'
+
+cat >expect <<EOF
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+notes (:11)
+EOF
+test_expect_success \
+ 'Q: verify third notes commit' \
+ 'git cat-file commit refs/notes/foobar2 | sed 1d >actual &&
+ test_cmp expect actual'
+
+cat >expect.unsorted <<EOF
+100644 blob $commit1
+EOF
+cat expect.unsorted | sort >expect
+test_expect_success \
+ 'Q: verify third notes tree' \
+ 'git cat-file -p refs/notes/foobar2^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
+ test_cmp expect actual'
+
+echo "$note1c_data" >expect
+test_expect_success \
+ 'Q: verify third note for first commit' \
+ 'git cat-file blob refs/notes/foobar2:$commit1 >actual && test_cmp expect actual'
+
+cat >expect <<EOF
+parent `git rev-parse --verify refs/notes/foobar^`
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+notes (:12)
+EOF
+test_expect_success \
+ 'Q: verify fourth notes commit' \
+ 'git cat-file commit refs/notes/foobar | sed 1d >actual &&
+ test_cmp expect actual'
+
+cat >expect.unsorted <<EOF
+100644 blob $commit2
+EOF
+cat expect.unsorted | sort >expect
+test_expect_success \
+ 'Q: verify fourth notes tree' \
+ 'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
+ test_cmp expect actual'
+
+echo "$note2b_data" >expect
+test_expect_success \
+ 'Q: verify second note for second commit' \
+ 'git cat-file blob refs/notes/foobar:$commit2 >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/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh
new file mode 100755
index 0000000000..a5c99d8507
--- /dev/null
+++ b/t/t9301-fast-import-notes.sh
@@ -0,0 +1,623 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Johan Herland
+#
+
+test_description='test git fast-import of notes objects'
+. ./test-lib.sh
+
+
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+first commit
+COMMIT
+
+M 644 inline foo
+data <<EOF
+file foo in first commit
+EOF
+
+M 755 inline bar
+data <<EOF
+file bar in first commit
+EOF
+
+M 644 inline baz/xyzzy
+data <<EOF
+file baz/xyzzy in first commit
+EOF
+
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+second commit
+COMMIT
+
+M 644 inline foo
+data <<EOF
+file foo in second commit
+EOF
+
+M 755 inline baz/xyzzy
+data <<EOF
+file baz/xyzzy in second commit
+EOF
+
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+third commit
+COMMIT
+
+M 644 inline foo
+data <<EOF
+file foo in third commit
+EOF
+
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+fourth commit
+COMMIT
+
+M 755 inline bar
+data <<EOF
+file bar in fourth commit
+EOF
+
+INPUT_END
+
+test_expect_success 'set up master branch' '
+
+ git fast-import <input &&
+ git whatchanged master
+'
+
+commit4=$(git rev-parse refs/heads/master)
+commit3=$(git rev-parse "$commit4^")
+commit2=$(git rev-parse "$commit4~2")
+commit1=$(git rev-parse "$commit4~3")
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+first notes commit
+COMMIT
+
+M 644 inline $commit1
+data <<EOF
+first note for first commit
+EOF
+
+M 755 inline $commit2
+data <<EOF
+first note for second commit
+EOF
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+ fourth commit
+ third commit
+ second commit
+ first note for second commit
+ first commit
+ first note for first commit
+EXPECT_END
+
+test_expect_success 'add notes with simple M command' '
+
+ git fast-import <input &&
+ GIT_NOTES_REF=refs/notes/test git log | grep "^ " > actual &&
+ test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+second notes commit
+COMMIT
+
+from refs/notes/test^0
+N inline $commit3
+data <<EOF
+first note for third commit
+EOF
+
+N inline $commit4
+data <<EOF
+first note for fourth commit
+EOF
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+ fourth commit
+ first note for fourth commit
+ third commit
+ first note for third commit
+ second commit
+ first note for second commit
+ first commit
+ first note for first commit
+EXPECT_END
+
+test_expect_success 'add notes with simple N command' '
+
+ git fast-import <input &&
+ GIT_NOTES_REF=refs/notes/test git log | grep "^ " > actual &&
+ test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+third notes commit
+COMMIT
+
+from refs/notes/test^0
+N inline $commit1
+data <<EOF
+second note for first commit
+EOF
+
+N inline $commit2
+data <<EOF
+second note for second commit
+EOF
+
+N inline $commit3
+data <<EOF
+second note for third commit
+EOF
+
+N inline $commit4
+data <<EOF
+second note for fourth commit
+EOF
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+ fourth commit
+ second note for fourth commit
+ third commit
+ second note for third commit
+ second commit
+ second note for second commit
+ first commit
+ second note for first commit
+EXPECT_END
+
+test_expect_success 'update existing notes with N command' '
+
+ git fast-import <input &&
+ GIT_NOTES_REF=refs/notes/test git log | grep "^ " > actual &&
+ test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+fourth notes commit
+COMMIT
+
+from refs/notes/test^0
+M 644 inline $(echo "$commit3" | sed "s|^..|&/|")
+data <<EOF
+prefix of note for third commit
+EOF
+
+M 644 inline $(echo "$commit4" | sed "s|^..|&/|")
+data <<EOF
+prefix of note for fourth commit
+EOF
+
+M 644 inline $(echo "$commit4" | sed "s|^\(..\)\(..\)|\1/\2/|")
+data <<EOF
+pre-prefix of note for fourth commit
+EOF
+
+N inline $commit1
+data <<EOF
+third note for first commit
+EOF
+
+N inline $commit2
+data <<EOF
+third note for second commit
+EOF
+
+N inline $commit3
+data <<EOF
+third note for third commit
+EOF
+
+N inline $commit4
+data <<EOF
+third note for fourth commit
+EOF
+
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+ fourth commit
+ pre-prefix of note for fourth commit
+ prefix of note for fourth commit
+ third note for fourth commit
+ third commit
+ prefix of note for third commit
+ third note for third commit
+ second commit
+ third note for second commit
+ first commit
+ third note for first commit
+EXPECT_END
+
+test_expect_success 'add concatentation notes with M command' '
+
+ git fast-import <input &&
+ GIT_NOTES_REF=refs/notes/test git log | grep "^ " > actual &&
+ test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+fifth notes commit
+COMMIT
+
+from refs/notes/test^0
+deleteall
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+ fourth commit
+ third commit
+ second commit
+ first commit
+EXPECT_END
+
+test_expect_success 'verify that deleteall also removes notes' '
+
+ git fast-import <input &&
+ GIT_NOTES_REF=refs/notes/test git log | grep "^ " > actual &&
+ test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+sixth notes commit
+COMMIT
+
+from refs/notes/test^0
+M 644 inline $commit1
+data <<EOF
+third note for first commit
+EOF
+
+M 644 inline $commit3
+data <<EOF
+third note for third commit
+EOF
+
+N inline $commit1
+data <<EOF
+fourth note for first commit
+EOF
+
+N inline $commit3
+data <<EOF
+fourth note for third commit
+EOF
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+ fourth commit
+ third commit
+ fourth note for third commit
+ second commit
+ first commit
+ fourth note for first commit
+EXPECT_END
+
+test_expect_success 'verify that later N commands override earlier M commands' '
+
+ git fast-import <input &&
+ GIT_NOTES_REF=refs/notes/test git log | grep "^ " > actual &&
+ test_cmp expect actual
+
+'
+
+# Write fast-import commands to create the given number of commits
+fast_import_commits () {
+ my_ref=$1
+ my_num_commits=$2
+ my_append_to_file=$3
+ my_i=0
+ while test $my_i -lt $my_num_commits
+ do
+ my_i=$(($my_i + 1))
+ test_tick
+ cat >>"$my_append_to_file" <<INPUT_END
+commit $my_ref
+mark :$my_i
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit #$my_i
+COMMIT
+
+M 644 inline file
+data <<EOF
+file contents in commit #$my_i
+EOF
+
+INPUT_END
+ done
+}
+
+# Write fast-import commands to create the given number of notes annotating
+# the commits created by fast_import_commits()
+fast_import_notes () {
+ my_notes_ref=$1
+ my_num_commits=$2
+ my_append_to_file=$3
+ my_note_append=$4
+ test_tick
+ cat >>"$my_append_to_file" <<INPUT_END
+commit $my_notes_ref
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+committing $my_num_commits notes
+COMMIT
+
+INPUT_END
+
+ my_i=0
+ while test $my_i -lt $my_num_commits
+ do
+ my_i=$(($my_i + 1))
+ cat >>"$my_append_to_file" <<INPUT_END
+N inline :$my_i
+data <<EOF
+note for commit #$my_i$my_note_append
+EOF
+
+INPUT_END
+ done
+}
+
+
+rm input expect
+num_commits=400
+# Create lots of commits
+fast_import_commits "refs/heads/many_commits" $num_commits input
+# Create one note per above commit
+fast_import_notes "refs/notes/many_notes" $num_commits input
+# Add a couple of non-notes as well
+test_tick
+cat >>input <<INPUT_END
+commit refs/notes/many_notes
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+committing some non-notes to the notes tree
+COMMIT
+
+M 755 inline foobar/non-note.txt
+data <<EOF
+This is not a note, but rather a regular file residing in a notes tree
+EOF
+
+M 644 inline deadbeef
+data <<EOF
+Non-note file
+EOF
+
+M 644 inline de/adbeef
+data <<EOF
+Another non-note file
+EOF
+
+INPUT_END
+# Finally create the expected output from all these notes and commits
+i=$num_commits
+while test $i -gt 0
+do
+ cat >>expect <<EXPECT_END
+ commit #$i
+ note for commit #$i
+EXPECT_END
+ i=$(($i - 1))
+done
+
+test_expect_success 'add lots of commits and notes' '
+
+ git fast-import <input &&
+ GIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits |
+ grep "^ " > actual &&
+ test_cmp expect actual
+
+'
+
+test_expect_success 'verify that lots of notes trigger a fanout scheme' '
+
+ # None of the entries in the top-level notes tree should be a full SHA1
+ git ls-tree --name-only refs/notes/many_notes |
+ while read path
+ do
+ if test $(expr length "$path") -ge 40
+ then
+ return 1
+ fi
+ done
+
+'
+
+cat >>expect_non-note1 << EOF
+This is not a note, but rather a regular file residing in a notes tree
+EOF
+
+cat >>expect_non-note2 << EOF
+Non-note file
+EOF
+
+cat >>expect_non-note3 << EOF
+Another non-note file
+EOF
+
+test_expect_success 'verify that non-notes are untouched by a fanout change' '
+
+ git cat-file -p refs/notes/many_notes:foobar/non-note.txt > actual &&
+ test_cmp expect_non-note1 actual &&
+ git cat-file -p refs/notes/many_notes:deadbeef > actual &&
+ test_cmp expect_non-note2 actual &&
+ git cat-file -p refs/notes/many_notes:de/adbeef > actual &&
+ test_cmp expect_non-note3 actual
+
+'
+remaining_notes=10
+test_tick
+cat >>input <<INPUT_END
+commit refs/notes/many_notes
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+removing all notes but $remaining_notes
+COMMIT
+from refs/notes/many_notes^0
+INPUT_END
+
+i=$remaining_notes
+while test $i -lt $num_commits
+do
+ i=$(($i + 1))
+ cat >>input <<INPUT_END
+N 0000000000000000000000000000000000000000 :$i
+INPUT_END
+done
+
+i=$num_commits
+rm expect
+while test $i -gt 0
+do
+ cat >>expect <<EXPECT_END
+ commit #$i
+EXPECT_END
+ if test $i -le $remaining_notes
+ then
+ cat >>expect <<EXPECT_END
+ note for commit #$i
+EXPECT_END
+ fi
+ i=$(($i - 1))
+done
+
+test_expect_success 'remove lots of notes' '
+
+ git fast-import <input &&
+ GIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits |
+ grep "^ " > actual &&
+ test_cmp expect actual
+
+'
+
+test_expect_success 'verify that removing notes trigger fanout consolidation' '
+
+ # All entries in the top-level notes tree should be a full SHA1
+ git ls-tree --name-only -r refs/notes/many_notes |
+ while read path
+ do
+ # Explicitly ignore the non-note paths
+ test "$path" = "foobar/non-note.txt" && continue
+ test "$path" = "deadbeef" && continue
+ test "$path" = "de/adbeef" && continue
+
+ if test $(expr length "$path") -ne 40
+ then
+ return 1
+ fi
+ done
+
+'
+
+test_expect_success 'verify that non-notes are untouched by a fanout change' '
+
+ git cat-file -p refs/notes/many_notes:foobar/non-note.txt > actual &&
+ test_cmp expect_non-note1 actual &&
+ git cat-file -p refs/notes/many_notes:deadbeef > actual &&
+ test_cmp expect_non-note2 actual &&
+ git cat-file -p refs/notes/many_notes:de/adbeef > actual &&
+ test_cmp expect_non-note3 actual
+
+'
+
+
+rm input expect
+num_notes_refs=10
+num_commits=16
+some_commits=8
+# Create commits
+fast_import_commits "refs/heads/more_commits" $num_commits input
+# Create one note per above commit per notes ref
+i=0
+while test $i -lt $num_notes_refs
+do
+ i=$(($i + 1))
+ fast_import_notes "refs/notes/more_notes_$i" $num_commits input
+done
+# Trigger branch reloading in git-fast-import by repeating the note creation
+i=0
+while test $i -lt $num_notes_refs
+do
+ i=$(($i + 1))
+ fast_import_notes "refs/notes/more_notes_$i" $some_commits input " (2)"
+done
+# Finally create the expected output from the notes in refs/notes/more_notes_1
+i=$num_commits
+while test $i -gt 0
+do
+ note_data="note for commit #$i"
+ if test $i -le $some_commits
+ then
+ note_data="$note_data (2)"
+ fi
+ cat >>expect <<EXPECT_END
+ commit #$i
+ $note_data
+EXPECT_END
+ i=$(($i - 1))
+done
+
+test_expect_success "add notes to $num_commits commits in each of $num_notes_refs refs" '
+
+ git fast-import --active-branches=5 <input &&
+ GIT_NOTES_REF=refs/notes/more_notes_1 git log refs/heads/more_commits |
+ grep "^ " > actual &&
+ test_cmp expect actual
+
+'
+
+test_done
diff --git a/t/t9301-fast-export.sh b/t/t9350-fast-export.sh
index 356964e53a..356964e53a 100755
--- a/t/t9301-fast-export.sh
+++ b/t/t9350-fast-export.sh
diff --git a/test-index-version.c b/test-index-version.c
new file mode 100644
index 0000000000..bfaad9e09e
--- /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/test-run-command.c b/test-run-command.c
new file mode 100644
index 0000000000..0612bfa7cd
--- /dev/null
+++ b/test-run-command.c
@@ -0,0 +1,35 @@
+/*
+ * test-run-command.c: test run command API.
+ *
+ * (C) 2009 Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "git-compat-util.h"
+#include "run-command.h"
+#include <string.h>
+#include <errno.h>
+
+int main(int argc, char **argv)
+{
+ struct child_process proc;
+
+ memset(&proc, 0, sizeof(proc));
+
+ if (argc < 3)
+ return 1;
+ proc.argv = (const char **)argv+2;
+
+ if (!strcmp(argv[1], "start-command-ENOENT")) {
+ if (start_command(&proc) < 0 && errno == ENOENT)
+ return 0;
+ fprintf(stderr, "FAIL %s\n", argv[1]);
+ return 1;
+ }
+
+ fprintf(stderr, "check usage\n");
+ return 1;
+}
diff --git a/transport-helper.c b/transport-helper.c
index 11f3d7ec52..107742891f 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,8 @@ static struct child_process *get_helper(struct transport *transport)
const char **refspecs = NULL;
int refspec_nr = 0;
int refspec_alloc = 0;
+ int duped;
+ int code;
if (data->helper)
return data->helper;
@@ -39,37 +112,68 @@ static struct child_process *get_helper(struct transport *transport)
helper->out = -1;
helper->err = 0;
helper->argv = xcalloc(4, sizeof(*helper->argv));
- strbuf_addf(&buf, "remote-%s", data->name);
+ strbuf_addf(&buf, "git-remote-%s", data->name);
helper->argv[0] = strbuf_detach(&buf, NULL);
helper->argv[1] = transport->remote->name;
- helper->argv[2] = transport->url;
- helper->git_cmd = 1;
- if (start_command(helper))
- die("Unable to run helper: git %s", helper->argv[0]);
+ helper->argv[2] = remove_ext_force(transport->url);
+ helper->git_cmd = 0;
+ helper->silent_exec_failure = 1;
+ code = start_command(helper);
+ if (code < 0 && errno == ENOENT)
+ die("Unable to find remote helper for '%s'", data->name);
+ else if (code != 0)
+ exit(code);
+
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 +186,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 +231,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 +258,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 +279,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 +318,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 +356,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 +372,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 +396,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,24 +529,32 @@ static int push_refs(struct transport *transport,
struct child_process *helper;
struct ref *ref;
- if (!remote_refs)
+ if (process_connect(transport, 1)) {
+ do_take_over(transport);
+ return transport->push_refs(transport, remote_refs, flags);
+ }
+
+ if (!remote_refs) {
+ fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
+ "Perhaps you should specify a branch such as 'master'.\n");
return 0;
+ }
helper = get_helper(transport);
if (!data->push)
return 1;
for (ref = remote_refs; ref; ref = ref->next) {
- if (ref->peer_ref)
- hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
- else if (!mirror)
+ if (!ref->peer_ref && !mirror)
continue;
- ref->deletion = is_null_sha1(ref->new_sha1);
- if (!ref->deletion &&
- !hashcmp(ref->old_sha1, ref->new_sha1)) {
- ref->status = REF_STATUS_UPTODATE;
+ /* Check for statuses set by set_ref_status_for_push() */
+ switch (ref->status) {
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ case REF_STATUS_UPTODATE:
continue;
+ default:
+ ; /* do nothing */
}
if (force_all)
@@ -369,17 +585,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;
@@ -430,6 +643,15 @@ static int push_refs(struct transport *transport,
continue;
}
+ if (ref->status != REF_STATUS_NONE) {
+ /*
+ * Earlier, the ref was marked not to be pushed, so ignore the ref
+ * status reported by the remote helper if the latter is 'no match'.
+ */
+ if (status == REF_STATUS_NONE)
+ continue;
+ }
+
ref->status = status;
ref->remote_status = msg;
}
@@ -464,6 +686,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 +698,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 +723,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 +738,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 3eea836a33..7714fdb6c6 100644
--- a/transport.c
+++ b/transport.c
@@ -8,6 +8,7 @@
#include "bundle.h"
#include "dir.h"
#include "refs.h"
+#include "branch.h"
/* rsync support */
@@ -135,6 +136,53 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list)
}
}
+static void set_upstreams(struct transport *transport, struct ref *refs,
+ int pretend)
+{
+ struct ref *ref;
+ for (ref = refs; ref; ref = ref->next) {
+ const char *localname;
+ const char *tmp;
+ const char *remotename;
+ unsigned char sha[20];
+ int flag = 0;
+ /*
+ * Check suitability for tracking. Must be successful /
+ * already up-to-date ref create/modify (not delete).
+ */
+ if (ref->status != REF_STATUS_OK &&
+ ref->status != REF_STATUS_UPTODATE)
+ continue;
+ if (!ref->peer_ref)
+ continue;
+ if (!ref->new_sha1 || is_null_sha1(ref->new_sha1))
+ continue;
+
+ /* Follow symbolic refs (mainly for HEAD). */
+ localname = ref->peer_ref->name;
+ remotename = ref->name;
+ tmp = resolve_ref(localname, sha, 1, &flag);
+ if (tmp && flag & REF_ISSYMREF &&
+ !prefixcmp(tmp, "refs/heads/"))
+ localname = tmp;
+
+ /* Both source and destination must be local branches. */
+ if (!localname || prefixcmp(localname, "refs/heads/"))
+ continue;
+ if (!remotename || prefixcmp(remotename, "refs/heads/"))
+ continue;
+
+ if (!pretend)
+ install_branch_config(BRANCH_CONFIG_VERBOSE,
+ localname + 11, transport->remote->name,
+ remotename);
+ else
+ printf("Would set upstream of '%s' to '%s' of '%s'\n",
+ localname + 11, remotename + 11,
+ transport->remote->name);
+ }
+}
+
static const char *rsync_url(const char *url)
{
return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url;
@@ -143,7 +191,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 +443,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 +481,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 +501,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 +519,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 +546,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 +775,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 +799,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 +830,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 +872,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 +923,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 +938,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 +994,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 +1021,13 @@ int transport_push(struct transport *transport,
*nonfastforward = 0;
verify_remote_names(refspec_nr, refspec);
- if (transport->push)
+ if (transport->push) {
+ /* Maybe FIXME. But no important transport uses this case. */
+ if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
+ die("This transport does not support using --set-upstream");
+
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();
@@ -875,7 +1035,8 @@ int transport_push(struct transport *transport,
int verbose = flags & TRANSPORT_PUSH_VERBOSE;
int quiet = flags & TRANSPORT_PUSH_QUIET;
int porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
- int ret;
+ int pretend = flags & TRANSPORT_PUSH_DRY_RUN;
+ int ret, err;
if (flags & TRANSPORT_PUSH_ALL)
match_flags |= MATCH_REFS_ALL;
@@ -887,13 +1048,23 @@ int transport_push(struct transport *transport,
return -1;
}
+ set_ref_status_for_push(remote_refs,
+ flags & TRANSPORT_PUSH_MIRROR,
+ flags & TRANSPORT_PUSH_FORCE);
+
ret = transport->push_refs(transport, remote_refs, flags);
+ err = push_had_errors(remote_refs);
+
+ ret |= err;
- if (!quiet || push_had_errors(remote_refs))
+ if (!quiet || err)
print_push_status(transport->url, remote_refs,
verbose | porcelain, porcelain,
nonfastforward);
+ if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
+ set_upstreams(transport, remote_refs, pretend);
+
if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
struct ref *ref;
for (ref = remote_refs; ref; ref = ref->next)
@@ -911,6 +1082,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 +1117,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)
}
rc = transport->fetch(transport, nr_heads, heads);
+
free(heads);
return rc;
}
@@ -958,6 +1131,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 9e74406fa2..7cea5cc723 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
@@ -74,6 +91,7 @@ struct transport {
#define TRANSPORT_PUSH_VERBOSE 16
#define TRANSPORT_PUSH_PORCELAIN 32
#define TRANSPORT_PUSH_QUIET 64
+#define TRANSPORT_PUSH_SET_UPSTREAM 128
/* Returns a transport suitable for the url */
struct transport *transport_get(struct remote *, const char *);
@@ -115,6 +133,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/tree-diff.c b/tree-diff.c
index 7d745b4406..fe9f52c479 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -68,7 +68,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
newbase[baselen + pathlen1] = 0;
opt->change(opt, mode1, mode2,
- sha1, sha2, newbase);
+ sha1, sha2, newbase, 0, 0);
newbase[baselen + pathlen1] = '/';
}
retval = diff_tree_sha1(sha1, sha2, newbase, opt);
@@ -77,7 +77,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
}
fullname = malloc_fullname(base, baselen, path1, pathlen1);
- opt->change(opt, mode1, mode2, sha1, sha2, fullname);
+ opt->change(opt, mode1, mode2, sha1, sha2, fullname, 0, 0);
free(fullname);
return 0;
}
@@ -241,7 +241,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
newbase[baselen + pathlen] = 0;
- opt->add_remove(opt, *prefix, mode, sha1, newbase);
+ opt->add_remove(opt, *prefix, mode, sha1, newbase, 0);
newbase[baselen + pathlen] = '/';
}
@@ -252,7 +252,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
free(newbase);
} else {
char *fullname = malloc_fullname(base, baselen, path, pathlen);
- opt->add_remove(opt, prefix[0], mode, sha1, fullname);
+ opt->add_remove(opt, prefix[0], mode, sha1, fullname, 0);
free(fullname);
}
}
diff --git a/unpack-trees.c b/unpack-trees.c
index dd5999c356..0ddbef3e63 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 d19df44f40..95ff36c824 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,
diff --git a/userdiff.c b/userdiff.c
index 57529ae63d..df992490d5 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -198,7 +198,7 @@ struct userdiff_driver *userdiff_find_by_path(const char *path)
struct git_attr_check check;
if (!attr)
- attr = git_attr("diff", 4);
+ attr = git_attr("diff");
check.attr = attr;
if (!path)
diff --git a/utf8.c b/utf8.c
index 7ddff23fa7..ab326ac83e 100644
--- a/utf8.c
+++ b/utf8.c
@@ -163,7 +163,7 @@ static int git_wcwidth(ucs_char_t ch)
* If the string was not a valid UTF-8, *start pointer is set to NULL
* and the return value is undefined.
*/
-ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p)
+static ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p)
{
unsigned char *s = (unsigned char *)*start;
ucs_char_t ch;
diff --git a/utf8.h b/utf8.h
index ae30ae4c6e..c9738d83d9 100644
--- a/utf8.h
+++ b/utf8.h
@@ -3,7 +3,6 @@
typedef unsigned int ucs_char_t; /* assuming 32bit int */
-ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p);
int utf8_width(const char **start, size_t *remainder_p);
int utf8_strwidth(const char *string);
int is_utf8(const char *text);
diff --git a/ws.c b/ws.c
index 760b5743fa..c0893386e6 100644
--- a/ws.c
+++ b/ws.c
@@ -64,7 +64,7 @@ static void setup_whitespace_attr_check(struct git_attr_check *check)
static struct git_attr *attr_whitespace;
if (!attr_whitespace)
- attr_whitespace = git_attr("whitespace", 10);
+ attr_whitespace = git_attr("whitespace");
check[0].attr = attr_whitespace;
}
diff --git a/wt-status.c b/wt-status.c
index 5d56988016..5807fc3211 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -343,7 +343,7 @@ static void wt_status_collect_untracked(struct wt_status *s)
DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
setup_standard_excludes(&dir);
- fill_directory(&dir, NULL);
+ fill_directory(&dir, s->pathspec);
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
if (!cache_name_is_other(ent->name, ent->len))
@@ -459,7 +459,7 @@ static void wt_status_print_changed(struct wt_status *s)
wt_status_print_trailer(s);
}
-static void wt_status_print_submodule_summary(struct wt_status *s)
+static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted)
{
struct child_process sm_summary;
char summary_limit[64];
@@ -468,11 +468,11 @@ static void wt_status_print_submodule_summary(struct wt_status *s)
const char *argv[] = {
"submodule",
"summary",
- "--cached",
+ uncommitted ? "--files" : "--cached",
"--for-status",
"--summary-limit",
summary_limit,
- s->amend ? "HEAD^" : "HEAD",
+ uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD"),
NULL
};
@@ -580,8 +580,10 @@ void wt_status_print(struct wt_status *s)
wt_status_print_updated(s);
wt_status_print_unmerged(s);
wt_status_print_changed(s);
- if (s->submodule_summary)
- wt_status_print_submodule_summary(s);
+ if (s->submodule_summary) {
+ wt_status_print_submodule_summary(s, 0); /* staged */
+ wt_status_print_submodule_summary(s, 1); /* unstaged */
+ }
if (s->show_untracked_files)
wt_status_print_untracked(s);
else if (s->commitable)
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h
index 4da052a3ff..3f6229edbe 100644
--- a/xdiff/xdiff.h
+++ b/xdiff/xdiff.h
@@ -58,6 +58,12 @@ extern "C" {
#define XDL_MERGE_ZEALOUS_ALNUM 3
#define XDL_MERGE_LEVEL_MASK 0x0f
+/* merge favor modes */
+#define XDL_MERGE_FAVOR_OURS 1
+#define XDL_MERGE_FAVOR_THEIRS 2
+#define XDL_MERGE_FAVOR(flags) (((flags)>>4) & 3)
+#define XDL_MERGE_FLAGS(level, style, favor) ((level)|(style)|((favor)<<4))
+
/* merge output styles */
#define XDL_MERGE_DIFF3 0x8000
#define XDL_MERGE_STYLE_MASK 0x8000
@@ -108,9 +114,16 @@ long xdl_mmfile_size(mmfile_t *mmf);
int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
xdemitconf_t const *xecfg, xdemitcb_t *ecb);
+typedef struct s_xmparam {
+ xpparam_t xpp;
+ int marker_size;
+} xmparam_t;
+
+#define DEFAULT_CONFLICT_MARKER_SIZE 7
+
int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
mmfile_t *mf2, const char *name2,
- xpparam_t const *xpp, int level, mmbuffer_t *result);
+ xmparam_t const *xmp, int flags, mmbuffer_t *result);
#ifdef __cplusplus
}
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c
index 1cb65a9516..8cbe45e675 100644
--- a/xdiff/xmerge.c
+++ b/xdiff/xmerge.c
@@ -145,13 +145,15 @@ static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest)
static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
xdfenv_t *xe2, const char *name2,
int size, int i, int style,
- xdmerge_t *m, char *dest)
+ xdmerge_t *m, char *dest, int marker_size)
{
- const int marker_size = 7;
int marker1_size = (name1 ? strlen(name1) + 1 : 0);
int marker2_size = (name2 ? strlen(name2) + 1 : 0);
int j;
+ if (marker_size <= 0)
+ marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+
/* Before conflicting part */
size += xdl_recs_copy(xe1, i, m->i1 - i, 0,
dest ? dest + size : NULL);
@@ -214,14 +216,20 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
xdfenv_t *xe2, const char *name2,
- xdmerge_t *m, char *dest, int style)
+ int favor,
+ xdmerge_t *m, char *dest, int style,
+ int marker_size)
{
int size, i;
for (size = i = 0; m; m = m->next) {
+ if (favor && !m->mode)
+ m->mode = favor;
+
if (m->mode == 0)
size = fill_conflict_hunk(xe1, name1, xe2, name2,
- size, i, style, m, dest);
+ size, i, style, m, dest,
+ marker_size);
else if (m->mode == 1)
size += xdl_recs_copy(xe1, i, m->i1 + m->chg1 - i, 0,
dest ? dest + size : NULL);
@@ -386,11 +394,13 @@ static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m,
*/
static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
xdfenv_t *xe2, xdchange_t *xscr2, const char *name2,
- int flags, xpparam_t const *xpp, mmbuffer_t *result) {
+ int flags, xmparam_t const *xmp, mmbuffer_t *result) {
xdmerge_t *changes, *c;
+ xpparam_t const *xpp = &xmp->xpp;
int i0, i1, i2, chg0, chg1, chg2;
int level = flags & XDL_MERGE_LEVEL_MASK;
int style = flags & XDL_MERGE_STYLE_MASK;
+ int favor = XDL_MERGE_FAVOR(flags);
if (style == XDL_MERGE_DIFF3) {
/*
@@ -522,26 +532,29 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
}
/* output */
if (result) {
+ int marker_size = xmp->marker_size;
int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2,
- changes, NULL, style);
+ favor, changes, NULL, style,
+ marker_size);
result->ptr = xdl_malloc(size);
if (!result->ptr) {
xdl_cleanup_merge(changes);
return -1;
}
result->size = size;
- xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes,
- result->ptr, style);
+ xdl_fill_merge_buffer(xe1, name1, xe2, name2, favor, changes,
+ result->ptr, style, marker_size);
}
return xdl_cleanup_merge(changes);
}
int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
mmfile_t *mf2, const char *name2,
- xpparam_t const *xpp, int flags, mmbuffer_t *result) {
+ xmparam_t const *xmp, int flags, mmbuffer_t *result) {
xdchange_t *xscr1, *xscr2;
xdfenv_t xe1, xe2;
int status;
+ xpparam_t const *xpp = &xmp->xpp;
result->ptr = NULL;
result->size = 0;
@@ -574,7 +587,7 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
} else {
status = xdl_do_merge(&xe1, xscr1, name1,
&xe2, xscr2, name2,
- flags, xpp, result);
+ flags, xmp, result);
}
xdl_free_script(xscr1);
xdl_free_script(xscr2);