summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore357
-rw-r--r--Documentation/RelNotes-1.6.5.3.txt63
-rw-r--r--Documentation/RelNotes-1.6.6.txt75
-rw-r--r--Documentation/config.txt73
-rw-r--r--Documentation/diff-options.txt61
-rw-r--r--Documentation/fetch-options.txt18
-rw-r--r--Documentation/git-add.txt8
-rw-r--r--Documentation/git-commit.txt2
-rw-r--r--Documentation/git-cvsserver.txt19
-rw-r--r--Documentation/git-describe.txt9
-rw-r--r--Documentation/git-difftool.txt2
-rw-r--r--Documentation/git-fast-import.txt45
-rw-r--r--Documentation/git-fetch.txt14
-rw-r--r--Documentation/git-filter-branch.txt13
-rw-r--r--Documentation/git-format-patch.txt46
-rw-r--r--Documentation/git-http-backend.txt178
-rw-r--r--Documentation/git-http-push.txt4
-rw-r--r--Documentation/git-ls-files.txt6
-rw-r--r--Documentation/git-mergetool.txt2
-rw-r--r--Documentation/git-notes.txt60
-rw-r--r--Documentation/git-pack-objects.txt12
-rw-r--r--Documentation/git-push.txt14
-rw-r--r--Documentation/git-read-tree.txt2
-rw-r--r--Documentation/git-rebase.txt16
-rw-r--r--Documentation/git-receive-pack.txt2
-rw-r--r--Documentation/git-remote-helpers.txt85
-rw-r--r--Documentation/git-remote.txt7
-rw-r--r--Documentation/git-replace.txt24
-rw-r--r--Documentation/git-reset.txt4
-rw-r--r--Documentation/git-send-email.txt4
-rw-r--r--Documentation/git-send-pack.txt4
-rw-r--r--Documentation/git-show-ref.txt3
-rw-r--r--Documentation/git-submodule.txt8
-rw-r--r--Documentation/git-svn.txt17
-rw-r--r--Documentation/git-update-index.txt6
-rw-r--r--Documentation/git-var.txt14
-rw-r--r--Documentation/git.txt3
-rw-r--r--Documentation/gitcore-tutorial.txt24
-rw-r--r--Documentation/githooks.txt2
-rw-r--r--Documentation/gitworkflows.txt115
-rw-r--r--Documentation/glossary-content.txt4
-rw-r--r--Documentation/howto/maintain-git.txt2
-rw-r--r--Documentation/howto/revert-branch-rebase.txt4
-rw-r--r--Documentation/howto/update-hook-example.txt2
-rw-r--r--Documentation/merge-config.txt2
-rw-r--r--Documentation/merge-options.txt5
-rw-r--r--Documentation/merge-strategies.txt5
-rw-r--r--Documentation/pretty-formats.txt5
-rw-r--r--Documentation/pull-fetch-param.txt11
-rw-r--r--Documentation/technical/api-history-graph.txt5
-rw-r--r--Documentation/technical/pack-protocol.txt535
-rw-r--r--Documentation/technical/protocol-capabilities.txt187
-rw-r--r--Documentation/technical/protocol-common.txt96
-rw-r--r--Documentation/user-manual.txt18
-rw-r--r--Makefile64
-rw-r--r--builtin-blame.c6
-rw-r--r--builtin-check-ref-format.c9
-rw-r--r--builtin-commit-tree.c2
-rw-r--r--builtin-commit.c4
-rw-r--r--builtin-describe.c43
-rw-r--r--builtin-fetch-pack.c210
-rw-r--r--builtin-fetch.c221
-rw-r--r--builtin-fsck.c2
-rw-r--r--builtin-grep.c19
-rw-r--r--builtin-log.c30
-rw-r--r--builtin-ls-files.c8
-rw-r--r--builtin-ls-remote.c6
-rw-r--r--builtin-ls-tree.c100
-rw-r--r--builtin-mailinfo.c12
-rw-r--r--builtin-mailsplit.c2
-rw-r--r--builtin-merge-ours.c6
-rw-r--r--builtin-merge-recursive.c2
-rw-r--r--builtin-merge.c20
-rw-r--r--builtin-mv.c8
-rw-r--r--builtin-pack-objects.c14
-rw-r--r--builtin-prune-packed.c2
-rw-r--r--builtin-push.c5
-rw-r--r--builtin-read-tree.c4
-rw-r--r--builtin-receive-pack.c30
-rw-r--r--builtin-reflog.c3
-rw-r--r--builtin-remote.c192
-rw-r--r--builtin-rerere.c3
-rw-r--r--builtin-rev-list.c2
-rw-r--r--builtin-rev-parse.c21
-rw-r--r--builtin-send-pack.c120
-rw-r--r--builtin-show-ref.c10
-rw-r--r--builtin-stripspace.c4
-rw-r--r--builtin-tar-tree.c6
-rw-r--r--builtin-update-index.c1
-rw-r--r--builtin-upload-archive.c15
-rw-r--r--builtin.h1
-rw-r--r--bundle.c2
-rw-r--r--cache.h13
-rw-r--r--color.h2
-rw-r--r--command-list.txt1
-rw-r--r--commit.c11
-rw-r--r--commit.h2
-rw-r--r--compat/mingw.c12
-rw-r--r--compat/mingw.h23
-rw-r--r--compat/vcbuild/scripts/clink.pl3
-rw-r--r--compat/win32mmap.c12
-rw-r--r--config.c17
-rw-r--r--configure.ac43
-rw-r--r--connect.c22
-rw-r--r--contrib/buildsystems/engine.pl3
-rwxr-xr-xcontrib/completion/git-completion.bash80
-rw-r--r--contrib/examples/builtin-fetch--tool.c (renamed from builtin-fetch--tool.c)8
-rwxr-xr-xcontrib/examples/git-merge.sh8
-rwxr-xr-xcontrib/examples/git-resolve.sh2
-rwxr-xr-xcontrib/fast-import/git-p45
-rwxr-xr-xcontrib/hooks/post-receive-email6
-rw-r--r--daemon.c51
-rw-r--r--diff-lib.c2
-rw-r--r--diff-no-index.c4
-rw-r--r--diff.c20
-rw-r--r--diffcore-break.c6
-rw-r--r--diffcore-rename.c7
-rw-r--r--editor.c32
-rw-r--r--environment.c3
-rw-r--r--fast-import.c91
-rw-r--r--fetch-pack.h3
-rw-r--r--fsck.h1
-rwxr-xr-xgit-add--interactive.perl21
-rwxr-xr-xgit-am.sh18
-rwxr-xr-xgit-bisect.sh3
-rw-r--r--git-compat-util.h1
-rwxr-xr-xgit-filter-branch.sh40
-rwxr-xr-xgit-instaweb.sh5
-rwxr-xr-xgit-merge-octopus.sh2
-rwxr-xr-xgit-merge-one-file.sh12
-rw-r--r--git-mergetool--lib.sh17
-rwxr-xr-xgit-notes.sh121
-rwxr-xr-xgit-pull.sh9
-rwxr-xr-xgit-rebase--interactive.sh6
-rwxr-xr-xgit-rebase.sh2
-rwxr-xr-xgit-send-email.perl3
-rwxr-xr-xgit-sh-setup.sh19
-rwxr-xr-xgit-submodule.sh7
-rwxr-xr-xgit-svn.perl73
-rw-r--r--git.c25
-rwxr-xr-xgitweb/gitweb.perl355
-rw-r--r--graph.h1
-rw-r--r--grep.c13
-rw-r--r--grep.h2
-rw-r--r--http-backend.c655
-rw-r--r--http-fetch.c21
-rw-r--r--http-push.c39
-rw-r--r--http.c13
-rw-r--r--http.h2
-rw-r--r--ident.c2
-rw-r--r--imap-send.c241
-rw-r--r--index-pack.c3
-rw-r--r--merge-recursive.c4
-rw-r--r--notes.c429
-rw-r--r--notes.h13
-rw-r--r--pack-redundant.c3
-rw-r--r--pager.c24
-rw-r--r--path.c130
-rw-r--r--pkt-line.c84
-rw-r--r--pkt-line.h4
-rw-r--r--pretty.c67
-rw-r--r--refs.c7
-rw-r--r--refs.h2
-rw-r--r--remote-curl.c764
-rw-r--r--remote.c85
-rw-r--r--remote.h3
-rw-r--r--revision.c19
-rw-r--r--revision.h1
-rw-r--r--send-pack.h3
-rw-r--r--show-index.c5
-rw-r--r--sideband.c11
-rw-r--r--strbuf.h2
-rw-r--r--submodule.c2
-rw-r--r--t/gitweb-lib.sh16
-rw-r--r--t/lib-httpd/apache.conf24
-rwxr-xr-xt/t1001-read-tree-m-2way.sh6
-rwxr-xr-xt/t1200-tutorial.sh220
-rwxr-xr-xt/t3003-ls-files-exclude.sh8
-rwxr-xr-xt/t3101-ls-tree-dirname.sh89
-rwxr-xr-xt/t3301-notes.sh150
-rwxr-xr-xt/t3302-notes-index-expensive.sh118
-rwxr-xr-xt/t3303-notes-subtrees.sh188
-rwxr-xr-xt/t3701-add-interactive.sh17
-rwxr-xr-xt/t4014-format-patch.sh37
-rwxr-xr-xt/t4034-diff-words.sh20
-rwxr-xr-xt/t4202-log.sh21
-rwxr-xr-xt/t5100-mailinfo.sh6
-rw-r--r--t/t5100/info00155
-rw-r--r--t/t5100/info0015--no-inbody-headers5
-rw-r--r--t/t5100/info00165
-rw-r--r--t/t5100/info0016--no-inbody-headers5
-rw-r--r--t/t5100/msg00152
-rw-r--r--t/t5100/msg0015--no-inbody-headers3
-rw-r--r--t/t5100/msg00162
-rw-r--r--t/t5100/msg0016--no-inbody-headers4
-rw-r--r--t/t5100/patch00158
-rw-r--r--t/t5100/patch0015--no-inbody-headers8
-rw-r--r--t/t5100/patch00168
-rw-r--r--t/t5100/patch0016--no-inbody-headers8
-rw-r--r--t/t5100/sample.mbox33
-rwxr-xr-xt/t5505-remote.sh13
-rwxr-xr-xt/t5506-remote-groups.sh21
-rwxr-xr-xt/t5510-fetch.sh11
-rwxr-xr-xt/t5514-fetch-multiple.sh154
-rwxr-xr-xt/t5518-fetch-exit-status.sh2
-rwxr-xr-xt/t5540-http-push.sh35
-rwxr-xr-xt/t5541-http-push.sh92
-rwxr-xr-xt/t5550-http-fetch.sh12
-rwxr-xr-xt/t5551-http-fetch.sh105
-rwxr-xr-xt/t5560-http-backend.sh260
-rwxr-xr-xt/t6028-merge-up-to-date.sh2
-rwxr-xr-xt/t6050-replace.sh17
-rwxr-xr-xt/t6120-describe.sh20
-rwxr-xr-xt/t7002-grep.sh15
-rwxr-xr-xt/t7003-filter-branch.sh18
-rwxr-xr-xt/t7005-editor.sh47
-rwxr-xr-xt/t7400-submodule-basic.sh16
-rwxr-xr-xt/t7501-commit.sh8
-rwxr-xr-xt/t7502-commit.sh9
-rwxr-xr-xt/t7600-merge.sh41
-rwxr-xr-xt/t9115-git-svn-dcommit-funky-renames.sh4
-rwxr-xr-xt/t9130-git-svn-authors-file.sh23
-rwxr-xr-xt/t9146-git-svn-empty-dirs.sh108
-rwxr-xr-xt/t9151-svn-mergeinfo.sh6
-rw-r--r--t/t9151/make-svnmerge-dump32
-rw-r--r--t/t9151/svn-mergeinfo.dump386
-rwxr-xr-xt/t9300-fast-import.sh166
-rwxr-xr-xt/t9400-git-cvsserver-server.sh2
-rwxr-xr-xt/t9401-git-cvsserver-crlf.sh2
-rwxr-xr-xt/t9501-gitweb-standalone-http-status.sh39
-rwxr-xr-xt/t9502-gitweb-standalone-parse-output.sh115
-rwxr-xr-xt/t9700-perl-git.sh4
-rwxr-xr-xt/t9700/test.pl2
-rw-r--r--t/test-lib.sh8
-rwxr-xr-xtemplates/hooks--pre-commit.sample18
-rw-r--r--transport-helper.c264
-rw-r--r--transport.c41
-rw-r--r--transport.h2
-rw-r--r--unpack-file.c2
-rw-r--r--unpack-trees.c28
-rw-r--r--upload-pack.c103
-rw-r--r--usage.c19
-rw-r--r--utf8.c68
-rw-r--r--utf8.h2
-rw-r--r--var.c26
245 files changed, 9018 insertions, 1763 deletions
diff --git a/.gitignore b/.gitignore
index 51a37b1af7..ac02a580da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,184 +1,187 @@
-GIT-BUILD-OPTIONS
-GIT-CFLAGS
-GIT-GUI-VARS
-GIT-VERSION-FILE
-git
-git-add
-git-add--interactive
-git-am
-git-annotate
-git-apply
-git-archimport
-git-archive
-git-bisect
-git-bisect--helper
-git-blame
-git-branch
-git-bundle
-git-cat-file
-git-check-attr
-git-check-ref-format
-git-checkout
-git-checkout-index
-git-cherry
-git-cherry-pick
-git-clean
-git-clone
-git-commit
-git-commit-tree
-git-config
-git-count-objects
-git-cvsexportcommit
-git-cvsimport
-git-cvsserver
-git-daemon
-git-diff
-git-diff-files
-git-diff-index
-git-diff-tree
-git-difftool
-git-difftool--helper
-git-describe
-git-fast-export
-git-fast-import
-git-fetch
-git-fetch--tool
-git-fetch-pack
-git-filter-branch
-git-fmt-merge-msg
-git-for-each-ref
-git-format-patch
-git-fsck
-git-fsck-objects
-git-gc
-git-get-tar-commit-id
-git-grep
-git-hash-object
-git-help
-git-http-fetch
-git-http-push
-git-imap-send
-git-index-pack
-git-init
-git-init-db
-git-instaweb
-git-log
-git-lost-found
-git-ls-files
-git-ls-remote
-git-ls-tree
-git-mailinfo
-git-mailsplit
-git-merge
-git-merge-base
-git-merge-index
-git-merge-file
-git-merge-tree
-git-merge-octopus
-git-merge-one-file
-git-merge-ours
-git-merge-recursive
-git-merge-resolve
-git-merge-subtree
-git-mergetool
-git-mergetool--lib
-git-mktag
-git-mktree
-git-name-rev
-git-mv
-git-pack-redundant
-git-pack-objects
-git-pack-refs
-git-parse-remote
-git-patch-id
-git-peek-remote
-git-prune
-git-prune-packed
-git-pull
-git-push
-git-quiltimport
-git-read-tree
-git-rebase
-git-rebase--interactive
-git-receive-pack
-git-reflog
-git-relink
-git-remote
-git-remote-curl
-git-repack
-git-replace
-git-repo-config
-git-request-pull
-git-rerere
-git-reset
-git-rev-list
-git-rev-parse
-git-revert
-git-rm
-git-send-email
-git-send-pack
-git-sh-setup
-git-shell
-git-shortlog
-git-show
-git-show-branch
-git-show-index
-git-show-ref
-git-stage
-git-stash
-git-status
-git-stripspace
-git-submodule
-git-svn
-git-symbolic-ref
-git-tag
-git-tar-tree
-git-unpack-file
-git-unpack-objects
-git-update-index
-git-update-ref
-git-update-server-info
-git-upload-archive
-git-upload-pack
-git-var
-git-verify-pack
-git-verify-tag
-git-web--browse
-git-whatchanged
-git-write-tree
-git-core-*/?*
-gitk-wish
-gitweb/gitweb.cgi
-test-chmtime
-test-ctype
-test-date
-test-delta
-test-dump-cache-tree
-test-genrandom
-test-match-trees
-test-parse-options
-test-path-utils
-test-sha1
-test-sigchain
-common-cmds.h
+/GIT-BUILD-OPTIONS
+/GIT-CFLAGS
+/GIT-GUI-VARS
+/GIT-VERSION-FILE
+/git
+/git-add
+/git-add--interactive
+/git-am
+/git-annotate
+/git-apply
+/git-archimport
+/git-archive
+/git-bisect
+/git-bisect--helper
+/git-blame
+/git-branch
+/git-bundle
+/git-cat-file
+/git-check-attr
+/git-check-ref-format
+/git-checkout
+/git-checkout-index
+/git-cherry
+/git-cherry-pick
+/git-clean
+/git-clone
+/git-commit
+/git-commit-tree
+/git-config
+/git-count-objects
+/git-cvsexportcommit
+/git-cvsimport
+/git-cvsserver
+/git-daemon
+/git-diff
+/git-diff-files
+/git-diff-index
+/git-diff-tree
+/git-difftool
+/git-difftool--helper
+/git-describe
+/git-fast-export
+/git-fast-import
+/git-fetch
+/git-fetch--tool
+/git-fetch-pack
+/git-filter-branch
+/git-fmt-merge-msg
+/git-for-each-ref
+/git-format-patch
+/git-fsck
+/git-fsck-objects
+/git-gc
+/git-get-tar-commit-id
+/git-grep
+/git-hash-object
+/git-help
+/git-http-backend
+/git-http-fetch
+/git-http-push
+/git-imap-send
+/git-index-pack
+/git-init
+/git-init-db
+/git-instaweb
+/git-log
+/git-lost-found
+/git-ls-files
+/git-ls-remote
+/git-ls-tree
+/git-mailinfo
+/git-mailsplit
+/git-merge
+/git-merge-base
+/git-merge-index
+/git-merge-file
+/git-merge-tree
+/git-merge-octopus
+/git-merge-one-file
+/git-merge-ours
+/git-merge-recursive
+/git-merge-resolve
+/git-merge-subtree
+/git-mergetool
+/git-mergetool--lib
+/git-mktag
+/git-mktree
+/git-name-rev
+/git-mv
+/git-notes
+/git-pack-redundant
+/git-pack-objects
+/git-pack-refs
+/git-parse-remote
+/git-patch-id
+/git-peek-remote
+/git-prune
+/git-prune-packed
+/git-pull
+/git-push
+/git-quiltimport
+/git-read-tree
+/git-rebase
+/git-rebase--interactive
+/git-receive-pack
+/git-reflog
+/git-relink
+/git-remote
+/git-remote-curl
+/git-repack
+/git-replace
+/git-repo-config
+/git-request-pull
+/git-rerere
+/git-reset
+/git-rev-list
+/git-rev-parse
+/git-revert
+/git-rm
+/git-send-email
+/git-send-pack
+/git-sh-setup
+/git-shell
+/git-shortlog
+/git-show
+/git-show-branch
+/git-show-index
+/git-show-ref
+/git-stage
+/git-stash
+/git-status
+/git-stripspace
+/git-submodule
+/git-svn
+/git-symbolic-ref
+/git-tag
+/git-tar-tree
+/git-unpack-file
+/git-unpack-objects
+/git-update-index
+/git-update-ref
+/git-update-server-info
+/git-upload-archive
+/git-upload-pack
+/git-var
+/git-verify-pack
+/git-verify-tag
+/git-web--browse
+/git-whatchanged
+/git-write-tree
+/git-core-*/?*
+/gitk-git/gitk-wish
+/gitweb/gitweb.cgi
+/test-chmtime
+/test-ctype
+/test-date
+/test-delta
+/test-dump-cache-tree
+/test-genrandom
+/test-match-trees
+/test-parse-options
+/test-path-utils
+/test-sha1
+/test-sigchain
+/common-cmds.h
*.tar.gz
*.dsc
*.deb
-git.spec
+/git.spec
*.exe
*.[aos]
*.py[co]
-config.mak
-autom4te.cache
-config.cache
-config.log
-config.status
-config.mak.autogen
-config.mak.append
-configure
-tags
-TAGS
-cscope*
+*+
+/config.mak
+/autom4te.cache
+/config.cache
+/config.log
+/config.status
+/config.mak.autogen
+/config.mak.append
+/configure
+/tags
+/TAGS
+/cscope*
*.obj
*.lib
*.sln
@@ -188,5 +191,5 @@ cscope*
*.user
*.idb
*.pdb
-Debug/
-Release/
+/Debug/
+/Release/
diff --git a/Documentation/RelNotes-1.6.5.3.txt b/Documentation/RelNotes-1.6.5.3.txt
new file mode 100644
index 0000000000..b2fad1b22e
--- /dev/null
+++ b/Documentation/RelNotes-1.6.5.3.txt
@@ -0,0 +1,63 @@
+Git v1.6.5.3 Release Notes
+==========================
+
+Fixes since v1.6.5.2
+--------------------
+
+ * info/grafts file didn't ignore trailing CR at the end of lines.
+
+ * Packages generated on newer FC were unreadable by older versions of
+ RPM as the new default is to use stronger hash.
+
+ * output from "git blame" was unreadable when the file ended in an
+ incomplete line.
+
+ * "git add -i/-p" didn't handle deletion of empty files correctly.
+
+ * "git clone" takes up to two parameters, but did not complain when
+ given more arguments than necessary and silently ignored them.
+
+ * "git cvsimport" did not read files given as command line arguments
+ correctly when it is run from a subdirectory.
+
+ * "git diff --color-words -U0" didn't work correctly.
+
+ * The handling of blank lines at the end of file by "git diff/apply
+ --whitespace" was inconsistent with the other kinds of errors.
+ They are now colored, warned against, and fixed the same way as others.
+
+ * There was no way to allow blank lines at the end of file without
+ allowing extra blanks at the end of lines. You can use blank-at-eof
+ and blank-at-eol whitespace error class to specify them separately.
+ The old trailing-space error class is now a short-hand to set both.
+
+ * "-p" option to "git format-patch" was supposed to suppress diffstat
+ generation, but it was broken since 1.6.1.
+
+ * "git imap-send" did not compile cleanly with newer OpenSSL.
+
+ * "git help -a" outside of a git repository was broken.
+
+ * "git ls-files -i" was supposed to be inverse of "git ls-files" without -i
+ with respect to exclude patterns, but it was broken since 1.6.5.2.
+
+ * "git ls-remote" outside of a git repository over http was broken.
+
+ * "git rebase -i" gave bogus error message when the command word was
+ misspelled.
+
+ * "git receive-pack" that is run in response to "git push" did not run
+ garbage collection nor update-server-info, but in larger hosting sites,
+ these almost always need to be run. To help site administrators, the
+ command now runs "gc --auto" and "u-s-i" by setting receive.autogc
+ and receive.updateserverinfo configuration variables, respectively.
+
+ * Release notes spelled the package name with incorrect capitalization.
+
+ * "gitweb" did not escape non-ascii characters correctly in the URL.
+
+ * "gitweb" showed "patch" link even for merge commits.
+
+ * "gitweb" showed incorrect links for blob line numbers in pathinfo mode.
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes-1.6.6.txt b/Documentation/RelNotes-1.6.6.txt
index abf34e6ace..371101d667 100644
--- a/Documentation/RelNotes-1.6.6.txt
+++ b/Documentation/RelNotes-1.6.6.txt
@@ -1,4 +1,4 @@
-GIT v1.6.6 Release Notes
+Git v1.6.6 Release Notes
========================
In this release, "git fsck" defaults to "git fsck --full" and checks
@@ -40,18 +40,47 @@ Updates since v1.6.5
(subsystems)
+ * various git-gui updates including new translations, wm states, etc.
+
+ * git-svn updates.
+
+ * "git fetch" over http learned a new mode that is different from the
+ traditional "dumb commit walker".
+
(portability)
+ * imap-send can be built on mingw port.
+
(performance)
+ * "git diff -B" has smaller memory footprint.
+
(usability, bells and whistles)
* The object replace mechanism can be bypassed with --no-replace-objects
global option given to the "git" program.
+ * In configuration files, a few variables that name paths can begin with ~/
+ and ~username/ and they are expanded as expected.
+
+ * "git subcmd -h" now shows short usage help for many more subcommands.
+
+ * "git bisect reset" can reset to an arbitrary commit.
+
+ * "git checkout frotz" when there is no local branch "frotz" but there
+ is only one remote tracking branch "frotz" is taken as a request to
+ start the named branch at the corresponding remote tracking branch.
+
+ * "git describe" can be told to add "-dirty" suffix with "--dirty" option.
+
+ * "git diff" learned --submodule option to show a list of one-line logs
+ instead of differences between the commit object names.
+
* "git fsck" by default checks the packfiles (i.e. "--full" is the
default); you can turn it off with "git fsck --no-full".
+ * "git grep" can use -F (fixed strings) and -i (ignore case) together.
+
* import-tars contributed fast-import frontend learned more types of
compressed tarballs.
@@ -59,11 +88,28 @@ Updates since v1.6.5
* "git log --decorate" shows the location of HEAD as well.
+ * "--pretty=format" option to "log" family of commands learned:
+
+ . to wrap text with the "%w()" specifier.
+ . to show reflog information with "%g[sdD]" specifier.
+
+ * "git notes" command to annotate existing commits.
+
+ * "git merge" (and "git pull") learned --ff-only option to make it fail
+ if the merge does not result in a fast-forward.
+
+ * "git mergetool" learned to use p4merge.
+
* "git rebase -i" learned "reword" that acts like "edit" but immediately
starts an editor to tweak the log message without returning control to
the shell, which is done by "edit" to give an opportunity to tweak the
contents.
+ * In "git submodule add <repository> <path>", <path> is now optional and
+ inferred from <repository> the same way "git clone <repository>" does.
+
+ * "git svn" learned to read SVN 1.5+ and SVK merge tickets.
+
* Author names shown in gitweb output are links to search commits by the
author.
@@ -76,33 +122,8 @@ Fixes since v1.6.5
All of the fixes in v1.6.5.X maintenance series are included in this
release, unless otherwise noted.
- * "git apply" and "git diff" (including patch output from "git log -p")
- now flags trailing blank lines as whitespace errors correctly (only
- "apply --whitespace=fix" stripped them but "apply --whitespace=warn"
- did not even warn).
-
- * Two whitespace error classes, 'blank-at-eof' and 'blank-at-eol', have
- been introduced (settable by core.whitespace configuration variable and
- whitespace attribute). The 'trailing-space' whitespace error class has
- become a short-hand to cover both of these and there is no behaviour
- change for existing set-ups.
-
- * "git cvsimport" did not work well when it is fed filenames from the
- command line and is not started at the top of the work tree. We should
- backport this by merging f6fdbb6 (cvsimport: fix relative argument
- filenames, 2009-10-19).
-
- * The way gitweb escapes its CGI parameters were broken especially when
- the parameter was a UTF-8 string. We may want to backport this to
- 1.6.5.X series by merging 452e225 (gitweb: fix esc_param, 2009-10-13).
-
- * gitweb used to show 'patch' link for merge commits but the output from
- it is not usable to feed "git am" with. We may want to backport this
- to 1.6.5.X series by merging 1655c98 (gitweb: Do not show 'patch' link
- for merge commits, 2009-10-09).
-
---
exec >/var/tmp/1
echo O=$(git describe master)
-O=v1.6.5.2-73-g9b12444
+O=v1.6.5.3-337-gf341feb
git shortlog --no-merges $O..master --not maint
diff --git a/Documentation/config.txt b/Documentation/config.txt
index d1e2120e15..657f0016d3 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -131,7 +131,11 @@ advice.*::
core.fileMode::
If false, the executable bit differences between the index and
the working copy are ignored; useful on broken filesystems like FAT.
- See linkgit:git-update-index[1]. True by default.
+ See linkgit:git-update-index[1].
++
+The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.fileMode false if appropriate when the
+repository is created.
core.ignoreCygwinFSTricks::
This option is only used by Cygwin implementation of Git. If false,
@@ -144,6 +148,18 @@ core.ignoreCygwinFSTricks::
is true, in which case ignoreCygwinFSTricks is ignored as Cygwin's
POSIX emulation is required to support core.filemode.
+core.ignorecase::
+ If true, this option enables various workarounds to enable
+ git to work better on filesystems that are not case sensitive,
+ like FAT. For example, if a directory listing finds
+ "makefile" when git expects "Makefile", git will assume
+ it is really the same file, and continue to remember it as
+ "Makefile".
++
+The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.ignorecase true if appropriate when the repository
+is created.
+
core.trustctime::
If false, the ctime differences between the index and the
working copy are ignored; useful when the inode change time
@@ -169,9 +185,10 @@ core.autocrlf::
writing to the filesystem. The variable can be set to
'input', in which case the conversion happens only while
reading from the filesystem but files are written out with
- `LF` at the end of lines. Currently, which paths to consider
- "text" (i.e. be subjected to the autocrlf mechanism) is
- decided purely based on the contents.
+ `LF` at the end of lines. A file is considered
+ "text" (i.e. be subjected to the autocrlf mechanism) based on
+ the file's `crlf` attribute, or if `crlf` is unspecified,
+ based on the file's contents. See linkgit:gitattributes[5].
core.safecrlf::
If true, makes git check if converting `CRLF` as controlled by
@@ -223,7 +240,11 @@ core.symlinks::
contain the link text. linkgit:git-update-index[1] and
linkgit:git-add[1] will not change the recorded type to regular
file. Useful on filesystems like FAT that do not support
- symbolic links. True by default.
+ symbolic links.
++
+The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.symlinks false if appropriate when the repository
+is created.
core.gitProxy::
A "proxy command" to execute (as 'command host port') instead
@@ -380,16 +401,15 @@ Common unit suffixes of 'k', 'm', or 'g' are supported.
core.excludesfile::
In addition to '.gitignore' (per-directory) and
'.git/info/exclude', git looks into this file for patterns
- of files which are not meant to be tracked. See
- linkgit:gitignore[5].
+ of files which are not meant to be tracked. "{tilde}/" is expanded
+ to the value of `$HOME` and "{tilde}user/" to the specified user's
+ home directory. See linkgit:gitignore[5].
core.editor::
Commands such as `commit` and `tag` that lets you edit
messages by launching an editor uses the value of this
variable when it is set, and the environment variable
- `GIT_EDITOR` is not set. The order of preference is
- `GIT_EDITOR` environment, `core.editor`, `VISUAL` and
- `EDITOR` environment variables and then finally `vi`.
+ `GIT_EDITOR` is not set. See linkgit:git-var[1].
core.pager::
The command that git will use to paginate output. Can
@@ -458,6 +478,19 @@ On some file system/operating system combinations, this is unreliable.
Set this config setting to 'rename' there; However, This will remove the
check that makes sure that existing object files will not get overwritten.
+core.notesRef::
+ When showing commit messages, also show notes which are stored in
+ the given ref. This ref is expected to contain files named
+ after the full SHA-1 of the commit they annotate.
++
+If such a file exists in the given ref, the referenced blob is read, and
+appended to the commit message, separated by a "Notes:" line. If the
+given ref itself does not exist, it is not an error, but means that no
+notes should be printed.
++
+This setting defaults to "refs/notes/commits", and can be overridden by
+the `GIT_NOTES_REF` environment variable.
+
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'
@@ -670,6 +703,8 @@ color.ui::
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
+ specified user's home directory.
diff.autorefreshindex::
When using 'git-diff' to compare with work tree
@@ -1093,6 +1128,14 @@ 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.postBuffer::
+ Maximum size in bytes of the buffer used by smart HTTP
+ transports when POSTing data to the remote system.
+ For requests larger than this buffer size, HTTP/1.1 and
+ Transfer-Encoding: chunked is used to avoid creating a
+ massive pack file locally. Default is 1 MiB, which is
+ sufficient for most requests.
+
http.lowSpeedLimit, http.lowSpeedTime::
If the HTTP transfer speed is less than 'http.lowSpeedLimit'
for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.
@@ -1360,7 +1403,7 @@ receive.denyCurrentBranch::
receive.denyNonFastForwards::
If set to true, git-receive-pack will deny a ref update which is
- not a fast forward. Use this to prevent such an update via a push,
+ not a fast-forward. Use this to prevent such an update via a push,
even if that push is forced. This configuration variable is
set when initializing a shared repository.
@@ -1394,7 +1437,13 @@ remote.<name>.mirror::
remote.<name>.skipDefaultUpdate::
If true, this remote will be skipped by default when updating
- using the update subcommand of linkgit:git-remote[1].
+ using linkgit:git-fetch[1] or the `update` subcommand of
+ linkgit:git-remote[1].
+
+remote.<name>.skipFetchAll::
+ If true, this remote will be skipped by default when updating
+ using linkgit:git-fetch[1] or the `update` subcommand of
+ linkgit:git-remote[1].
remote.<name>.receivepack::
The default program to execute on the remote side when pushing. See
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index e26b84706f..2b37193a37 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -14,7 +14,8 @@ endif::git-format-patch[]
ifdef::git-format-patch[]
-p::
- Generate patches without diffstat.
+--no-stat::
+ Generate plain patches without any diffstats.
endif::git-format-patch[]
ifndef::git-format-patch[]
@@ -27,33 +28,40 @@ endif::git-format-patch[]
-U<n>::
--unified=<n>::
Generate diffs with <n> lines of context instead of
- the usual three. Implies "-p".
+ the usual three.
+ifndef::git-format-patch[]
+ Implies `-p`.
+endif::git-format-patch[]
+ifndef::git-format-patch[]
--raw::
Generate the raw format.
{git-diff-core? This is the default.}
+endif::git-format-patch[]
+ifndef::git-format-patch[]
--patch-with-raw::
- Synonym for "-p --raw".
+ Synonym for `-p --raw`.
+endif::git-format-patch[]
--patience::
Generate a diff using the "patience diff" algorithm.
--stat[=width[,name-width]]::
Generate a diffstat. You can override the default
- output width for 80-column terminal by "--stat=width".
+ output width for 80-column terminal by `--stat=width`.
The width of the filename part can be controlled by
giving another width to it separated by a comma.
--numstat::
- Similar to \--stat, but shows number of added and
+ Similar to `\--stat`, but shows number of added and
deleted lines in decimal notation and pathname without
abbreviation, to make it more machine friendly. For
binary files, outputs two `-` instead of saying
`0 0`.
--shortstat::
- Output only the last line of the --stat format containing total
+ Output only the last line of the `--stat` format containing total
number of modified files, as well as number of added and deleted
lines.
@@ -61,24 +69,26 @@ endif::git-format-patch[]
Output the distribution of relative amount of changes (number of lines added or
removed) for each sub-directory. Directories with changes below
a cut-off percent (3% by default) are not shown. The cut-off percent
- can be set with "--dirstat=limit". Changes in a child directory is not
- counted for the parent directory, unless "--cumulative" is used.
+ can be set with `--dirstat=limit`. Changes in a child directory is not
+ counted for the parent directory, unless `--cumulative` is used.
--dirstat-by-file[=limit]::
- Same as --dirstat, but counts changed files instead of lines.
+ Same as `--dirstat`, but counts changed files instead of lines.
--summary::
Output a condensed summary of extended header information
such as creations, renames and mode changes.
+ifndef::git-format-patch[]
--patch-with-stat::
- Synonym for "-p --stat".
- {git-format-patch? This is the default.}
+ Synonym for `-p --stat`.
+endif::git-format-patch[]
+ifndef::git-format-patch[]
-z::
- NUL-line termination on output. This affects the --raw
+ NUL-line termination on output. This affects the `--raw`
output field terminator. Also output from commands such
- as "git-log" will be delimited with NUL between commits.
+ as `git-log` will be delimited with NUL between commits.
--name-only::
Show only names of changed files.
@@ -117,16 +127,19 @@ The regex can also be set via a diff driver or configuration option, see
linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly
overrides any diff driver or configuration setting. Diff drivers
override configuration settings.
+endif::git-format-patch[]
--no-renames::
Turn off rename detection, even when the configuration
file gives the default to do so.
+ifndef::git-format-patch[]
--check::
Warn if changes introduce trailing whitespace
or an indent that uses a space before a tab. Exits with
non-zero status if problems are found. Not compatible with
--exit-code.
+endif::git-format-patch[]
--full-index::
Instead of the first handful of characters, show the full
@@ -134,16 +147,16 @@ override configuration settings.
line when generating patch format output.
--binary::
- In addition to --full-index, output "binary diff" that
- can be applied with "git apply".
+ In addition to `--full-index`, output a binary diff that
+ can be applied with `git-apply`.
--abbrev[=<n>]::
Instead of showing the full 40-byte hexadecimal object
name in diff-raw format output and diff-tree header
lines, show only a partial prefix. This is
- independent of --full-index option above, which controls
+ independent of the `--full-index` option above, which controls
the diff-patch output format. Non default number of
- digits can be specified with --abbrev=<n>.
+ digits can be specified with `--abbrev=<n>`.
-B::
Break complete rewrite changes into pairs of delete and create.
@@ -154,6 +167,7 @@ override configuration settings.
-C::
Detect copies as well as renames. See also `--find-copies-harder`.
+ifndef::git-format-patch[]
--diff-filter=[ACDMRTUXB*]::
Select only files that are Added (`A`), Copied (`C`),
Deleted (`D`), Modified (`M`), Renamed (`R`), have their
@@ -165,6 +179,7 @@ override configuration settings.
paths are selected if there is any file that matches
other criteria in the comparison; if there is no file
that matches other criteria, nothing is selected.
+endif::git-format-patch[]
--find-copies-harder::
For performance reasons, by default, `-C` option finds copies only
@@ -176,12 +191,13 @@ override configuration settings.
`-C` option has the same effect.
-l<num>::
- -M and -C options require O(n^2) processing time where n
+ The `-M` and `-C` options require O(n^2) processing time where n
is the number of potential rename/copy targets. This
option prevents rename/copy detection from running if
the number of rename/copy targets exceeds the specified
number.
+ifndef::git-format-patch[]
-S<string>::
Look for differences that introduce or remove an instance of
<string>. Note that this is different than the string simply
@@ -189,18 +205,20 @@ override configuration settings.
linkgit:gitdiffcore[7] for more details.
--pickaxe-all::
- When -S finds a change, show all the changes in that
+ When `-S` finds a change, show all the changes in that
changeset, not just the files that contain the change
in <string>.
--pickaxe-regex::
Make the <string> not a plain string but an extended POSIX
regex to match.
+endif::git-format-patch[]
-O<orderfile>::
Output the patch in the order specified in the
<orderfile>, which has one shell glob pattern per line.
+ifndef::git-format-patch[]
-R::
Swap two inputs; that is, show differences from index or
on-disk file to tree contents.
@@ -212,6 +230,7 @@ override configuration settings.
not in a subdirectory (e.g. in a bare repository), you
can name which subdirectory to make the output relative
to by giving a <path> as an argument.
+endif::git-format-patch[]
-a::
--text::
@@ -236,13 +255,15 @@ override configuration settings.
Show the context between diff hunks, up to the specified number
of lines, thereby fusing hunks that are close to each other.
+ifndef::git-format-patch[]
--exit-code::
Make the program exit with codes similar to diff(1).
That is, it exits with 1 if there were differences and
0 means no differences.
--quiet::
- Disable all output of the program. Implies --exit-code.
+ Disable all output of the program. Implies `--exit-code`.
+endif::git-format-patch[]
--ext-diff::
Allow an external diff helper to be executed. If you set an
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index 28868747da..ab6419fe6e 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -1,3 +1,6 @@
+--all::
+ Fetch all remotes.
+
-a::
--append::
Append ref names and object names of fetched refs to the
@@ -9,6 +12,11 @@
`git clone` with `--depth=<depth>` option (see linkgit:git-clone[1])
by the specified number of commits.
+ifndef::git-pull[]
+--dry-run::
+ Show what would be done, without making any changes.
+endif::git-pull[]
+
-f::
--force::
When 'git-fetch' is used with `<rbranch>:<lbranch>`
@@ -21,6 +29,16 @@
--keep::
Keep downloaded pack.
+ifndef::git-pull[]
+--multiple::
+ Allow several <repository> and <group> arguments to be
+ specified. No <refspec>s may be specified.
+
+--prune::
+ After fetching, remove any remote tracking branches which
+ no longer exist on the remote.
+endif::git-pull[]
+
ifdef::git-pull[]
--no-tags::
endif::git-pull[]
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index 45ebf87ca3..e93e606f45 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -76,10 +76,10 @@ OPTIONS
work tree and add them to the index. This gives the user a chance
to review the difference before adding modified contents to the
index.
-
- This effectively runs ``add --interactive``, but bypasses the
- initial command menu and directly jumps to `patch` subcommand.
- See ``Interactive mode'' for details.
++
+This effectively runs `add --interactive`, but bypasses the
+initial command menu and directly jumps to the `patch` subcommand.
+See ``Interactive mode'' for details.
-e, \--edit::
Open the diff vs. the index in an editor and let the user
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 0578a40d84..3ea80c820f 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -323,7 +323,7 @@ ENVIRONMENT AND CONFIGURATION VARIABLES
The editor used to edit the commit log message will be chosen from the
GIT_EDITOR environment variable, the core.editor configuration variable, the
VISUAL environment variable, or the EDITOR environment variable (in that
-order).
+order). See linkgit:git-var[1] for details.
HOOKS
-----
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
index 785779e221..99a7c14700 100644
--- a/Documentation/git-cvsserver.txt
+++ b/Documentation/git-cvsserver.txt
@@ -182,10 +182,9 @@ Database Backend
----------------
'git-cvsserver' uses one database per git head (i.e. CVS module) to
-store information about the repository for faster access. The
-database doesn't contain any persistent data and can be completely
-regenerated from the git repository at any time. The database
-needs to be updated (i.e. written to) after every commit.
+store information about the repository to maintain consistent
+CVS revision numbers. The database needs to be
+updated (i.e. written to) after every commit.
If the commit is done directly by using `git` (as opposed to
using 'git-cvsserver') the update will need to happen on the
@@ -204,6 +203,18 @@ write so it might not be enough to grant the users using
'git-cvsserver' write access to the database file without granting
them write access to the directory, too.
+The database can not be reliably regenerated in a
+consistent form after the branch it is tracking has changed.
+Example: For merged branches, 'git-cvsserver' only tracks
+one branch of development, and after a 'git-merge' an
+incrementally updated database may track a different branch
+than a database regenerated from scratch, causing inconsistent
+CVS revision numbers. `git-cvsserver` has no way of knowing which
+branch it would have picked if it had been run incrementally
+pre-merge. So if you have to fully or partially (from old
+backup) regenerate the database, you should be suspicious
+of pre-existing CVS sandboxes.
+
You can configure the database backend with the following
configuration variables:
diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt
index e9dbca7d87..78b9808aa3 100644
--- a/Documentation/git-describe.txt
+++ b/Documentation/git-describe.txt
@@ -8,7 +8,9 @@ git-describe - Show the most recent tag that is reachable from a commit
SYNOPSIS
--------
+[verse]
'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] <committish>...
+'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] --dirty[=<mark>]
DESCRIPTION
-----------
@@ -27,6 +29,11 @@ OPTIONS
<committish>...::
Committish object names to describe.
+--dirty[=<mark>]::
+ Describe the working tree.
+ It means describe HEAD and appends <mark> (`-dirty` by
+ default) if the working tree is dirty.
+
--all::
Instead of using only the annotated tags, use any ref
found in `.git/refs/`. This option enables matching
@@ -120,7 +127,7 @@ closest tagname without any suffix:
tags/v1.0.0
Note that the suffix you get if you type these commands today may be
-longer than what Linus saw above when he ran this command, as your
+longer than what Linus saw above when he ran these commands, as your
git repository may have new commits whose object names begin with
975b that did not exist back then, and "-g975b" suffix alone may not
be sufficient to disambiguate these commits.
diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt
index 96a6c51a4b..8e9aed67d7 100644
--- a/Documentation/git-difftool.txt
+++ b/Documentation/git-difftool.txt
@@ -31,7 +31,7 @@ OPTIONS
Use the diff tool specified by <tool>.
Valid merge tools are:
kdiff3, kompare, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff,
- ecmerge, diffuse, opendiff and araxis.
+ ecmerge, diffuse, opendiff, p4merge and araxis.
+
If a diff tool is not specified, 'git-difftool'
will use the configuration variable `diff.tool`. If the
diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
index c2f483a8d2..288032c7b8 100644
--- a/Documentation/git-fast-import.txt
+++ b/Documentation/git-fast-import.txt
@@ -316,7 +316,7 @@ change to the project.
data
('from' SP <committish> LF)?
('merge' SP <committish> LF)?
- (filemodify | filedelete | filecopy | filerename | filedeleteall)*
+ (filemodify | filedelete | filecopy | filerename | filedeleteall | notemodify)*
LF?
....
@@ -339,14 +339,13 @@ commit message use a 0 length data. Commit messages are free-form
and are not interpreted by Git. Currently they must be encoded in
UTF-8, as fast-import does not permit other encodings to be specified.
-Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename`
-and `filedeleteall` commands
+Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename`,
+`filedeleteall` and `notemodify` commands
may be included to update the contents of the branch prior to
creating the commit. These commands may be supplied in any order.
However it is recommended that a `filedeleteall` command precede
-all `filemodify`, `filecopy` and `filerename` commands in the same
-commit, as `filedeleteall`
-wipes the branch clean (see below).
+all `filemodify`, `filecopy`, `filerename` and `notemodify` commands in
+the same commit, as `filedeleteall` wipes the branch clean (see below).
The `LF` after the command is optional (it used to be required).
@@ -595,6 +594,40 @@ more memory per active branch (less than 1 MiB for even most large
projects); so frontends that can easily obtain only the affected
paths for a commit are encouraged to do so.
+`notemodify`
+^^^^^^^^^^^^
+Included in a `commit` command to add a new note (annotating a given
+commit) or change the content of an existing note. This command has
+two different means of specifying the content of the note.
+
+External data format::
+ The data content for the note was already supplied by a prior
+ `blob` command. The frontend just needs to connect it to the
+ commit that is to be annotated.
++
+....
+ 'N' SP <dataref> SP <committish> LF
+....
++
+Here `<dataref>` can be either a mark reference (`:<idnum>`)
+set by a prior `blob` command, or a full 40-byte SHA-1 of an
+existing Git blob object.
+
+Inline data format::
+ The data content for the note has not been supplied yet.
+ The frontend wants to supply it as part of this modify
+ command.
++
+....
+ 'N' SP 'inline' SP <committish> LF
+ data
+....
++
+See below for a detailed description of the `data` command.
+
+In both formats `<committish>` is any of the commit specification
+expressions also accepted by `from` (see above).
+
`mark`
~~~~~~
Arranges for fast-import to save a reference to the current object, allowing
diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt
index f2483d624e..9b9e5686e4 100644
--- a/Documentation/git-fetch.txt
+++ b/Documentation/git-fetch.txt
@@ -10,11 +10,17 @@ SYNOPSIS
--------
'git fetch' <options> <repository> <refspec>...
+'git fetch' <options> <group>
+
+'git fetch' --multiple <options> [<repository> | <group>]...
+
+'git fetch' --all <options>
+
DESCRIPTION
-----------
-Fetches named heads or tags from another repository, along with
-the objects necessary to complete them.
+Fetches named heads or tags from one or more other repositories,
+along with the objects necessary to complete them.
The ref names and their object names of fetched refs are stored
in `.git/FETCH_HEAD`. This information is left for a later merge
@@ -28,6 +34,10 @@ pointed by remote tags that it does not yet have, then fetch
those missing tags. If the other end has tags that point at
branches you are not interested in, you will not get them.
+'git fetch' can fetch from either a single named repository, or
+or from several repositories at once if <group> is given and
+there is a remotes.<group> entry in the configuration file.
+(See linkgit:git-config[1]).
OPTIONS
-------
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index 2b40babb6b..394a77a35f 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -159,7 +159,18 @@ to other tags will be rewritten to point to the underlying commit.
--subdirectory-filter <directory>::
Only look at the history which touches the given subdirectory.
The result will contain that directory (and only that) as its
- project root.
+ project root. Implies --remap-to-ancestor.
+
+--remap-to-ancestor::
+ Rewrite refs to the nearest rewritten ancestor instead of
+ ignoring them.
++
+Normally, positive refs on the command line are only changed if the
+commit they point to was rewritten. However, you can limit the extent
+of this rewriting by using linkgit:rev-list[1] arguments, e.g., path
+limiters. Refs pointing to such excluded commits would then normally
+be ignored. With this option, they are instead rewritten to point at
+the nearest ancestor that was not excluded.
--prune-empty::
Some kind of filters will generate empty commits, that left the tree
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 687e667598..f1fd0df08a 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -43,28 +43,28 @@ There are two ways to specify which commits to operate on.
The first rule takes precedence in the case of a single <commit>. To
apply the second rule, i.e., format everything since the beginning of
-history up until <commit>, use the '\--root' option: "git format-patch
-\--root <commit>". If you want to format only <commit> itself, you
-can do this with "git format-patch -1 <commit>".
+history up until <commit>, use the '\--root' option: `git format-patch
+\--root <commit>`. If you want to format only <commit> itself, you
+can do this with `git format-patch -1 <commit>`.
By default, each output file is numbered sequentially from 1, and uses the
first line of the commit message (massaged for pathname safety) as
-the filename. With the --numbered-files option, the output file names
+the filename. With the `--numbered-files` option, the output file names
will only be numbers, without the first line of the commit appended.
The names of the output files are printed to standard
-output, unless the --stdout option is specified.
+output, unless the `--stdout` option is specified.
-If -o is specified, output files are created in <dir>. Otherwise
+If `-o` is specified, output files are created in <dir>. Otherwise
they are created in the current working directory.
By default, the subject of a single patch is "[PATCH] First Line" and
the subject when multiple patches are output is "[PATCH n/m] First
-Line". To force 1/1 to be added for a single patch, use -n. To omit
-patch numbers from the subject, use -N
+Line". To force 1/1 to be added for a single patch, use `-n`. To omit
+patch numbers from the subject, use `-N`.
-If given --thread, 'git-format-patch' will generate In-Reply-To and
-References headers to make the second and subsequent patch mails appear
-as replies to the first mail; this also generates a Message-Id header to
+If given `--thread`, `git-format-patch` will generate `In-Reply-To` and
+`References` headers to make the second and subsequent patch mails appear
+as replies to the first mail; this also generates a `Message-Id` header to
reference.
OPTIONS
@@ -112,7 +112,7 @@ include::diff-options.txt[]
--attach[=<boundary>]::
Create multipart/mixed attachment, the first part of
which is the commit message and the patch itself in the
- second part, with "Content-Disposition: attachment".
+ second part, with `Content-Disposition: attachment`.
--no-attach::
Disable the creation of an attachment, overriding the
@@ -121,13 +121,13 @@ include::diff-options.txt[]
--inline[=<boundary>]::
Create multipart/mixed attachment, the first part of
which is the commit message and the patch itself in the
- second part, with "Content-Disposition: inline".
+ second part, with `Content-Disposition: inline`.
--thread[=<style>]::
--no-thread::
- Controls addition of In-Reply-To and References headers to
+ Controls addition of `In-Reply-To` and `References` headers to
make the second and subsequent mails appear as replies to the
- first. Also controls generation of the Message-Id header to
+ first. Also controls generation of the `Message-Id` header to
reference.
+
The optional <style> argument can be either `shallow` or `deep`.
@@ -136,16 +136,16 @@ series, where the head is chosen from the cover letter, the
`\--in-reply-to`, and the first patch mail, in this order. 'deep'
threading makes every mail a reply to the previous one.
+
-The default is --no-thread, unless the 'format.thread' configuration
-is set. If --thread is specified without a style, it defaults to the
+The default is `--no-thread`, unless the 'format.thread' configuration
+is set. If `--thread` is specified without a style, it defaults to the
style specified by 'format.thread' if any, or else `shallow`.
+
Beware that the default for 'git send-email' is to thread emails
-itself. If you want 'git format-patch' to take care of hreading, you
-will want to ensure that threading is disabled for 'git send-email'.
+itself. If you want `git format-patch` to take care of threading, you
+will want to ensure that threading is disabled for `git send-email`.
--in-reply-to=Message-Id::
- Make the first mail (or all the mails with --no-thread) appear as a
+ Make the first mail (or all the mails with `--no-thread`) appear as a
reply to the given Message-Id, which avoids breaking threads to
provide a new patch series.
@@ -160,16 +160,16 @@ will want to ensure that threading is disabled for 'git send-email'.
Instead of the standard '[PATCH]' prefix in the subject
line, instead use '[<Subject-Prefix>]'. This
allows for useful naming of a patch series, and can be
- combined with the --numbered option.
+ combined with the `--numbered` option.
--cc=<email>::
- Add a "Cc:" header to the email headers. This is in addition
+ Add a `Cc:` header to the email headers. This is in addition
to any configured headers, and may be used multiple times.
--add-header=<header>::
Add an arbitrary header to the email headers. This is in addition
to any configured headers, and may be used multiple times.
- For example, --add-header="Organization: git-foo"
+ For example, `--add-header="Organization: git-foo"`
--cover-letter::
In addition to the patches, generate a cover letter file
diff --git a/Documentation/git-http-backend.txt b/Documentation/git-http-backend.txt
new file mode 100644
index 0000000000..67aec067c8
--- /dev/null
+++ b/Documentation/git-http-backend.txt
@@ -0,0 +1,178 @@
+git-http-backend(1)
+===================
+
+NAME
+----
+git-http-backend - Server side implementation of Git over HTTP
+
+SYNOPSIS
+--------
+[verse]
+'git-http-backend'
+
+DESCRIPTION
+-----------
+A simple CGI program to serve the contents of a Git repository to Git
+clients accessing the repository over http:// and https:// protocols.
+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.
+
+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,
+the `receive-pack` service is enabled, which serves 'git-send-pack'
+clients, which is invoked from 'git-push'.
+
+SERVICES
+--------
+These services can be enabled/disabled using the per-repository
+configuration file:
+
+http.getanyfile::
+ This serves older Git clients which are unable to use the
+ upload pack service. When enabled, clients are able to read
+ any file within the repository, including objects that are
+ no longer reachable from a branch but are still present.
+ It is enabled by default, but a repository can disable it
+ by setting this configuration item to `false`.
+
+http.uploadpack::
+ This serves 'git-fetch-pack' and 'git-ls-remote' clients.
+ It is enabled by default, but a repository can disable it
+ by setting this configuration item to `false`.
+
+http.receivepack::
+ This serves 'git-send-pack' clients, allowing push. It is
+ disabled by default for anonymous users, and enabled by
+ default for users authenticated by the web server. It can be
+ disabled by setting this item to `false`, or enabled for all
+ users, including anonymous users, by setting it to `true`.
+
+URL TRANSLATION
+---------------
+To determine the location of the repository on disk, 'git-http-backend'
+concatenates the environment variables PATH_INFO, which is set
+automatically by the web server, and GIT_PROJECT_ROOT, which must be set
+manually in the web server configuration. If GIT_PROJECT_ROOT is not
+set, 'git-http-backend' reads PATH_TRANSLATED, which is also set
+automatically by the web server.
+
+EXAMPLES
+--------
+All of the following examples map 'http://$hostname/git/foo/bar.git'
+to '/var/www/git/foo/bar.git'.
+
+Apache 2.x::
+ Ensure mod_cgi, mod_alias, and mod_env are enabled, set
+ GIT_PROJECT_ROOT (or DocumentRoot) appropriately, and
+ create a ScriptAlias to the CGI:
++
+----------------------------------------------------------------
+SetEnv GIT_PROJECT_ROOT /var/www/git
+ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
+----------------------------------------------------------------
++
+To enable anonymous read access but authenticated write access,
+require authorization with a LocationMatch directive:
++
+----------------------------------------------------------------
+<LocationMatch "^/git/.*/git-receive-pack$">
+ AuthType Basic
+ AuthName "Git Access"
+ Require group committers
+ ...
+</LocationMatch>
+----------------------------------------------------------------
++
+To require authentication for both reads and writes, use a Location
+directive around the repository, or one of its parent directories:
++
+----------------------------------------------------------------
+<Location /git/private>
+ AuthType Basic
+ AuthName "Private Git Access"
+ Require group committers
+ ...
+</Location>
+----------------------------------------------------------------
++
+To serve gitweb at the same url, use a ScriptAliasMatch to only
+those URLs that 'git-http-backend' can handle, and forward the
+rest to gitweb:
++
+----------------------------------------------------------------
+ScriptAliasMatch \
+ "(?x)^/git/(.*/(HEAD | \
+ info/refs | \
+ objects/(info/[^/]+ | \
+ [0-9a-f]{2}/[0-9a-f]{38} | \
+ pack/pack-[0-9a-f]{40}\.(pack|idx)) | \
+ git-(upload|receive)-pack))$" \
+ /usr/libexec/git-core/git-http-backend/$1
+
+ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/
+----------------------------------------------------------------
+
+Accelerated static Apache 2.x::
+ Similar to the above, but Apache can be used to return static
+ files that are stored on disk. On many systems this may
+ be more efficient as Apache can ask the kernel to copy the
+ file contents from the file system directly to the network:
++
+----------------------------------------------------------------
+SetEnv GIT_PROJECT_ROOT /var/www/git
+
+AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1
+AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1
+ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
+----------------------------------------------------------------
++
+This can be combined with the gitweb configuration:
++
+----------------------------------------------------------------
+SetEnv GIT_PROJECT_ROOT /var/www/git
+
+AliasMatch ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/git/$1
+AliasMatch ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/git/$1
+ScriptAliasMatch \
+ "(?x)^/git/(.*/(HEAD | \
+ info/refs | \
+ objects/info/[^/]+ | \
+ git-(upload|receive)-pack))$" \
+ /usr/libexec/git-core/git-http-backend/$1
+ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/
+----------------------------------------------------------------
+
+
+ENVIRONMENT
+-----------
+'git-http-backend' relies upon the CGI environment variables set
+by the invoking web server, including:
+
+* PATH_INFO (if GIT_PROJECT_ROOT is set, otherwise PATH_TRANSLATED)
+* REMOTE_USER
+* REMOTE_ADDR
+* CONTENT_TYPE
+* QUERY_STRING
+* REQUEST_METHOD
+
+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
+identifying information of the remote user who performed the push.
+
+All CGI environment variables are available to each of the hooks
+invoked by the 'git-receive-pack'.
+
+Author
+------
+Written by Shawn O. Pearce <spearce@spearce.org>.
+
+Documentation
+--------------
+Documentation by Shawn O. Pearce <spearce@spearce.org>.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt
index aef383e0b1..ddf7a18dc4 100644
--- a/Documentation/git-http-push.txt
+++ b/Documentation/git-http-push.txt
@@ -82,11 +82,11 @@ destination side.
Without '--force', the <src> ref is stored at the remote only if
<dst> does not exist, or <dst> is a proper subset (i.e. an
-ancestor) of <src>. This check, known as "fast forward check",
+ancestor) of <src>. This check, known as "fast-forward check",
is performed in order to avoid accidentally overwriting the
remote ref and lose other peoples' commits from there.
-With '--force', the fast forward check is disabled for all refs.
+With '--force', the fast-forward check is disabled for all refs.
Optionally, a <ref> parameter can be prefixed with a plus '+' sign
to disable the fast-forward check only on that ref.
diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt
index 021066e95d..625723e41f 100644
--- a/Documentation/git-ls-files.txt
+++ b/Documentation/git-ls-files.txt
@@ -48,8 +48,10 @@ OPTIONS
-i::
--ignored::
- Show ignored files in the output.
- Note that this also reverses any exclude list present.
+ Show only ignored files in the output. When showing files in the
+ index, print only those matched by an exclude pattern. When
+ showing "other" files, show only those matched by an exclude
+ pattern.
-s::
--stage::
diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt
index 68ed6c0956..4a6f7f3a2d 100644
--- a/Documentation/git-mergetool.txt
+++ b/Documentation/git-mergetool.txt
@@ -27,7 +27,7 @@ OPTIONS
Use the merge resolution program specified by <tool>.
Valid merge tools are:
kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge,
- diffuse, tortoisemerge, opendiff and araxis.
+ diffuse, tortoisemerge, opendiff, p4merge and araxis.
+
If a merge resolution program is not specified, 'git-mergetool'
will use the configuration variable `merge.tool`. If the
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
new file mode 100644
index 0000000000..94cceb1319
--- /dev/null
+++ b/Documentation/git-notes.txt
@@ -0,0 +1,60 @@
+git-notes(1)
+============
+
+NAME
+----
+git-notes - Add/inspect commit notes
+
+SYNOPSIS
+--------
+[verse]
+'git-notes' (edit [-F <file> | -m <msg>] | show) [commit]
+
+DESCRIPTION
+-----------
+This command allows you to add notes to commit messages, without
+changing the commit. To discern these notes from the message stored
+in the commit object, the notes are indented like the message, after
+an unindented line saying "Notes:".
+
+To disable commit notes, you have to set the config variable
+core.notesRef to the empty string. Alternatively, you can set it
+to a different ref, something like "refs/notes/bugzilla". This setting
+can be overridden by the environment variable "GIT_NOTES_REF".
+
+
+SUBCOMMANDS
+-----------
+
+edit::
+ Edit the notes for a given commit (defaults to HEAD).
+
+show::
+ Show the notes for a given commit (defaults to HEAD).
+
+
+OPTIONS
+-------
+-m <msg>::
+ Use the given note message (instead of prompting).
+ If multiple `-m` (or `-F`) options are given, their
+ values are concatenated as separate paragraphs.
+
+-F <file>::
+ Take the note message from the given file. Use '-' to
+ read the note message from the standard input.
+ If multiple `-F` (or `-m`) options are given, their
+ values are concatenated as separate paragraphs.
+
+
+Author
+------
+Written by Johannes Schindelin <johannes.schindelin@gmx.de>
+
+Documentation
+-------------
+Documentation by Johannes Schindelin
+
+GIT
+---
+Part of the linkgit:git[7] suite
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
index 2e4992970e..f54d433d36 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -9,8 +9,9 @@ git-pack-objects - Create a packed archive of objects
SYNOPSIS
--------
[verse]
-'git pack-objects' [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty]
- [--local] [--incremental] [--window=N] [--depth=N] [--all-progress]
+'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied]
+ [--no-reuse-delta] [--delta-base-offset] [--non-empty]
+ [--local] [--incremental] [--window=N] [--depth=N]
[--revs [--unpacked | --all]*] [--stdout | base-name]
[--keep-true-parents] < object-list
@@ -137,7 +138,7 @@ base-name::
--all-progress::
When --stdout is specified then progress report is
- displayed during the object count and deltification phases
+ displayed during the object count and compression phases
but inhibited during the write-out phase. The reason is
that in some cases the output stream is directly linked
to another command which may wish to display progress
@@ -146,6 +147,11 @@ base-name::
report for the write-out phase as well even if --stdout is
used.
+--all-progress-implied::
+ This is used to imply --all-progress whenever progress display
+ is activated. Unlike --all-progress this flag doesn't actually
+ force any progress display by itself.
+
-q::
This flag makes the command not to report its progress
on the standard error stream.
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 37c88953d1..52c0538df5 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -50,9 +50,9 @@ updated.
+
The object referenced by <src> is used to update the <dst> reference
on the remote side, but by default this is only allowed if the
-update can fast forward <dst>. By having the optional leading `{plus}`,
+update can fast-forward <dst>. By having the optional leading `{plus}`,
you can tell git to update the <dst> ref even when the update is not a
-fast forward. This does *not* attempt to merge <src> into <dst>. See
+fast-forward. This does *not* attempt to merge <src> into <dst>. See
EXAMPLES below for details.
+
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
@@ -60,7 +60,7 @@ EXAMPLES below for details.
Pushing an empty <src> allows you to delete the <dst> ref from
the remote repository.
+
-The special refspec `:` (or `{plus}:` to allow non-fast forward updates)
+The special refspec `:` (or `{plus}:` to allow non-fast-forward updates)
directs git to push "matching" branches: for every branch that exists on
the local side, the remote side is updated if a branch of the same name
already exists on the remote side. This is the default operation mode
@@ -176,10 +176,10 @@ summary::
For a successfully pushed ref, the summary shows the old and new
values of the ref in a form suitable for using as an argument to
`git log` (this is `<old>..<new>` in most cases, and
- `<old>...<new>` for forced non-fast forward updates). For a
+ `<old>...<new>` for forced non-fast-forward updates). For a
failed update, more details are given for the failure.
The string `rejected` indicates that git did not try to send the
- ref at all (typically because it is not a fast forward). The
+ ref at all (typically because it is not a fast-forward). The
string `remote rejected` indicates that the remote end refused
the update; this rejection is typically caused by a hook on the
remote side. The string `remote failure` indicates that the
@@ -347,9 +347,9 @@ git push origin :experimental::
git push origin {plus}dev:master::
Update the origin repository's master branch with the dev branch,
- allowing non-fast forward updates. *This can leave unreferenced
+ allowing non-fast-forward updates. *This can leave unreferenced
commits dangling in the origin repository.* Consider the
- following situation, where a fast forward is not possible:
+ following situation, where a fast-forward is not possible:
+
----
o---o---o---A---B origin/master
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index 4a932b08c6..a10ce4ba40 100644
--- a/Documentation/git-read-tree.txt
+++ b/Documentation/git-read-tree.txt
@@ -144,7 +144,7 @@ Two Tree Merge
Typically, this is invoked as `git read-tree -m $H $M`, where $H
is the head commit of the current repository, and $M is the head
of a foreign tree, which is simply ahead of $H (i.e. we are in a
-fast forward situation).
+fast-forward situation).
When two trees are specified, the user is telling 'git-read-tree'
the following:
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 33e0ef1f6d..ca5e1e8653 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -228,13 +228,23 @@ OPTIONS
Use merging strategies to rebase. When the recursive (default) merge
strategy is used, this allows rebase to be aware of renames on the
upstream side.
++
+Note that a rebase merge works by replaying each commit from the working
+branch on top of the <upstream> branch. Because of this, when a merge
+conflict happens, the side reported as 'ours' is the so-far rebased
+series, starting with <upstream>, and 'theirs' is the working branch. In
+other words, the sides are swapped.
-s <strategy>::
--strategy=<strategy>::
Use the given merge strategy.
- If there is no `-s` option, a built-in list of strategies
- is used instead ('git-merge-recursive' when merging a single
- head, 'git-merge-octopus' otherwise). This implies --merge.
+ If there is no `-s` option 'git-merge-recursive' is used
+ instead. This implies --merge.
++
+Because 'git-rebase' replays each commit from the working branch
+on top of the <upstream> branch using the given strategy, using
+the 'ours' strategy simply discards all patches from the <branch>,
+which makes little sense.
-q::
--quiet::
diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt
index 514f03c979..cb5f405280 100644
--- a/Documentation/git-receive-pack.txt
+++ b/Documentation/git-receive-pack.txt
@@ -20,7 +20,7 @@ The UI for the protocol is on the 'git-send-pack' side, and the
program pair is meant to be used to push updates to remote
repository. For pull operations, see linkgit:git-fetch-pack[1].
-The command allows for creation and fast forwarding of sha1 refs
+The command allows for creation and fast-forwarding of sha1 refs
(heads/tags) on the remote end (strictly speaking, it is the
local end 'git-receive-pack' runs, but to the user who is sitting at
the send-pack end, it is updating the remote. Confused?)
diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt
index 173ee232f2..8beb42dbb9 100644
--- a/Documentation/git-remote-helpers.txt
+++ b/Documentation/git-remote-helpers.txt
@@ -34,15 +34,51 @@ Commands are given by the caller on the helper's standard input, one per line.
value of the ref. A space-separated list of attributes follows
the name; unrecognized attributes are ignored. After the
complete list, outputs a blank line.
++
+If 'push' is supported this may be called as 'list for-push'
+to obtain the current refs prior to sending one or more 'push'
+commands to the helper.
+
+'option' <name> <value>::
+ Set the transport helper option <name> to <value>. Outputs a
+ single line containing one of 'ok' (option successfully set),
+ 'unsupported' (option not recognized) or 'error <msg>'
+ (option <name> is supported but <value> is not correct
+ for it). Options should be set before other commands,
+ and may how those commands behave.
++
+Supported if the helper has the "option" capability.
'fetch' <sha1> <name>::
- Fetches the given object, writing the necessary objects to the
- database. Outputs a blank line when the fetch is
- complete. Only objects which were reported in the ref list
- with a sha1 may be fetched this way.
+ Fetches the given object, writing the necessary objects
+ to the database. Fetch commands are sent in a batch, one
+ per line, and the batch is terminated with a blank line.
+ Outputs a single blank line when all fetch commands in the
+ same batch are complete. Only objects which were reported
+ in the ref list with a sha1 may be fetched this way.
++
+Optionally may output a 'lock <file>' line indicating a file under
+GIT_DIR/objects/pack which is keeping a pack until refs can be
+suitably updated.
+
Supported if the helper has the "fetch" capability.
+'push' +<src>:<dst>::
+ Pushes the given <src> commit or branch locally to the
+ remote branch described by <dst>. A batch sequence of
+ one or more push commands is terminated with a blank line.
++
+Zero or more protocol options may be entered after the last 'push'
+command, before the batch's terminating blank line.
++
+When the push is complete, outputs one or more 'ok <dst>' or
+'error <dst> <why>?' lines to indicate success or failure of
+each pushed ref. The status report output is terminated by
+a blank line. The option field <why> may be quoted in a C
+style string if it contains an LF.
++
+Supported if the helper has the "push" 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
@@ -57,10 +93,49 @@ CAPABILITIES
'fetch'::
This helper supports the 'fetch' command.
+'option'::
+ This helper supports the option command.
+
+'push'::
+ This helper supports the 'push' command.
+
REF LIST ATTRIBUTES
-------------------
-None are defined yet, but the caller must accept any which are supplied.
+'for-push'::
+ The caller wants to use the ref list to prepare push
+ commands. A helper might chose to acquire the ref list by
+ opening a different type of connection to the destination.
+
+OPTIONS
+-------
+'option verbosity' <N>::
+ Change the level of messages displayed by the helper.
+ When N is 0 the end-user has asked the process to be
+ quiet, and the helper should produce only error output.
+ N of 1 is the default level of verbosity, higher values
+ of N correspond to the number of -v flags passed on the
+ command line.
+
+'option progress' \{'true'|'false'\}::
+ Enable (or disable) progress messages displayed by the
+ transport helper during a command.
+
+'option depth' <depth>::
+ Deepen the history of a shallow repository.
+
+'option followtags' \{'true'|'false'\}::
+ If enabled the helper should automatically fetch annotated
+ tag objects if the object the tag points at was transferred
+ during the fetch command. If the tag is not fetched by
+ the helper a second fetch command will usually be sent to
+ ask for the tag specifically. Some helpers may be able to
+ use this option to avoid a second network connection.
+
+'option dry-run' \{'true'|'false'\}:
+ If true, pretend the operation completed successfully,
+ but don't actually change any repository data. For most
+ helpers this only applies to the 'push', if supported.
Documentation
-------------
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 82a3d29673..c272c92d4b 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -13,10 +13,10 @@ SYNOPSIS
'git remote add' [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>
'git remote rename' <old> <new>
'git remote rm' <name>
-'git remote set-head' <name> [-a | -d | <branch>]
-'git remote show' [-n] <name>
+'git remote set-head' <name> (-a | -d | <branch>)
+'git remote' [-v | --verbose] 'show' [-n] <name>
'git remote prune' [-n | --dry-run] <name>
-'git remote update' [-p | --prune] [group | remote]...
+'git remote' [-v | --verbose] 'update' [-p | --prune] [group | remote]...
DESCRIPTION
-----------
@@ -30,6 +30,7 @@ OPTIONS
-v::
--verbose::
Be a little more verbose and show remote url after name.
+ NOTE: This must be placed between `remote` and `subcommand`.
COMMANDS
diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt
index 8adc1ef55c..65a0da508a 100644
--- a/Documentation/git-replace.txt
+++ b/Documentation/git-replace.txt
@@ -17,31 +17,35 @@ DESCRIPTION
Adds a 'replace' reference in `.git/refs/replace/`
The name of the 'replace' reference is the SHA1 of the object that is
-replaced. The content of the replace reference is the SHA1 of the
+replaced. The content of the 'replace' reference is the SHA1 of the
replacement object.
-Unless `-f` is given, the replace reference must not yet exist in
+Unless `-f` is given, the 'replace' reference must not yet exist in
`.git/refs/replace/` directory.
-Replace references will be used by default by all git commands except
-those doing reachability traversal (prune, pack transfer and fsck).
+Replacement references will be used by default by all git commands
+except those doing reachability traversal (prune, pack transfer and
+fsck).
-It is possible to disable use of replacement refs for any command
-using the --no-replace-objects option just after "git".
+It is possible to disable use of replacement references for any
+command using the `--no-replace-objects` option just after 'git'.
-For example if commit "foo" has been replaced by commit "bar":
+For example if commit 'foo' has been replaced by commit 'bar':
------------------------------------------------
-$ git --no-replace-object cat-file commit foo
+$ git --no-replace-objects cat-file commit foo
------------------------------------------------
-show information about commit "foo", while:
+shows information about commit 'foo', while:
------------------------------------------------
$ git cat-file commit foo
------------------------------------------------
-show information about commit "bar".
+shows information about commit 'bar'.
+
+The 'GIT_NO_REPLACE_OBJECTS' environment variable can be set to
+achieve the same effect as the `--no-replace-objects` option.
OPTIONS
-------
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 469cf6dbac..2d27e405a3 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -150,7 +150,7 @@ Automatic merge failed; fix conflicts and then commit the result.
$ git reset --hard <2>
$ git pull . topic/branch <3>
Updating from 41223... to 13134...
-Fast forward
+Fast-forward
$ git reset --hard ORIG_HEAD <4>
------------
+
@@ -161,7 +161,7 @@ right now, so you decide to do that later.
which is a synonym for "git reset --hard HEAD" clears the mess
from the index file and the working tree.
<3> Merge a topic branch into the current branch, which resulted
-in a fast forward.
+in a fast-forward.
<4> But you decided that the topic branch is not ready for public
consumption yet. "pull" or "merge" always leaves the original
tip of the current branch in ORIG_HEAD, so resetting hard to it
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 767cf4d4bd..c85d7f4385 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -60,8 +60,8 @@ The --bcc option must be repeated for each user you want on the bcc list.
The --cc option must be repeated for each user you want on the cc list.
--compose::
- Use $GIT_EDITOR, core.editor, $VISUAL, or $EDITOR to edit an
- introductory message for the patch series.
+ Invoke a text editor (see GIT_EDITOR in linkgit:git-var[1])
+ to edit an introductory message for the patch series.
+
When '--compose' is used, git send-email will use the From, Subject, and
In-Reply-To headers specified in the message. If the body of the message
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index 399821832c..5a04c6eaf7 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -105,11 +105,11 @@ name. See linkgit:git-rev-parse[1].
Without '--force', the <src> ref is stored at the remote only if
<dst> does not exist, or <dst> is a proper subset (i.e. an
-ancestor) of <src>. This check, known as "fast forward check",
+ancestor) of <src>. This check, known as "fast-forward check",
is performed in order to avoid accidentally overwriting the
remote ref and lose other peoples' commits from there.
-With '--force', the fast forward check is disabled for all refs.
+With '--force', the fast-forward check is disabled for all refs.
Optionally, a <ref> parameter can be prefixed with a plus '+' sign
to disable the fast-forward check only on that ref.
diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index f4429bdc68..70f400b266 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -8,7 +8,7 @@ git-show-ref - List references in a local repository
SYNOPSIS
--------
[verse]
-'git show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference]
+'git show-ref' [-q|--quiet] [--verify] [--head] [-d|--dereference]
[-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags]
[--heads] [--] <pattern>...
'git show-ref' --exclude-existing[=<pattern>] < ref-list
@@ -30,7 +30,6 @@ the `.git` directory.
OPTIONS
-------
--h::
--head::
Show the HEAD reference.
diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt
index 5ccdd18c89..4ef70c42eb 100644
--- a/Documentation/git-submodule.txt
+++ b/Documentation/git-submodule.txt
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git submodule' [--quiet] add [-b branch]
- [--reference <repository>] [--] <repository> <path>
+ [--reference <repository>] [--] <repository> [<path>]
'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
'git submodule' [--quiet] init [--] [<path>...]
'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase]
@@ -69,7 +69,11 @@ add::
to the changeset to be committed next to the current
project: the current project is termed the "superproject".
+
-This requires two arguments: <repository> and <path>.
+This requires at least one argument: <repository>. The optional
+argument <path> is the relative location for the cloned submodule
+to exist in the superproject. If <path> is not given, the
+"humanish" part of the source repository is used ("repo" for
+"/path/to/repo.git" and "foo" for "host.xz:foo/.git").
+
<repository> is the URL of the new submodule's origin repository.
This may be either an absolute URL, or (if it begins with ./
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index 1812890a7e..4cdca0d874 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -320,6 +320,13 @@ Any other arguments are passed directly to 'git log'
directories. The output is suitable for appending to
the $GIT_DIR/info/exclude file.
+'mkdirs'::
+ Attempts to recreate empty directories that core git cannot track
+ based on information in $GIT_DIR/svn/<refname>/unhandled.log files.
+ Empty directories are automatically recreated when using
+ "git svn clone" and "git svn rebase", so "mkdirs" is intended
+ for use after commands like "git checkout" or "git reset".
+
'commit-diff'::
Commits the diff of two tree-ish arguments from the
command-line. This command does not rely on being inside an `git svn
@@ -735,6 +742,16 @@ merges you've made. Furthermore, if you merge or pull from a git branch
that is a mirror of an SVN branch, 'dcommit' may commit to the wrong
branch.
+If you do merge, note the following rule: 'git svn dcommit' will
+attempt to commit on top of the SVN commit named in
+------------------------------------------------------------------------
+git log --grep=^git-svn-id: --first-parent -1
+------------------------------------------------------------------------
+You 'must' therefore ensure that the most recent commit of the branch
+you want to dcommit to is the 'first' parent of the merge. Chaos will
+ensue otherwise, especially if the first parent is an older commit on
+the same SVN branch.
+
'git clone' does not clone branches under the refs/remotes/ hierarchy or
any 'git svn' metadata, or config. So repositories created and managed with
using 'git svn' should use 'rsync' for cloning, if cloning is to be done
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
index 25e0bbea86..6052484ab9 100644
--- a/Documentation/git-update-index.txt
+++ b/Documentation/git-update-index.txt
@@ -99,6 +99,10 @@ in the index e.g. when merging in a commit;
thus, in case the assumed-untracked file is changed upstream,
you will need to handle the situation manually.
+--really-refresh::
+ Like '--refresh', but checks stat information unconditionally,
+ without regard to the "assume unchanged" setting.
+
-g::
--again::
Runs 'git-update-index' itself on the paths whose index
@@ -308,7 +312,7 @@ Configuration
-------------
The command honors `core.filemode` configuration variable. If
-your repository is on an filesystem whose executable bits are
+your repository is on a filesystem whose executable bits are
unreliable, this should be set to 'false' (see linkgit:git-config[1]).
This causes the command to ignore differences in file modes recorded
in the index and the file mode on the filesystem if they differ only on
diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt
index e2f4c0901b..ef6aa81872 100644
--- a/Documentation/git-var.txt
+++ b/Documentation/git-var.txt
@@ -36,6 +36,20 @@ GIT_AUTHOR_IDENT::
GIT_COMMITTER_IDENT::
The person who put a piece of code into git.
+GIT_EDITOR::
+ Text editor for use by git commands. The value is meant to be
+ interpreted by the shell when it is used. Examples: `~/bin/vi`,
+ `$SOME_ENVIRONMENT_VARIABLE`, `"C:\Program Files\Vim\gvim.exe"
+ --nofork`. The order of preference is the `$GIT_EDITOR`
+ environment variable, then `core.editor` configuration, then
+ `$VISUAL`, then `$EDITOR`, and then finally 'vi'.
+
+GIT_PAGER::
+ Text viewer for use by git commands (e.g., 'less'). The value
+ is meant to be interpreted by the shell. The order of preference
+ is the `$GIT_PAGER` environment variable, then `core.pager`
+ configuration, then `$PAGER`, and then finally 'less'.
+
Diagnostics
-----------
You don't exist. Go away!::
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 0f536793df..8e577cc4fe 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -43,9 +43,10 @@ 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.5.2/git.html[documentation for release 1.6.5.2]
+* link:v1.6.5.3/git.html[documentation for release 1.6.5.3]
* release notes for
+ link:RelNotes-1.6.5.3.txt[1.6.5.3],
link:RelNotes-1.6.5.2.txt[1.6.5.2],
link:RelNotes-1.6.5.1.txt[1.6.5.1],
link:RelNotes-1.6.5.txt[1.6.5].
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index b3640c4e64..b7380b069a 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -185,7 +185,7 @@ object is. git will tell you that you have a "blob" object (i.e., just a
regular file), and you can see the contents with
----------------
-$ git cat-file "blob" 557db03
+$ git cat-file blob 557db03
----------------
which will print out "Hello World". The object `557db03` is nothing
@@ -993,7 +993,7 @@ would be different)
----------------
Updating from ae3a2da... to a80b4aa....
-Fast forward (no commit created; -m option ignored)
+Fast-forward (no commit created; -m option ignored)
example | 1 +
hello | 1 +
2 files changed, 2 insertions(+), 0 deletions(-)
@@ -1003,7 +1003,7 @@ Because your branch did not contain anything more than what had
already been merged into the `master` branch, the merge operation did
not actually do a merge. Instead, it just updated the top of
the tree of your branch to that of the `master` branch. This is
-often called 'fast forward' merge.
+often called 'fast-forward' merge.
You can run `gitk \--all` again to see how the commit ancestry
looks like, or run 'show-branch', which tells you this.
@@ -1188,7 +1188,7 @@ $ git show-branch
--
+ [mybranch] Some work.
* [master] Some fun.
-*+ [mybranch^] New day.
+*+ [mybranch^] Initial commit
------------
Now we are ready to experiment with the merge by hand.
@@ -1204,11 +1204,11 @@ $ mb=$(git merge-base HEAD mybranch)
The command writes the commit object name of the common ancestor
to the standard output, so we captured its output to a variable,
because we will be using it in the next step. By the way, the common
-ancestor commit is the "New day." commit in this case. You can
+ancestor commit is the "Initial commit" commit in this case. You can
tell it by:
------------
-$ git name-rev $mb
+$ git name-rev --name-only --tags $mb
my-first-tag
------------
@@ -1237,8 +1237,8 @@ inspect the index file with this command:
------------
$ git ls-files --stage
100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example
-100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello
-100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello
+100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello
+100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello
100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
------------
@@ -1253,8 +1253,8 @@ To look at only non-zero stages, use `\--unmerged` flag:
------------
$ git ls-files --unmerged
-100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello
-100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello
+100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello
+100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello
100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
------------
@@ -1283,8 +1283,8 @@ the working tree.. This can be seen if you run `ls-files
------------
$ git ls-files --stage
100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example
-100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello
-100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello
+100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello
+100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello
100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
------------
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 06e0f315c3..4cc3d1387f 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -229,7 +229,7 @@ from updating that ref.
This hook can be used to prevent 'forced' update on certain refs by
making sure that the object name is a commit object that is a
descendant of the commit object named by the old object name.
-That is, to enforce a "fast forward only" policy.
+That is, to enforce a "fast-forward only" policy.
It could also be used to log the old..new status. However, it
does not know the entire set of branches, so it would end up
diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt
index 2b021e3c15..91c0eea890 100644
--- a/Documentation/gitworkflows.txt
+++ b/Documentation/gitworkflows.txt
@@ -209,6 +209,121 @@ chance to see if their in-progress work will be compatible. `git.git`
has such an official throw-away integration branch called 'pu'.
+Branch management for a release
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Assuming you are using the merge approach discussed above, when you
+are releasing your project you will need to do some additional branch
+management work.
+
+A feature release is created from the 'master' branch, since 'master'
+tracks the commits that should go into the next feature release.
+
+The 'master' branch is supposed to be a superset of 'maint'. If this
+condition does not hold, then 'maint' contains some commits that
+are not included on 'master'. The fixes represented by those commits
+will therefore not be included in your feature release.
+
+To verify that 'master' is indeed a superset of 'maint', use git log:
+
+.Verify 'master' is a superset of 'maint'
+[caption="Recipe: "]
+=====================================
+git log master..maint
+=====================================
+
+This command should not list any commits. Otherwise, check out
+'master' and merge 'maint' into it.
+
+Now you can proceed with the creation of the feature release. Apply a
+tag to the tip of 'master' indicating the release version:
+
+.Release tagging
+[caption="Recipe: "]
+=====================================
+`git tag -s -m "GIT X.Y.Z" vX.Y.Z master`
+=====================================
+
+You need to push the new tag to a public git server (see
+"DISTRIBUTED WORKFLOWS" below). This makes the tag available to
+others tracking your project. The push could also trigger a
+post-update hook to perform release-related items such as building
+release tarballs and preformatted documentation pages.
+
+Similarly, for a maintenance release, 'maint' is tracking the commits
+to be released. Therefore, in the steps above simply tag and push
+'maint' rather than 'master'.
+
+
+Maintenance branch management after a feature release
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+After a feature release, you need to manage your maintenance branches.
+
+First, if you wish to continue to release maintenance fixes for the
+feature release made before the recent one, then you must create
+another branch to track commits for that previous release.
+
+To do this, the current maintenance branch is copied to another branch
+named with the previous release version number (e.g. maint-X.Y.(Z-1)
+where X.Y.Z is the current release).
+
+.Copy maint
+[caption="Recipe: "]
+=====================================
+`git branch maint-X.Y.(Z-1) maint`
+=====================================
+
+The 'maint' branch should now be fast-forwarded to the newly released
+code so that maintenance fixes can be tracked for the current release:
+
+.Update maint to new release
+[caption="Recipe: "]
+=====================================
+* `git checkout maint`
+* `git merge --ff-only master`
+=====================================
+
+If the merge fails because it is not a fast-forward, then it is
+possible some fixes on 'maint' were missed in the feature release.
+This will not happen if the content of the branches was verified as
+described in the previous section.
+
+
+Branch management for next and pu after a feature release
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+After a feature release, the integration branch 'next' may optionally be
+rewound and rebuilt from the tip of 'master' using the surviving
+topics on 'next':
+
+.Rewind and rebuild next
+[caption="Recipe: "]
+=====================================
+* `git checkout next`
+* `git reset --hard master`
+* `git merge ai/topic_in_next1`
+* `git merge ai/topic_in_next2`
+* ...
+=====================================
+
+The advantage of doing this is that the history of 'next' will be
+clean. For example, some topics merged into 'next' may have initially
+looked promising, but were later found to be undesirable or premature.
+In such a case, the topic is reverted out of 'next' but the fact
+remains in the history that it was once merged and reverted. By
+recreating 'next', you give another incarnation of such topics a clean
+slate to retry, and a feature release is a good point in history to do
+so.
+
+If you do this, then you should make a public announcement indicating
+that 'next' was rewound and rebuilt.
+
+The same rewind and rebuild process may be followed for 'pu'. A public
+announcement is not necessary since 'pu' is a throw-away branch, as
+described above.
+
+
DISTRIBUTED WORKFLOWS
---------------------
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index 43d84d15e9..1f029f8aa0 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -124,7 +124,7 @@ to point at the new commit.
An evil merge is a <<def_merge,merge>> that introduces changes that
do not appear in any <<def_parent,parent>>.
-[[def_fast_forward]]fast forward::
+[[def_fast_forward]]fast-forward::
A fast-forward is a special type of <<def_merge,merge>> where you have a
<<def_revision,revision>> and you are "merging" another
<<def_branch,branch>>'s changes that happen to be a descendant of what
@@ -220,7 +220,7 @@ to point at the new commit.
conflict, manual intervention may be required to complete the
merge.
+
-As a noun: unless it is a <<def_fast_forward,fast forward>>, a
+As a noun: unless it is a <<def_fast_forward,fast-forward>>, a
successful merge results in the creation of a new <<def_commit,commit>>
representing the result of the merge, and having as
<<def_parent,parents>> the tips of the merged <<def_branch,branches>>.
diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt
index 4357e26913..d527b30770 100644
--- a/Documentation/howto/maintain-git.txt
+++ b/Documentation/howto/maintain-git.txt
@@ -59,7 +59,7 @@ The policy.
not yet pass the criteria set for 'next'.
- The tips of 'master', 'maint' and 'next' branches will always
- fast forward, to allow people to build their own
+ fast-forward, to allow people to build their own
customization on top of them.
- Usually 'master' contains all of 'maint', 'next' contains all
diff --git a/Documentation/howto/revert-branch-rebase.txt b/Documentation/howto/revert-branch-rebase.txt
index e70d8a31e7..8c32da6deb 100644
--- a/Documentation/howto/revert-branch-rebase.txt
+++ b/Documentation/howto/revert-branch-rebase.txt
@@ -85,7 +85,7 @@ Fortunately I did not have to; what I have in the current branch
------------------------------------------------
$ git checkout master
-$ git merge revert-c99 ;# this should be a fast forward
+$ git merge revert-c99 ;# this should be a fast-forward
Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c...
cache.h | 8 ++++----
commit.c | 2 +-
@@ -95,7 +95,7 @@ Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c...
5 files changed, 8 insertions(+), 8 deletions(-)
------------------------------------------------
-There is no need to redo the test at this point. We fast forwarded
+There is no need to redo the test at this point. We fast-forwarded
and we know 'master' matches 'revert-c99' exactly. In fact:
------------------------------------------------
diff --git a/Documentation/howto/update-hook-example.txt b/Documentation/howto/update-hook-example.txt
index 697d918885..b7f8d416d6 100644
--- a/Documentation/howto/update-hook-example.txt
+++ b/Documentation/howto/update-hook-example.txt
@@ -76,7 +76,7 @@ case "$1" in
if expr "$2" : '0*$' >/dev/null; then
info "The branch '$1' is new..."
else
- # updating -- make sure it is a fast forward
+ # updating -- make sure it is a fast-forward
mb=$(git-merge-base "$2" "$3")
case "$mb,$2" in
"$2,$mb") info "Update is fast-forward" ;;
diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt
index c0f96e7070..a403155052 100644
--- a/Documentation/merge-config.txt
+++ b/Documentation/merge-config.txt
@@ -23,7 +23,7 @@ merge.tool::
Controls which merge resolution program is used by
linkgit:git-mergetool[1]. Valid built-in values are: "kdiff3",
"tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff",
- "diffuse", "ecmerge", "tortoisemerge", "araxis", and
+ "diffuse", "ecmerge", "tortoisemerge", "p4merge", "araxis" and
"opendiff". Any other value is treated is custom merge tool
and there must be a corresponding mergetool.<tool>.cmd option.
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index 48d04a5d88..fec3394305 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -49,6 +49,11 @@ merge.
With --no-squash perform the merge and commit the result. This
option can be used to override --squash.
+--ff-only::
+ Refuse to merge and exit with a non-zero status unless the
+ current `HEAD` is already up-to-date or the merge can be
+ resolved as a fast-forward.
+
-s <strategy>::
--strategy=<strategy>::
Use the given merge strategy; can be supplied more than
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
index 4365b7e842..42910a3d5e 100644
--- a/Documentation/merge-strategies.txt
+++ b/Documentation/merge-strategies.txt
@@ -29,8 +29,9 @@ octopus::
pulling or merging more than one branch.
ours::
- This resolves any number of heads, but the result of the
- merge is always the current branch head. It is meant to
+ This resolves any number of heads, but the resulting tree of the
+ 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.
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 38b9904791..7ff6a6ce22 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -123,6 +123,7 @@ The placeholders are:
- '%s': subject
- '%f': sanitized subject line, suitable for a filename
- '%b': body
+- '%N': commit notes
- '%gD': reflog selector, e.g., `refs/stash@\{1\}`
- '%gd': shortened reflog selector, e.g., `stash@\{1\}`
- '%gs': reflog subject
@@ -134,6 +135,10 @@ The placeholders are:
- '%m': left, right or boundary mark
- '%n': newline
- '%x00': print a byte from a hex code
+- '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of
+ linkgit:git-shortlog[1]. NOTE: Color placeholders (`%C*`) are not
+ recognized as having no width, so they should not be put into wrapped
+ sections.
NOTE: Some placeholders may depend on other options given to the
revision traversal engine. For example, the `%g*` reflog options will
diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt
index f9811f2473..0551ebdfaf 100644
--- a/Documentation/pull-fetch-param.txt
+++ b/Documentation/pull-fetch-param.txt
@@ -4,6 +4,13 @@
(see the section <<URLS,GIT URLS>> below) or the name
of a remote (see the section <<REMOTES,REMOTES>> below).
+ifndef::git-pull[]
+<group>::
+ A name referring to a list of repositories as the value
+ of remotes.<group> in the configuration file.
+ (See linkgit:git-config[1]).
+endif::git-pull[]
+
<refspec>::
The format of a <refspec> parameter is an optional plus
`{plus}`, followed by the source ref <src>, followed
@@ -11,9 +18,9 @@
+
The remote ref that matches <src>
is fetched, and if <dst> is not empty string, the local
-ref that matches it is fast forwarded using <src>.
+ref that matches it is fast-forwarded using <src>.
If the optional plus `+` is used, the local ref
-is updated even if it does not result in a fast forward
+is updated even if it does not result in a fast-forward
update.
+
[NOTE]
diff --git a/Documentation/technical/api-history-graph.txt b/Documentation/technical/api-history-graph.txt
index d66e61b1ec..d6fc90ac7e 100644
--- a/Documentation/technical/api-history-graph.txt
+++ b/Documentation/technical/api-history-graph.txt
@@ -11,9 +11,6 @@ Core functions:
* `graph_init()` creates a new `struct git_graph`
-* `graph_release()` destroys a `struct git_graph`, and frees the memory
- associated with it.
-
* `graph_update()` moves the graph to a new commit.
* `graph_next_line()` outputs the next line of the graph into a strbuf. It
@@ -134,8 +131,6 @@ while ((commit = get_revision(opts)) != NULL) {
putchar(opts->diffopt.line_termination);
}
}
-
-graph_release(graph);
------------
Sample output
diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt
index 9cd48b4859..7950eeeda4 100644
--- a/Documentation/technical/pack-protocol.txt
+++ b/Documentation/technical/pack-protocol.txt
@@ -1,41 +1,494 @@
-Pack transfer protocols
-=======================
-
-There are two Pack push-pull protocols.
-
-upload-pack (S) | fetch/clone-pack (C) protocol:
-
- # Tell the puller what commits we have and what their names are
- S: SHA1 name
- S: ...
- S: SHA1 name
- S: # flush -- it's your turn
- # Tell the pusher what commits we want, and what we have
- C: want name
- C: ..
- C: want name
- C: have SHA1
- C: have SHA1
- C: ...
- C: # flush -- occasionally ask "had enough?"
- S: NAK
- C: have SHA1
- C: ...
- C: have SHA1
- S: ACK
- C: done
- S: XXXXXXX -- packfile contents.
-
-send-pack | receive-pack protocol.
-
- # Tell the pusher what commits we have and what their names are
- C: SHA1 name
- C: ...
- C: SHA1 name
- C: # flush -- it's your turn
- # Tell the puller what the pusher has
- S: old-SHA1 new-SHA1 name
- S: old-SHA1 new-SHA1 name
- S: ...
- S: # flush -- done with the list
- S: XXXXXXX --- packfile contents.
+Packfile transfer protocols
+===========================
+
+Git supports transferring data in packfiles over the ssh://, git:// and
+file:// transports. There exist two sets of protocols, one for pushing
+data from a client to a server and another for fetching data from a
+server to a client. All three transports (ssh, git, file) use the same
+protocol to transfer data.
+
+The processes invoked in the canonical Git implementation are 'upload-pack'
+on the server side and 'fetch-pack' on the client side for fetching data;
+then 'receive-pack' on the server and 'send-pack' on the client for pushing
+data. The protocol functions to have a server tell a client what is
+currently on the server, then for the two to negotiate the smallest amount
+of data to send in order to fully update one or the other.
+
+Transports
+----------
+There are three transports over which the packfile protocol is
+initiated. The Git transport is a simple, unauthenticated server that
+takes the command (almost always 'upload-pack', though Git
+servers can be configured to be globally writable, in which 'receive-
+pack' initiation is also allowed) with which the client wishes to
+communicate and executes it and connects it to the requesting
+process.
+
+In the SSH transport, the client just runs the 'upload-pack'
+or 'receive-pack' process on the server over the SSH protocol and then
+communicates with that invoked process over the SSH connection.
+
+The file:// transport runs the 'upload-pack' or 'receive-pack'
+process locally and communicates with it over a pipe.
+
+Git Transport
+-------------
+
+The Git transport starts off by sending the command and repository
+on the wire using the pkt-line format, followed by a NUL byte and a
+hostname paramater, terminated by a NUL byte.
+
+ 0032git-upload-pack /project.git\0host=myserver.com\0
+
+--
+ git-proto-request = request-command SP pathname NUL [ host-parameter NUL ]
+ request-command = "git-upload-pack" / "git-receive-pack" /
+ "git-upload-archive" ; case sensitive
+ pathname = *( %x01-ff ) ; exclude NUL
+ host-parameter = "host=" hostname [ ":" port ]
+--
+
+Only host-parameter is allowed in the git-proto-request. Clients
+MUST NOT attempt to send additional parameters. It is used for the
+git-daemon name based virtual hosting. See --interpolated-path
+option to git daemon, with the %H/%CH format characters.
+
+Basically what the Git client is doing to connect to an 'upload-pack'
+process on the server side over the Git protocol is this:
+
+ $ echo -e -n \
+ "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
+ nc -v example.com 9418
+
+
+SSH Transport
+-------------
+
+Initiating the upload-pack or receive-pack processes over SSH is
+executing the binary on the server via SSH remote execution.
+It is basically equivalent to running this:
+
+ $ ssh git.example.com "git-upload-pack '/project.git'"
+
+For a server to support Git pushing and pulling for a given user over
+SSH, that user needs to be able to execute one or both of those
+commands via the SSH shell that they are provided on login. On some
+systems, that shell access is limited to only being able to run those
+two commands, or even just one of them.
+
+In an ssh:// format URI, it's absolute in the URI, so the '/' after
+the host name (or port number) is sent as an argument, which is then
+read by the remote git-upload-pack exactly as is, so it's effectively
+an absolute path in the remote filesystem.
+
+ git clone ssh://user@example.com/project.git
+ |
+ v
+ ssh user@example.com "git-upload-pack '/project.git'"
+
+In a "user@host:path" format URI, its relative to the user's home
+directory, because the Git client will run:
+
+ git clone user@example.com:project.git
+ |
+ v
+ ssh user@example.com "git-upload-pack 'project.git'"
+
+The exception is if a '~' is used, in which case
+we execute it without the leading '/'.
+
+ ssh://user@example.com/~alice/project.git,
+ |
+ v
+ ssh user@example.com "git-upload-pack '~alice/project.git'"
+
+A few things to remember here:
+
+- The "command name" is spelled with dash (e.g. git-upload-pack), but
+ this can be overridden by the client;
+
+- The repository path is always quoted with single quotes.
+
+Fetching Data From a Server
+===========================
+
+When one Git repository wants to get data that a second repository
+has, the first can 'fetch' from the second. This operation determines
+what data the server has that the client does not then streams that
+data down to the client in packfile format.
+
+
+Reference Discovery
+-------------------
+
+When the client initially connects the server will immediately respond
+with a listing of each reference it has (all branches and tags) along
+with the object name that each reference currently points to.
+
+ $ echo -e -n "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
+ nc -v example.com 9418
+ 00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag
+ 00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration
+ 003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 refs/heads/master
+ 003cb88d2441cac0977faf98efc80305012112238d9d refs/tags/v0.9
+ 003c525128480b96c89e6418b1e40909bf6c5b2d580f refs/tags/v1.0
+ 003fe92df48743b7bc7d26bcaabfddde0a1e20cae47c refs/tags/v1.0^{}
+ 0000
+
+Server SHOULD terminate each non-flush line using LF ("\n") terminator;
+client MUST NOT complain if there is no terminator.
+
+The returned response is a pkt-line stream describing each ref and
+its current value. The stream MUST be sorted by name according to
+the C locale ordering.
+
+If HEAD is a valid ref, HEAD MUST appear as the first advertised
+ref. If HEAD is not a valid ref, HEAD MUST NOT appear in the
+advertisement list at all, but other refs may still appear.
+
+The stream MUST include capability declarations behind a NUL on the
+first ref. The peeled value of a ref (that is "ref^{}") MUST be
+immediately after the ref itself, if presented. A conforming server
+MUST peel the ref if its an annotated tag.
+
+----
+ advertised-refs = (no-refs / list-of-refs)
+ flush-pkt
+
+ no-refs = PKT-LINE(zero-id SP "capabilities^{}"
+ NUL capability-list LF)
+
+ list-of-refs = first-ref *other-ref
+ first-ref = PKT-LINE(obj-id SP refname
+ NUL capability-list LF)
+
+ other-ref = PKT-LINE(other-tip / other-peeled)
+ other-tip = obj-id SP refname LF
+ other-peeled = obj-id SP refname "^{}" LF
+
+ capability-list = capability *(SP capability)
+ capability = 1*(LC_ALPHA / DIGIT / "-" / "_")
+ LC_ALPHA = %x61-7A
+----
+
+Server and client MUST use lowercase for obj-id, both MUST treat obj-id
+as case-insensitive.
+
+See protocol-capabilities.txt for a list of allowed server capabilities
+and descriptions.
+
+Packfile Negotiation
+--------------------
+After reference and capabilities discovery, the client can decide
+to terminate the connection by sending a flush-pkt, telling the
+server it can now gracefully terminate (as happens with the ls-remote
+command) or it can enter the negotiation phase, where the client and
+server determine what the minimal packfile necessary for transport is.
+
+Once the client has the initial list of references that the server
+has, as well as the list of capabilities, it will begin telling the
+server what objects it wants and what objects it has, so the server
+can make a packfile that only contains the objects that the client needs.
+The client will also send a list of the capabilities it wants to be in
+effect, out of what the server said it could do with the first 'want' line.
+
+----
+ upload-request = want-list
+ have-list
+ compute-end
+
+ want-list = first-want
+ *additional-want
+ flush-pkt
+
+ first-want = PKT-LINE("want" SP obj-id SP capability-list LF)
+ additional-want = PKT-LINE("want" SP obj-id LF)
+
+ have-list = *have-line
+ have-line = PKT-LINE("have" SP obj-id LF)
+ compute-end = flush-pkt / PKT-LINE("done")
+----
+
+Clients MUST send all the obj-ids it wants from the reference
+discovery phase as 'want' lines. Clients MUST send at least one
+'want' command in the request body. Clients MUST NOT mention an
+obj-id in a 'want' command which did not appear in the response
+obtained through ref discovery.
+
+If client is requesting a shallow clone, it will now send a 'deepen'
+line with the depth it is requesting.
+
+Once all the "want"s (and optional 'deepen') are transferred,
+clients MUST send a flush-pkt. If the client has all the references
+on the server, client flushes and disconnects.
+
+TODO: shallow/unshallow response and document the deepen command in the ABNF.
+
+Now the client will send a list of the obj-ids it has using 'have'
+lines. In multi_ack mode, the canonical implementation will send up
+to 32 of these at a time, then will send a flush-pkt. The canonical
+implementation will skip ahead and send the next 32 immediately,
+so that there is always a block of 32 "in-flight on the wire" at a
+time.
+
+If the server reads 'have' lines, it then will respond by ACKing any
+of the obj-ids the client said it had that the server also has. The
+server will ACK obj-ids differently depending on which ack mode is
+chosen by the client.
+
+In multi_ack mode:
+
+ * the server will respond with 'ACK obj-id continue' for any common
+ commits.
+
+ * once the server has found an acceptable common base commit and is
+ ready to make a packfile, it will blindly ACK all 'have' obj-ids
+ back to the client.
+
+ * the server will then send a 'NACK' and then wait for another response
+ from the client - either a 'done' or another list of 'have' lines.
+
+In multi_ack_detailed mode:
+
+ * the server will differentiate the ACKs where it is signaling
+ that it is ready to send data with 'ACK obj-id ready' lines, and
+ signals the identified common commits with 'ACK obj-id common' lines.
+
+Without either multi_ack or multi_ack_detailed:
+
+ * upload-pack sends "ACK obj-id" on the first common object it finds.
+ After that it says nothing until the client gives it a "done".
+
+ * upload-pack sends "NAK" on a flush-pkt if no common object
+ has been found yet. If one has been found, and thus an ACK
+ was already sent, its silent on the flush-pkt.
+
+After the client has gotten enough ACK responses that it can determine
+that the server has enough information to send an efficient packfile
+(in the canonical implementation, this is determined when it has received
+enough ACKs that it can color everything left in the --date-order queue
+as common with the server, or the --date-order queue is empty), or the
+client determines that it wants to give up (in the canonical implementation,
+this is determined when the client sends 256 'have' lines without getting
+any of them ACKed by the server - meaning there is nothing in common and
+the server should just send all it's objects), then the client will send
+a 'done' command. The 'done' command signals to the server that the client
+is ready to receive it's packfile data.
+
+However, the 256 limit *only* turns on in the canonical client
+implementation if we have received at least one "ACK %s continue"
+during a prior round. This helps to ensure that at least one common
+ancestor is found before we give up entirely.
+
+Once the 'done' line is read from the client, the server will either
+send a final 'ACK obj-id' or it will send a 'NAK'. The server only sends
+ACK after 'done' if there is at least one common base and multi_ack or
+multi_ack_detailed is enabled. The server always sends NAK after 'done'
+if there is no common base found.
+
+Then the server will start sending it's packfile data.
+
+----
+ server-response = *ack_multi ack / nak
+ ack_multi = PKT-LINE("ACK" SP obj-id ack_status LF)
+ ack_status = "continue" / "common" / "ready"
+ ack = PKT-LINE("ACK SP obj-id LF)
+ nak = PKT-LINE("NAK" LF)
+----
+
+A simple clone may look like this (with no 'have' lines):
+
+----
+ C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d\0multi_ack \
+ side-band-64k ofs-delta\n
+ C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n
+ C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n
+ C: 0032want 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n
+ C: 0032want 74730d410fcb6603ace96f1dc55ea6196122532d\n
+ C: 0000
+ C: 0009done\n
+
+ S: 0008NAK\n
+ S: [PACKFILE]
+----
+
+An incremental update (fetch) response might look like this:
+
+----
+ C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d\0multi_ack \
+ side-band-64k ofs-delta\n
+ C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n
+ C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n
+ C: 0000
+ C: 0032have 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n
+ C: [30 more have lines]
+ C: 0032have 74730d410fcb6603ace96f1dc55ea6196122532d\n
+ C: 0000
+
+ S: 003aACK 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01 continue\n
+ S: 003aACK 74730d410fcb6603ace96f1dc55ea6196122532d continue\n
+ S: 0008NAK\n
+
+ C: 0009done\n
+
+ S: 003aACK 74730d410fcb6603ace96f1dc55ea6196122532d\n
+ S: [PACKFILE]
+----
+
+
+Packfile Data
+-------------
+
+Now that the client and server have finished negotiation about what
+the minimal amount of data that needs to be sent to the client is, the server
+will construct and send the required data in packfile format.
+
+See pack-format.txt for what the packfile itself actually looks like.
+
+If 'side-band' or 'side-band-64k' capabilities have been specified by
+the client, the server will send the packfile data multiplexed.
+
+Each packet starting with the packet-line length of the amount of data
+that follows, followed by a single byte specifying the sideband the
+following data is coming in on.
+
+In 'side-band' mode, it will send up to 999 data bytes plus 1 control
+code, for a total of up to 1000 bytes in a pkt-line. In 'side-band-64k'
+mode it will send up to 65519 data bytes plus 1 control code, for a
+total of up to 65520 bytes in a pkt-line.
+
+The sideband byte will be a '1', '2' or a '3'. Sideband '1' will contain
+packfile data, sideband '2' will be used for progress information that the
+client will generally print to stderr and sideband '3' is used for error
+information.
+
+If no 'side-band' capability was specified, the server will stream the
+entire packfile without multiplexing.
+
+
+Pushing Data To a Server
+========================
+
+Pushing data to a server will invoke the 'receive-pack' process on the
+server, which will allow the client to tell it which references it should
+update and then send all the data the server will need for those new
+references to be complete. Once all the data is received and validated,
+the server will then update its references to what the client specified.
+
+Authentication
+--------------
+
+The protocol itself contains no authentication mechanisms. That is to be
+handled by the transport, such as SSH, before the 'receive-pack' process is
+invoked. If 'receive-pack' is configured over the Git transport, those
+repositories will be writable by anyone who can access that port (9418) as
+that transport is unauthenticated.
+
+Reference Discovery
+-------------------
+
+The reference discovery phase is done nearly the same way as it is in the
+fetching protocol. Each reference obj-id and name on the server is sent
+in packet-line format to the client, followed by a flush-pkt. The only
+real difference is that the capability listing is different - the only
+possible values are 'report-status', 'delete-refs' and 'ofs-delta'.
+
+Reference Update Request and Packfile Transfer
+----------------------------------------------
+
+Once the client knows what references the server is at, it can send a
+list of reference update requests. For each reference on the server
+that it wants to update, it sends a line listing the obj-id currently on
+the server, the obj-id the client would like to update it to and the name
+of the reference.
+
+This list is followed by a flush-pkt and then the packfile that should
+contain all the objects that the server will need to complete the new
+references.
+
+----
+ update-request = command-list [pack-file]
+
+ command-list = PKT-LINE(command NUL capability-list LF)
+ *PKT-LINE(command LF)
+ flush-pkt
+
+ command = create / delete / update
+ create = zero-id SP new-id SP name
+ delete = old-id SP zero-id SP name
+ update = old-id SP new-id SP name
+
+ old-id = obj-id
+ new-id = obj-id
+
+ pack-file = "PACK" 28*(OCTET)
+----
+
+If the receiving end does not support delete-refs, the sending end MUST
+NOT ask for delete command.
+
+The pack-file MUST NOT be sent if the only command used is 'delete'.
+
+A pack-file MUST be sent if either create or update command is used,
+even if the server already has all the necessary objects. In this
+case the client MUST send an empty pack-file. The only time this
+is likely to happen is if the client is creating
+a new branch or a tag that points to an existing obj-id.
+
+The server will receive the packfile, unpack it, then validate each
+reference that is being updated that it hasn't changed while the request
+was being processed (the obj-id is still the same as the old-id), and
+it will run any update hooks to make sure that the update is acceptable.
+If all of that is fine, the server will then update the references.
+
+Report Status
+-------------
+
+After receiving the pack data from the sender, the receiver sends a
+report if 'report-status' capability is in effect.
+It is a short listing of what happened in that update. It will first
+list the status of the packfile unpacking as either 'unpack ok' or
+'unpack [error]'. Then it will list the status for each of the references
+that it tried to update. Each line is either 'ok [refname]' if the
+update was successful, or 'ng [refname] [error]' if the update was not.
+
+----
+ report-status = unpack-status
+ 1*(command-status)
+ flush-pkt
+
+ unpack-status = PKT-LINE("unpack" SP unpack-result LF)
+ unpack-result = "ok" / error-msg
+
+ command-status = command-ok / command-fail
+ command-ok = PKT-LINE("ok" SP refname LF)
+ command-fail = PKT-LINE("ng" SP refname SP error-msg LF)
+
+ error-msg = 1*(OCTECT) ; where not "ok"
+----
+
+Updates can be unsuccessful for a number of reasons. The reference can have
+changed since the reference discovery phase was originally sent, meaning
+someone pushed in the meantime. The reference being pushed could be a
+non-fast-forward reference and the update hooks or configuration could be
+set to not allow that, etc. Also, some references can be updated while others
+can be rejected.
+
+An example client/server communication might look like this:
+
+----
+ S: 007c74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/local\0report-status delete-refs ofs-delta\n
+ S: 003e7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug\n
+ S: 003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/master\n
+ S: 003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/team\n
+ S: 0000
+
+ C: 003e7d1665144a3a975c05f1f43902ddaf084e784dbe 74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/debug\n
+ C: 003e74730d410fcb6603ace96f1dc55ea6196122532d 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a refs/heads/master\n
+ C: 0000
+ C: [PACKDATA]
+
+ S: 000aunpack ok\n
+ S: 0014ok refs/heads/debug\n
+ S: 0026ng refs/heads/master non-fast-forward\n
+----
diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
new file mode 100644
index 0000000000..1892d3eeac
--- /dev/null
+++ b/Documentation/technical/protocol-capabilities.txt
@@ -0,0 +1,187 @@
+Git Protocol Capabilities
+=========================
+
+Servers SHOULD support all capabilities defined in this document.
+
+On the very first line of the initial server response of either
+receive-pack and upload-pack the first reference is followed by
+a NUL byte and then a list of space delimited server capabilities.
+These allow the server to declare what it can and cannot support
+to the client.
+
+Client will then send a space separated list of capabilities it wants
+to be in effect. The client MUST NOT ask for capabilities the server
+did not say it supports.
+
+Server MUST diagnose and abort if capabilities it does not understand
+was sent. Server MUST NOT ignore capabilities that client requested
+and server advertised. As a consequence of these rules, server MUST
+NOT advertise capabilities it does not understand.
+
+The 'report-status' and 'delete-refs' capabilities are sent and
+recognized by the receive-pack (push to server) process.
+
+The 'ofs-delta' capability is sent and recognized by both upload-pack
+and receive-pack protocols.
+
+All other capabilities are only recognized by the upload-pack (fetch
+from server) process.
+
+multi_ack
+---------
+
+The 'multi_ack' capability allows the server to return "ACK obj-id
+continue" as soon as it finds a commit that it can use as a common
+base, between the client's wants and the client's have set.
+
+By sending this early, the server can potentially head off the client
+from walking any further down that particular branch of the client's
+repository history. The client may still need to walk down other
+branches, sending have lines for those, until the server has a
+complete cut across the DAG, or the client has said "done".
+
+Without multi_ack, a client sends have lines in --date-order until
+the server has found a common base. That means the client will send
+have lines that are already known by the server to be common, because
+they overlap in time with another branch that the server hasn't found
+a common base on yet.
+
+For example suppose the client has commits in caps that the server
+doesn't and the server has commits in lower case that the client
+doesn't, as in the following diagram:
+
+ +---- u ---------------------- x
+ / +----- y
+ / /
+ a -- b -- c -- d -- E -- F
+ \
+ +--- Q -- R -- S
+
+If the client wants x,y and starts out by saying have F,S, the server
+doesn't know what F,S is. Eventually the client says "have d" and
+the server sends "ACK d continue" to let the client know to stop
+walking down that line (so don't send c-b-a), but its not done yet,
+it needs a base for x. The client keeps going with S-R-Q, until a
+gets reached, at which point the server has a clear base and it all
+ends.
+
+Without multi_ack the client would have sent that c-b-a chain anyway,
+interleaved with S-R-Q.
+
+thin-pack
+---------
+
+This capability means that the server can send a 'thin' pack, a pack
+which does not contain base objects; if those base objects are available
+on client side. Client requests 'thin-pack' capability when it
+understands how to "thicken" it by adding required delta bases making
+it self-contained.
+
+Client MUST NOT request 'thin-pack' capability if it cannot turn a thin
+pack into a self-contained pack.
+
+
+side-band, side-band-64k
+------------------------
+
+This capability means that server can send, and client understand multiplexed
+progress reports and error info interleaved with the packfile itself.
+
+These two options are mutually exclusive. A modern client always
+favors 'side-band-64k'.
+
+Either mode indicates that the packfile data will be streamed broken
+up into packets of up to either 1000 bytes in the case of 'side_band',
+or 65520 bytes in the case of 'side_band_64k'. Each packet is made up
+of a leading 4-byte pkt-line length of how much data is in the packet,
+followed by a 1-byte stream code, followed by the actual data.
+
+The stream code can be one of:
+
+ 1 - pack data
+ 2 - progress messages
+ 3 - fatal error message just before stream aborts
+
+The "side-band-64k" capability came about as a way for newer clients
+that can handle much larger packets to request packets that are
+actually crammed nearly full, while maintaining backward compatibility
+for the older clients.
+
+Further, with side-band and its up to 1000-byte messages, it's actually
+999 bytes of payload and 1 byte for the stream code. With side-band-64k,
+same deal, you have up to 65519 bytes of data and 1 byte for the stream
+code.
+
+The client MUST send only maximum of one of "side-band" and "side-
+band-64k". Server MUST diagnose it as an error if client requests
+both.
+
+ofs-delta
+---------
+
+Server can send, and client understand PACKv2 with delta refering to
+its base by position in pack rather than by an obj-id. That is, they can
+send/read OBJ_OFS_DELTA (aka type 6) in a packfile.
+
+shallow
+-------
+
+This capability adds "deepen", "shallow" and "unshallow" commands to
+the fetch-pack/upload-pack protocol so clients can request shallow
+clones.
+
+no-progress
+-----------
+
+The client was started with "git clone -q" or something, and doesn't
+want that side band 2. Basically the client just says "I do not
+wish to receive stream 2 on sideband, so do not send it to me, and if
+you did, I will drop it on the floor anyway". However, the sideband
+channel 3 is still used for error responses.
+
+include-tag
+-----------
+
+The 'include-tag' capability is about sending annotated tags if we are
+sending objects they point to. If we pack an object to the client, and
+a tag object points exactly at that object, we pack the tag object too.
+In general this allows a client to get all new annotated tags when it
+fetches a branch, in a single network connection.
+
+Clients MAY always send include-tag, hardcoding it into a request when
+the server advertises this capability. The decision for a client to
+request include-tag only has to do with the client's desires for tag
+data, whether or not a server had advertised objects in the
+refs/tags/* namespace.
+
+Servers MUST pack the tags if their referrant is packed and the client
+has requested include-tags.
+
+Clients MUST be prepared for the case where a server has ignored
+include-tag and has not actually sent tags in the pack. In such
+cases the client SHOULD issue a subsequent fetch to acquire the tags
+that include-tag would have otherwise given the client.
+
+The server SHOULD send include-tag, if it supports it, regardless
+of whether or not there are tags available.
+
+report-status
+-------------
+
+The upload-pack process can receive a 'report-status' capability,
+which tells it that the client wants a report of what happened after
+a packfile upload and reference update. If the pushing client requests
+this capability, after unpacking and updating references the server
+will respond with whether the packfile unpacked successfully and if
+each reference was updated successfully. If any of those were not
+successful, it will send back an error message. See pack-protocol.txt
+for example messages.
+
+delete-refs
+-----------
+
+If the server sends back the 'delete-refs' capability, it means that
+it is capable of accepting an zero-id value as the target
+value of a reference update. It is not sent back by the client, it
+simply informs the client that it can be sent zero-id values
+to delete references.
diff --git a/Documentation/technical/protocol-common.txt b/Documentation/technical/protocol-common.txt
new file mode 100644
index 0000000000..d30a1b9510
--- /dev/null
+++ b/Documentation/technical/protocol-common.txt
@@ -0,0 +1,96 @@
+Documentation Common to Pack and Http Protocols
+===============================================
+
+ABNF Notation
+-------------
+
+ABNF notation as described by RFC 5234 is used within the protocol documents,
+except the following replacement core rules are used:
+----
+ HEXDIG = DIGIT / "a" / "b" / "c" / "d" / "e" / "f"
+----
+
+We also define the following common rules:
+----
+ NUL = %x00
+ zero-id = 40*"0"
+ obj-id = 40*(HEXDIGIT)
+
+ refname = "HEAD"
+ refname /= "refs/" <see discussion below>
+----
+
+A refname is a hierarchical octet string beginning with "refs/" and
+not violating the 'git-check-ref-format' command's validation rules.
+More specifically, they:
+
+. They can include slash `/` for hierarchical (directory)
+ grouping, but no slash-separated component can begin with a
+ dot `.`.
+
+. They must contain at least one `/`. This enforces the presence of a
+ category like `heads/`, `tags/` etc. but the actual names are not
+ restricted.
+
+. They cannot have two consecutive dots `..` anywhere.
+
+. They cannot have ASCII control characters (i.e. bytes whose
+ values are lower than \040, or \177 `DEL`), space, tilde `~`,
+ caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`,
+ or open bracket `[` anywhere.
+
+. They cannot end with a slash `/` nor a dot `.`.
+
+. They cannot end with the sequence `.lock`.
+
+. They cannot contain a sequence `@{`.
+
+. They cannot contain a `\\`.
+
+
+pkt-line Format
+---------------
+
+Much (but not all) of the payload is described around pkt-lines.
+
+A pkt-line is a variable length binary string. The first four bytes
+of the line, the pkt-len, indicates the total length of the line,
+in hexadecimal. The pkt-len includes the 4 bytes used to contain
+the length's hexadecimal representation.
+
+A pkt-line MAY contain binary data, so implementors MUST ensure
+pkt-line parsing/formatting routines are 8-bit clean.
+
+A non-binary line SHOULD BE terminated by an LF, which if present
+MUST be included in the total length.
+
+The maximum length of a pkt-line's data component is 65520 bytes.
+Implementations MUST NOT send pkt-line whose length exceeds 65524
+(65520 bytes of payload + 4 bytes of length data).
+
+Implementations SHOULD NOT send an empty pkt-line ("0004").
+
+A pkt-line with a length field of 0 ("0000"), called a flush-pkt,
+is a special case and MUST be handled differently than an empty
+pkt-line ("0004").
+
+----
+ pkt-line = data-pkt / flush-pkt
+
+ data-pkt = pkt-len pkt-payload
+ pkt-len = 4*(HEXDIG)
+ pkt-payload = (pkt-len - 4)*(OCTET)
+
+ flush-pkt = "0000"
+----
+
+Examples (as C-style strings):
+
+----
+ pkt-line actual value
+ ---------------------------------
+ "0006a\n" "a\n"
+ "0005a" "a"
+ "000bfoobar\n" "foobar\n"
+ "0004" ""
+----
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index 67ebffa568..27d97b6d1e 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -1384,7 +1384,7 @@ were merged.
However, if the current branch is a descendant of the other--so every
commit present in the one is already contained in the other--then git
-just performs a "fast forward"; the head of the current branch is moved
+just performs a "fast-forward"; the head of the current branch is moved
forward to point at the head of the merged-in branch, without any new
commits being created.
@@ -1719,7 +1719,7 @@ producing a default commit message documenting the branch and
repository that you pulled from.
(But note that no such commit will be created in the case of a
-<<fast-forwards,fast forward>>; instead, your branch will just be
+<<fast-forwards,fast-forward>>; instead, your branch will just be
updated to point to the latest commit from the upstream branch.)
The `git pull` command can also be given "." as the "remote" repository,
@@ -1943,7 +1943,7 @@ $ git push ssh://yourserver.com/~you/proj.git master
-------------------------------------------------
As with `git fetch`, `git push` will complain if this does not result in a
-<<fast-forwards,fast forward>>; see the following section for details on
+<<fast-forwards,fast-forward>>; see the following section for details on
handling this case.
Note that the target of a "push" is normally a
@@ -1976,7 +1976,7 @@ details.
What to do when a push fails
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If a push would not result in a <<fast-forwards,fast forward>> of the
+If a push would not result in a <<fast-forwards,fast-forward>> of the
remote branch, then it will fail with an error like:
-------------------------------------------------
@@ -2115,7 +2115,7 @@ $ git checkout release && git pull
Important note! If you have any local changes in these branches, then
this merge will create a commit object in the history (with no local
-changes git will simply do a "Fast forward" merge). Many people dislike
+changes git will simply do a "fast-forward" merge). Many people dislike
the "noise" that this creates in the Linux history, so you should avoid
doing this capriciously in the "release" branch, as these noisy commits
will become part of the permanent history when you ask Linus to pull
@@ -2569,7 +2569,7 @@ them again with linkgit:git-am[1].
Other tools
-----------
-There are numerous other tools, such as StGIT, which exist for the
+There are numerous other tools, such as StGit, which exist for the
purpose of maintaining a patch series. These are outside of the scope of
this manual.
@@ -2729,9 +2729,9 @@ In the previous example, when updating an existing branch, "git fetch"
checks to make sure that the most recent commit on the remote
branch is a descendant of the most recent commit on your copy of the
branch before updating your copy of the branch to point at the new
-commit. Git calls this process a <<fast-forwards,fast forward>>.
+commit. Git calls this process a <<fast-forwards,fast-forward>>.
-A fast forward looks something like this:
+A fast-forward looks something like this:
................................................
o--o--o--o <-- old head of the branch
@@ -4275,7 +4275,7 @@ You see, Git is actually the best tool to find out about the source of Git
itself!
[[glossary]]
-GIT Glossary
+Git Glossary
============
include::glossary-content.txt[]
diff --git a/Makefile b/Makefile
index 5d5976f94f..25a977153c 100644
--- a/Makefile
+++ b/Makefile
@@ -204,6 +204,18 @@ all::
# memory allocators with the nedmalloc allocator written by Niall Douglas.
#
# Define NO_REGEX if you have no or inferior regex support in your C library.
+#
+# Define DEFAULT_PAGER to a sensible pager command (defaults to "less") if
+# you want to use something different. The value will be interpreted by the
+# shell at runtime when it is used.
+#
+# Define DEFAULT_EDITOR to a sensible editor command (defaults to "vi") if you
+# want to use something different. The value will be interpreted by the shell
+# if necessary when it is used. Examples:
+#
+# DEFAULT_EDITOR='~/bin/vi',
+# DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
+# DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
@$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -216,6 +228,12 @@ uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not')
uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not')
+ifdef MSVC
+ # avoid the MingW and Cygwin configuration sections
+ uname_S := Windows
+ uname_O := Windows
+endif
+
# CFLAGS and LDFLAGS are for the users to override from the command line.
CFLAGS = -g -O2 -Wall
@@ -325,6 +343,7 @@ SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-mergetool--lib.sh
+SCRIPT_SH += git-notes.sh
SCRIPT_SH += git-parse-remote.sh
SCRIPT_SH += git-pull.sh
SCRIPT_SH += git-quiltimport.sh
@@ -358,6 +377,7 @@ EXTRA_PROGRAMS =
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
@@ -369,6 +389,7 @@ 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
# builtin-$C.o but is linked in as part of some other command.
@@ -437,6 +458,7 @@ LIB_H += ll-merge.h
LIB_H += log-tree.h
LIB_H += mailmap.h
LIB_H += merge-recursive.h
+LIB_H += notes.h
LIB_H += object.h
LIB_H += pack.h
LIB_H += pack-refs.h
@@ -522,6 +544,7 @@ LIB_OBJS += match-trees.o
LIB_OBJS += merge-file.o
LIB_OBJS += merge-recursive.o
LIB_OBJS += name-hash.o
+LIB_OBJS += notes.o
LIB_OBJS += object.o
LIB_OBJS += pack-check.o
LIB_OBJS += pack-refs.o
@@ -601,7 +624,6 @@ BUILTIN_OBJS += builtin-diff-index.o
BUILTIN_OBJS += builtin-diff-tree.o
BUILTIN_OBJS += builtin-diff.o
BUILTIN_OBJS += builtin-fast-export.o
-BUILTIN_OBJS += builtin-fetch--tool.o
BUILTIN_OBJS += builtin-fetch-pack.o
BUILTIN_OBJS += builtin-fetch.o
BUILTIN_OBJS += builtin-fmt-merge-msg.o
@@ -789,6 +811,8 @@ ifeq ($(uname_O),Cygwin)
NO_MMAP = YesPlease
NO_IPV6 = YesPlease
X = .exe
+ COMPAT_OBJS += compat/cygwin.o
+ UNRELIABLE_FSTAT = UnfortunatelyYes
endif
ifeq ($(uname_S),FreeBSD)
NEEDS_LIBICONV = YesPlease
@@ -898,15 +922,11 @@ ifeq ($(uname_S),HP-UX)
NO_SYS_SELECT_H = YesPlease
SNPRINTF_RETURNS_BOGUS = YesPlease
endif
-ifneq (,$(findstring CYGWIN,$(uname_S)))
- COMPAT_OBJS += compat/cygwin.o
- UNRELIABLE_FSTAT = UnfortunatelyYes
-endif
-ifdef MSVC
+ifeq ($(uname_S),Windows)
GIT_VERSION := $(GIT_VERSION).MSVC
pathsep = ;
NO_PREAD = YesPlease
- NO_OPENSSL = YesPlease
+ NEEDS_CRYPTO_WITH_SSL = YesPlease
NO_LIBGEN_H = YesPlease
NO_SYMLINK_HEAD = YesPlease
NO_IPV6 = YesPlease
@@ -936,6 +956,7 @@ ifdef MSVC
NO_REGEX = YesPlease
NO_CURL = YesPlease
NO_PTHREADS = YesPlease
+ BLK_SHA1 = YesPlease
CC = compat/vcbuild/scripts/clink.pl
AR = compat/vcbuild/scripts/lib.pl
@@ -954,11 +975,11 @@ else
BASIC_CFLAGS += -Zi -MTd
endif
X = .exe
-else
+endif
ifneq (,$(findstring MINGW,$(uname_S)))
pathsep = ;
NO_PREAD = YesPlease
- NO_OPENSSL = YesPlease
+ NEEDS_CRYPTO_WITH_SSL = YesPlease
NO_LIBGEN_H = YesPlease
NO_SYMLINK_HEAD = YesPlease
NO_IPV6 = YesPlease
@@ -985,6 +1006,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
UNRELIABLE_FSTAT = UnfortunatelyYes
OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
NO_REGEX = YesPlease
+ BLK_SHA1 = 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
@@ -1003,7 +1025,6 @@ else
NO_PTHREADS = YesPlease
endif
endif
-endif
-include config.mak.autogen
-include config.mak
@@ -1082,7 +1103,6 @@ EXTLIBS += -lz
ifndef NO_POSIX_ONLY_PROGRAMS
PROGRAMS += git-daemon$X
- PROGRAMS += git-imap-send$X
endif
ifndef NO_OPENSSL
OPENSSL_LIBSSL = -lssl
@@ -1370,6 +1390,22 @@ BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \
$(COMPAT_CFLAGS)
LIB_OBJS += $(COMPAT_OBJS)
+# Quote for C
+
+ifdef DEFAULT_EDITOR
+DEFAULT_EDITOR_CQ = "$(subst ",\",$(subst \,\\,$(DEFAULT_EDITOR)))"
+DEFAULT_EDITOR_CQ_SQ = $(subst ','\'',$(DEFAULT_EDITOR_CQ))
+
+BASIC_CFLAGS += -DDEFAULT_EDITOR='$(DEFAULT_EDITOR_CQ_SQ)'
+endif
+
+ifdef DEFAULT_PAGER
+DEFAULT_PAGER_CQ = "$(subst ",\",$(subst \,\\,$(DEFAULT_PAGER)))"
+DEFAULT_PAGER_CQ_SQ = $(subst ','\'',$(DEFAULT_PAGER_CQ))
+
+BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)'
+endif
+
ALL_CFLAGS += $(BASIC_CFLAGS)
ALL_LDFLAGS += $(BASIC_LDFLAGS)
@@ -1633,6 +1669,7 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
# and the first level quoting from the shell that runs "echo".
GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
@echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
+ @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
@echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
@echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
@@ -1806,7 +1843,10 @@ dist: git.spec git-archive$(X) configure
gzip -f -9 $(GIT_TARNAME).tar
rpm: dist
- $(RPMBUILD) -ta $(GIT_TARNAME).tar.gz
+ $(RPMBUILD) \
+ --define "_source_filedigest_algorithm md5" \
+ --define "_binary_filedigest_algorithm md5" \
+ -ta $(GIT_TARNAME).tar.gz
htmldocs = git-htmldocs-$(GIT_VERSION)
manpages = git-manpages-$(GIT_VERSION)
diff --git a/builtin-blame.c b/builtin-blame.c
index b0aa530507..d4e25a595d 100644
--- a/builtin-blame.c
+++ b/builtin-blame.c
@@ -1604,6 +1604,9 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
} while (ch != '\n' &&
cp < sb->final_buf + sb->final_buf_size);
}
+
+ if (sb->final_buf_size && cp[-1] != '\n')
+ putchar('\n');
}
static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
@@ -1667,6 +1670,9 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
} while (ch != '\n' &&
cp < sb->final_buf + sb->final_buf_size);
}
+
+ if (sb->final_buf_size && cp[-1] != '\n')
+ putchar('\n');
}
static void output(struct scoreboard *sb, int option)
diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c
index e3e7bdf52f..b106c65d80 100644
--- a/builtin-check-ref-format.c
+++ b/builtin-check-ref-format.c
@@ -7,6 +7,10 @@
#include "builtin.h"
#include "strbuf.h"
+static const char builtin_check_ref_format_usage[] =
+"git check-ref-format [--print] <refname>\n"
+" or: git check-ref-format --branch <branchname-shorthand>";
+
/*
* Replace each run of adjacent slashes in src with a single slash,
* and write the result to dst.
@@ -31,6 +35,9 @@ static void collapse_slashes(char *dst, const char *src)
int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
{
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(builtin_check_ref_format_usage);
+
if (argc == 3 && !strcmp(argv[1], "--branch")) {
struct strbuf sb = STRBUF_INIT;
@@ -49,6 +56,6 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
exit(0);
}
if (argc != 2)
- usage("git check-ref-format refname");
+ usage(builtin_check_ref_format_usage);
return !!check_ref_format(argv[1]);
}
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
index 6467077731..ddcb7a4bbb 100644
--- a/builtin-commit-tree.c
+++ b/builtin-commit-tree.c
@@ -105,7 +105,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
- if (argc < 2)
+ if (argc < 2 || !strcmp(argv[1], "-h"))
usage(commit_tree_usage);
if (get_sha1(argv[1], tree_sha1))
die("Not a valid object name %s", argv[1]);
diff --git a/builtin-commit.c b/builtin-commit.c
index beddf01dd3..09d28405ec 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -530,7 +530,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
; /* do nothing */
if (prefixcmp(sb.buf + i, sob.buf)) {
- if (!ends_rfc2822_footer(&sb))
+ if (!i || !ends_rfc2822_footer(&sb))
strbuf_addch(&sb, '\n');
strbuf_addbuf(&sb, &sob);
}
@@ -999,7 +999,7 @@ static int git_commit_config(const char *k, const char *v, void *cb)
struct wt_status *s = cb;
if (!strcmp(k, "commit.template"))
- return git_config_string(&template_file, k, v);
+ return git_config_pathname(&template_file, k, v);
return git_status_config(k, v, s);
}
diff --git a/builtin-describe.c b/builtin-describe.c
index eaa8a9d229..71be2a9364 100644
--- a/builtin-describe.c
+++ b/builtin-describe.c
@@ -5,12 +5,14 @@
#include "builtin.h"
#include "exec_cmd.h"
#include "parse-options.h"
+#include "diff.h"
#define SEEN (1u<<0)
#define MAX_TAGS (FLAG_BITS - 1)
static const char * const describe_usage[] = {
"git describe [options] <committish>*",
+ "git describe [options] --dirty",
NULL
};
@@ -23,6 +25,13 @@ static int max_candidates = 10;
static int found_names;
static const char *pattern;
static int always;
+static const char *dirty;
+
+/* diff-index command arguments to check if working tree is dirty. */
+static const char *diff_index_args[] = {
+ "diff-index", "--quiet", "HEAD", "--", NULL
+};
+
struct commit_name {
struct tag *tag;
@@ -96,8 +105,6 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void
if (!all) {
if (!prio)
return 0;
- if (!tags && prio < 2)
- return 0;
}
add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1);
return 0;
@@ -184,6 +191,7 @@ static void describe(const char *arg, int last_one)
struct possible_tag all_matches[MAX_TAGS];
unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
unsigned long seen_commits = 0;
+ unsigned int unannotated_cnt = 0;
if (get_sha1(arg, sha1))
die("Not a valid object name %s", arg);
@@ -192,13 +200,15 @@ static void describe(const char *arg, int last_one)
die("%s is not a valid '%s' object", arg, commit_type);
n = cmit->util;
- if (n) {
+ if (n && (tags || all || n->prio == 2)) {
/*
* Exact match to an existing ref.
*/
display_name(n);
if (longformat)
show_suffix(0, n->tag ? n->tag->tagged->sha1 : sha1);
+ if (dirty)
+ printf("%s", dirty);
printf("\n");
return;
}
@@ -217,7 +227,9 @@ static void describe(const char *arg, int last_one)
seen_commits++;
n = c->util;
if (n) {
- if (match_cnt < max_candidates) {
+ if (!tags && !all && n->prio < 2) {
+ unannotated_cnt++;
+ } else if (match_cnt < max_candidates) {
struct possible_tag *t = &all_matches[match_cnt++];
t->name = n;
t->depth = seen_commits - 1;
@@ -256,10 +268,20 @@ static void describe(const char *arg, int last_one)
if (!match_cnt) {
const unsigned char *sha1 = cmit->object.sha1;
if (always) {
- printf("%s\n", find_unique_abbrev(sha1, abbrev));
+ printf("%s", find_unique_abbrev(sha1, abbrev));
+ if (dirty)
+ printf("%s", dirty);
+ printf("\n");
return;
}
- die("cannot describe '%s'", sha1_to_hex(sha1));
+ if (unannotated_cnt)
+ die("No annotated tags can describe '%s'.\n"
+ "However, there were unannotated tags: try --tags.",
+ sha1_to_hex(sha1));
+ else
+ die("No tags can describe '%s'.\n"
+ "Try --always, or create some tags.",
+ sha1_to_hex(sha1));
}
qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);
@@ -291,6 +313,8 @@ static void describe(const char *arg, int last_one)
display_name(all_matches[0].name);
if (abbrev)
show_suffix(all_matches[0].depth, cmit->object.sha1);
+ if (dirty)
+ printf("%s", dirty);
printf("\n");
if (!last_one)
@@ -315,6 +339,9 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
"only consider tags matching <pattern>"),
OPT_BOOLEAN(0, "always", &always,
"show abbreviated commit object as fallback"),
+ {OPTION_STRING, 0, "dirty", &dirty, "mark",
+ "append <mark> on dirty working tree (default: \"-dirty\")",
+ PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"},
OPT_END(),
};
@@ -355,7 +382,11 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
die("No names found, cannot describe anything.");
if (argc == 0) {
+ if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix))
+ dirty = NULL;
describe("HEAD", 1);
+ } else if (dirty) {
+ die("--dirty is incompatible with committishes");
} else {
while (argc-- > 0) {
describe(*argv++, argc == 0);
diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c
index 629735f547..8ed4a6feaa 100644
--- a/builtin-fetch-pack.c
+++ b/builtin-fetch-pack.c
@@ -157,6 +157,66 @@ static const unsigned char *get_rev(void)
return commit->object.sha1;
}
+enum ack_type {
+ NAK = 0,
+ ACK,
+ ACK_continue,
+ ACK_common,
+ ACK_ready
+};
+
+static void consume_shallow_list(int fd)
+{
+ if (args.stateless_rpc && args.depth > 0) {
+ /* If we sent a depth we will get back "duplicate"
+ * shallow and unshallow commands every time there
+ * is a block of have lines exchanged.
+ */
+ char line[1000];
+ while (packet_read_line(fd, line, sizeof(line))) {
+ if (!prefixcmp(line, "shallow "))
+ continue;
+ if (!prefixcmp(line, "unshallow "))
+ continue;
+ die("git fetch-pack: expected shallow list");
+ }
+ }
+}
+
+static enum ack_type get_ack(int fd, unsigned char *result_sha1)
+{
+ static char line[1000];
+ int len = packet_read_line(fd, line, sizeof(line));
+
+ if (!len)
+ die("git fetch-pack: expected ACK/NAK, got EOF");
+ if (line[len-1] == '\n')
+ line[--len] = 0;
+ if (!strcmp(line, "NAK"))
+ return NAK;
+ if (!prefixcmp(line, "ACK ")) {
+ if (!get_sha1_hex(line+4, result_sha1)) {
+ if (strstr(line+45, "continue"))
+ return ACK_continue;
+ if (strstr(line+45, "common"))
+ return ACK_common;
+ if (strstr(line+45, "ready"))
+ return ACK_ready;
+ return ACK;
+ }
+ }
+ die("git fetch_pack: expected ACK/NAK, got '%s'", line);
+}
+
+static void send_request(int fd, struct strbuf *buf)
+{
+ if (args.stateless_rpc) {
+ send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX);
+ packet_flush(fd);
+ } else
+ safe_write(fd, buf->buf, buf->len);
+}
+
static int find_common(int fd[2], unsigned char *result_sha1,
struct ref *refs)
{
@@ -165,7 +225,11 @@ static int find_common(int fd[2], unsigned char *result_sha1,
const unsigned char *sha1;
unsigned in_vain = 0;
int got_continue = 0;
+ struct strbuf req_buf = STRBUF_INIT;
+ size_t state_len = 0;
+ if (args.stateless_rpc && multi_ack == 1)
+ die("--stateless-rpc requires multi_ack_detailed");
if (marked)
for_each_ref(clear_marks, NULL);
marked = 1;
@@ -175,6 +239,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
fetching = 0;
for ( ; refs ; refs = refs->next) {
unsigned char *remote = refs->old_sha1;
+ const char *remote_hex;
struct object *o;
/*
@@ -192,32 +257,42 @@ static int find_common(int fd[2], unsigned char *result_sha1,
continue;
}
- if (!fetching)
- packet_write(fd[1], "want %s%s%s%s%s%s%s%s\n",
- sha1_to_hex(remote),
- (multi_ack ? " multi_ack" : ""),
- (use_sideband == 2 ? " side-band-64k" : ""),
- (use_sideband == 1 ? " side-band" : ""),
- (args.use_thin_pack ? " thin-pack" : ""),
- (args.no_progress ? " no-progress" : ""),
- (args.include_tag ? " include-tag" : ""),
- (prefer_ofs_delta ? " ofs-delta" : ""));
- else
- packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
+ remote_hex = sha1_to_hex(remote);
+ if (!fetching) {
+ struct strbuf c = STRBUF_INIT;
+ if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed");
+ if (multi_ack == 1) strbuf_addstr(&c, " multi_ack");
+ if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k");
+ if (use_sideband == 1) strbuf_addstr(&c, " side-band");
+ if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack");
+ if (args.no_progress) strbuf_addstr(&c, " no-progress");
+ if (args.include_tag) strbuf_addstr(&c, " include-tag");
+ if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta");
+ packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf);
+ strbuf_release(&c);
+ } else
+ packet_buf_write(&req_buf, "want %s\n", remote_hex);
fetching++;
}
+
+ if (!fetching) {
+ strbuf_release(&req_buf);
+ packet_flush(fd[1]);
+ return 1;
+ }
+
if (is_repository_shallow())
- write_shallow_commits(fd[1], 1);
+ write_shallow_commits(&req_buf, 1);
if (args.depth > 0)
- packet_write(fd[1], "deepen %d", args.depth);
- packet_flush(fd[1]);
- if (!fetching)
- return 1;
+ packet_buf_write(&req_buf, "deepen %d", args.depth);
+ packet_buf_flush(&req_buf);
+ state_len = req_buf.len;
if (args.depth > 0) {
char line[1024];
unsigned char sha1[20];
+ send_request(fd[1], &req_buf);
while (packet_read_line(fd[0], line, sizeof(line))) {
if (!prefixcmp(line, "shallow ")) {
if (get_sha1_hex(line + 8, sha1))
@@ -239,45 +314,73 @@ static int find_common(int fd[2], unsigned char *result_sha1,
}
die("expected shallow/unshallow, got %s", line);
}
+ } else if (!args.stateless_rpc)
+ send_request(fd[1], &req_buf);
+
+ if (!args.stateless_rpc) {
+ /* If we aren't using the stateless-rpc interface
+ * we don't need to retain the headers.
+ */
+ strbuf_setlen(&req_buf, 0);
+ state_len = 0;
}
flushes = 0;
retval = -1;
while ((sha1 = get_rev())) {
- packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
+ packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1));
if (args.verbose)
fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
in_vain++;
if (!(31 & ++count)) {
int ack;
- packet_flush(fd[1]);
+ packet_buf_flush(&req_buf);
+ send_request(fd[1], &req_buf);
+ strbuf_setlen(&req_buf, state_len);
flushes++;
/*
* We keep one window "ahead" of the other side, and
* will wait for an ACK only on the next one
*/
- if (count == 32)
+ if (!args.stateless_rpc && count == 32)
continue;
+ consume_shallow_list(fd[0]);
do {
ack = get_ack(fd[0], result_sha1);
if (args.verbose && ack)
fprintf(stderr, "got ack %d %s\n", ack,
sha1_to_hex(result_sha1));
- if (ack == 1) {
+ switch (ack) {
+ case ACK:
flushes = 0;
multi_ack = 0;
retval = 0;
goto done;
- } else if (ack == 2) {
+ case ACK_common:
+ case ACK_ready:
+ case ACK_continue: {
struct commit *commit =
lookup_commit(result_sha1);
+ if (args.stateless_rpc
+ && ack == ACK_common
+ && !(commit->object.flags & COMMON)) {
+ /* We need to replay the have for this object
+ * on the next RPC request so the peer knows
+ * it is in common with us.
+ */
+ const char *hex = sha1_to_hex(result_sha1);
+ packet_buf_write(&req_buf, "have %s\n", hex);
+ state_len = req_buf.len;
+ }
mark_common(commit, 0, 1);
retval = 0;
in_vain = 0;
got_continue = 1;
+ break;
+ }
}
} while (ack);
flushes--;
@@ -289,20 +392,24 @@ static int find_common(int fd[2], unsigned char *result_sha1,
}
}
done:
- packet_write(fd[1], "done\n");
+ packet_buf_write(&req_buf, "done\n");
+ send_request(fd[1], &req_buf);
if (args.verbose)
fprintf(stderr, "done\n");
if (retval != 0) {
multi_ack = 0;
flushes++;
}
+ strbuf_release(&req_buf);
+
+ consume_shallow_list(fd[0]);
while (flushes || multi_ack) {
int ack = get_ack(fd[0], result_sha1);
if (ack) {
if (args.verbose)
fprintf(stderr, "got ack (%d) %s\n", ack,
sha1_to_hex(result_sha1));
- if (ack == 1)
+ if (ack == ACK)
return 0;
multi_ack = 1;
continue;
@@ -584,7 +691,12 @@ static struct ref *do_fetch_pack(int fd[2],
if (is_repository_shallow() && !server_supports("shallow"))
die("Server does not support shallow clients");
- if (server_supports("multi_ack")) {
+ if (server_supports("multi_ack_detailed")) {
+ if (args.verbose)
+ fprintf(stderr, "Server supports multi_ack_detailed\n");
+ multi_ack = 2;
+ }
+ else if (server_supports("multi_ack")) {
if (args.verbose)
fprintf(stderr, "Server supports multi_ack\n");
multi_ack = 1;
@@ -615,6 +727,8 @@ static struct ref *do_fetch_pack(int fd[2],
*/
warning("no common commits");
+ if (args.stateless_rpc)
+ packet_flush(fd[1]);
if (get_pack(fd, pack_lockfile))
die("git fetch-pack: fetch failed.");
@@ -685,6 +799,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
struct ref *ref = NULL;
char *dest = NULL, **heads;
int fd[2];
+ char *pack_lockfile = NULL;
+ char **pack_lockfile_ptr = NULL;
struct child_process *conn;
nr_heads = 0;
@@ -734,6 +850,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
args.no_progress = 1;
continue;
}
+ if (!strcmp("--stateless-rpc", arg)) {
+ args.stateless_rpc = 1;
+ continue;
+ }
+ if (!strcmp("--lock-pack", arg)) {
+ args.lock_pack = 1;
+ pack_lockfile_ptr = &pack_lockfile;
+ continue;
+ }
usage(fetch_pack_usage);
}
dest = (char *)arg;
@@ -744,19 +869,27 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
if (!dest)
usage(fetch_pack_usage);
- conn = git_connect(fd, (char *)dest, args.uploadpack,
- args.verbose ? CONNECT_VERBOSE : 0);
- if (conn) {
- get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
-
- ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, NULL);
- close(fd[0]);
- close(fd[1]);
- if (finish_connect(conn))
- ref = NULL;
+ if (args.stateless_rpc) {
+ conn = NULL;
+ fd[0] = 0;
+ fd[1] = 1;
} else {
- ref = NULL;
+ conn = git_connect(fd, (char *)dest, args.uploadpack,
+ args.verbose ? CONNECT_VERBOSE : 0);
}
+
+ get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
+
+ ref = fetch_pack(&args, fd, conn, ref, dest,
+ nr_heads, heads, pack_lockfile_ptr);
+ if (pack_lockfile) {
+ printf("lock %s\n", pack_lockfile);
+ fflush(stdout);
+ }
+ close(fd[0]);
+ close(fd[1]);
+ if (finish_connect(conn))
+ ref = NULL;
ret = !ref;
if (!ret && nr_heads) {
@@ -809,6 +942,7 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
if (args.depth > 0) {
struct cache_time mtime;
+ struct strbuf sb = STRBUF_INIT;
char *shallow = git_path("shallow");
int fd;
@@ -826,12 +960,14 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
fd = hold_lock_file_for_update(&lock, shallow,
LOCK_DIE_ON_ERROR);
- if (!write_shallow_commits(fd, 0)) {
+ if (!write_shallow_commits(&sb, 0)
+ || write_in_full(fd, sb.buf, sb.len) != sb.len) {
unlink_or_warn(shallow);
rollback_lock_file(&lock);
} else {
commit_lock_file(&lock);
}
+ strbuf_release(&sb);
}
reprepare_packed_git();
diff --git a/builtin-fetch.c b/builtin-fetch.c
index a35a6f8cb8..5b7db616dc 100644
--- a/builtin-fetch.c
+++ b/builtin-fetch.c
@@ -14,6 +14,9 @@
static const char * const builtin_fetch_usage[] = {
"git fetch [options] [<repository> <refspec>...]",
+ "git fetch [options] <group>",
+ "git fetch --multiple [options] [<repository> | <group>]...",
+ "git fetch --all [options]",
NULL
};
@@ -23,7 +26,7 @@ enum {
TAGS_SET = 2
};
-static int append, force, keep, update_head_ok, verbosity;
+static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
static int tags = TAGS_DEFAULT;
static const char *depth;
static const char *upload_pack;
@@ -32,16 +35,24 @@ static struct transport *transport;
static struct option builtin_fetch_options[] = {
OPT__VERBOSITY(&verbosity),
+ OPT_BOOLEAN(0, "all", &all,
+ "fetch from all remotes"),
OPT_BOOLEAN('a', "append", &append,
"append to .git/FETCH_HEAD instead of overwriting"),
OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
"path to upload pack on remote end"),
OPT_BOOLEAN('f', "force", &force,
"force overwrite of local branch"),
+ OPT_BOOLEAN('m', "multiple", &multiple,
+ "fetch from multiple remotes"),
OPT_SET_INT('t', "tags", &tags,
"fetch all tags and associated objects", TAGS_SET),
OPT_SET_INT('n', NULL, &tags,
"do not fetch all tags (--no-tags)", TAGS_UNSET),
+ OPT_BOOLEAN('p', "prune", &prune,
+ "prune tracking branches no longer on remote"),
+ OPT_BOOLEAN(0, "dry-run", &dry_run,
+ "dry run"),
OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
"allow updating of HEAD ref"),
@@ -178,6 +189,8 @@ static int s_update_ref(const char *action,
char *rla = getenv("GIT_REFLOG_ACTION");
static struct ref_lock *lock;
+ if (dry_run)
+ return 0;
if (!rla)
rla = default_rla.buf;
snprintf(msg, sizeof(msg), "%s: %s", rla, action);
@@ -269,7 +282,7 @@ static int update_local_ref(struct ref *ref,
strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
strcat(quickref, "..");
strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
- r = s_update_ref("fast forward", ref, 1);
+ r = s_update_ref("fast-forward", ref, 1);
sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
pretty_ref, r ? " (unable to update local ref)" : "");
@@ -287,7 +300,7 @@ static int update_local_ref(struct ref *ref,
r ? "unable to update local ref" : "forced update");
return r;
} else {
- sprintf(display, "! %-*s %-*s -> %s (non fast forward)",
+ sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)",
SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
pretty_ref);
return 1;
@@ -303,7 +316,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
char note[1024];
const char *what, *kind;
struct ref *rm;
- char *url, *filename = git_path("FETCH_HEAD");
+ char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
fp = fopen(filename, "a");
if (!fp)
@@ -485,11 +498,34 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map)
return ret;
}
+static int prune_refs(struct transport *transport, struct ref *ref_map)
+{
+ int result = 0;
+ struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
+ const char *dangling_msg = dry_run
+ ? " (%s will become dangling)\n"
+ : " (%s has become dangling)\n";
+
+ for (ref = stale_refs; ref; ref = ref->next) {
+ if (!dry_run)
+ result |= delete_ref(ref->name, NULL, 0);
+ if (verbosity >= 0) {
+ fprintf(stderr, " x %-*s %-*s -> %s\n",
+ SUMMARY_WIDTH, "[deleted]",
+ REFCOL_WIDTH, "(none)", prettify_refname(ref->name));
+ warn_dangling_symref(stderr, dangling_msg, ref->name);
+ }
+ }
+ free_refs(stale_refs);
+ return result;
+}
+
static int add_existing(const char *refname, const unsigned char *sha1,
int flag, void *cbdata)
{
struct string_list *list = (struct string_list *)cbdata;
- string_list_insert(refname, list);
+ struct string_list_item *item = string_list_insert(refname, list);
+ item->util = (void *)sha1;
return 0;
}
@@ -615,9 +651,14 @@ static void check_not_current_branch(struct ref *ref_map)
static int do_fetch(struct transport *transport,
struct refspec *refs, int ref_count)
{
+ struct string_list existing_refs = { NULL, 0, 0, 0 };
+ struct string_list_item *peer_item = NULL;
struct ref *ref_map;
struct ref *rm;
int autotags = (transport->remote->fetch_tags == 1);
+
+ for_each_ref(add_existing, &existing_refs);
+
if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
tags = TAGS_SET;
if (transport->remote->fetch_tags == -1)
@@ -627,7 +668,7 @@ static int do_fetch(struct transport *transport,
die("Don't know how to fetch from %s", transport->url);
/* if not appending, truncate FETCH_HEAD */
- if (!append) {
+ if (!append && !dry_run) {
char *filename = git_path("FETCH_HEAD");
FILE *fp = fopen(filename, "w");
if (!fp)
@@ -640,8 +681,13 @@ static int do_fetch(struct transport *transport,
check_not_current_branch(ref_map);
for (rm = ref_map; rm; rm = rm->next) {
- if (rm->peer_ref)
- read_ref(rm->peer_ref->name, rm->peer_ref->old_sha1);
+ if (rm->peer_ref) {
+ peer_item = string_list_lookup(rm->peer_ref->name,
+ &existing_refs);
+ if (peer_item)
+ hashcpy(rm->peer_ref->old_sha1,
+ peer_item->util);
+ }
}
if (tags == TAGS_DEFAULT && autotags)
@@ -650,6 +696,8 @@ static int do_fetch(struct transport *transport,
free_refs(ref_map);
return 1;
}
+ if (prune)
+ prune_refs(transport, ref_map);
free_refs(ref_map);
/* if neither --no-tags nor --tags was specified, do automated tag
@@ -680,33 +728,100 @@ static void set_option(const char *name, const char *value)
name, transport->url);
}
-int cmd_fetch(int argc, const char **argv, const char *prefix)
+static int get_one_remote_for_fetch(struct remote *remote, void *priv)
+{
+ struct string_list *list = priv;
+ if (!remote->skip_default_update)
+ string_list_append(remote->name, list);
+ return 0;
+}
+
+struct remote_group_data {
+ const char *name;
+ struct string_list *list;
+};
+
+static int get_remote_group(const char *key, const char *value, void *priv)
+{
+ struct remote_group_data *g = priv;
+
+ if (!prefixcmp(key, "remotes.") &&
+ !strcmp(key + 8, g->name)) {
+ /* split list by white space */
+ int space = strcspn(value, " \t\n");
+ while (*value) {
+ if (space > 1) {
+ string_list_append(xstrndup(value, space),
+ g->list);
+ }
+ value += space + (value[space] != '\0');
+ space = strcspn(value, " \t\n");
+ }
+ }
+
+ return 0;
+}
+
+static int add_remote_or_group(const char *name, struct string_list *list)
+{
+ int prev_nr = list->nr;
+ struct remote_group_data g = { name, list };
+
+ git_config(get_remote_group, &g);
+ if (list->nr == prev_nr) {
+ struct remote *remote;
+ if (!remote_is_configured(name))
+ return 0;
+ remote = remote_get(name);
+ string_list_append(remote->name, list);
+ }
+ return 1;
+}
+
+static int fetch_multiple(struct string_list *list)
+{
+ int i, result = 0;
+ const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL, NULL };
+ int argc = 1;
+
+ if (dry_run)
+ argv[argc++] = "--dry-run";
+ if (prune)
+ argv[argc++] = "--prune";
+ if (verbosity >= 2)
+ argv[argc++] = "-v";
+ if (verbosity >= 1)
+ argv[argc++] = "-v";
+ else if (verbosity < 0)
+ argv[argc++] = "-q";
+
+ for (i = 0; i < list->nr; i++) {
+ const char *name = list->items[i].string;
+ argv[argc] = name;
+ if (verbosity >= 0)
+ printf("Fetching %s\n", name);
+ if (run_command_v_opt(argv, RUN_GIT_CMD)) {
+ error("Could not fetch %s", name);
+ result = 1;
+ }
+ }
+
+ return result;
+}
+
+static int fetch_one(struct remote *remote, int argc, const char **argv)
{
- struct remote *remote;
int i;
static const char **refs = NULL;
int ref_nr = 0;
int exit_code;
- /* Record the command line for the reflog */
- strbuf_addstr(&default_rla, "fetch");
- for (i = 1; i < argc; i++)
- strbuf_addf(&default_rla, " %s", argv[i]);
-
- argc = parse_options(argc, argv, prefix,
- builtin_fetch_options, builtin_fetch_usage, 0);
-
- if (argc == 0)
- remote = remote_get(NULL);
- else
- remote = remote_get(argv[0]);
-
if (!remote)
die("Where do you want to fetch from today?");
transport = transport_get(remote, remote->url[0]);
if (verbosity >= 2)
- transport->verbose = 1;
+ transport->verbose = verbosity <= 3 ? verbosity : 3;
if (verbosity < 0)
transport->verbose = -1;
if (upload_pack)
@@ -716,10 +831,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
if (depth)
set_option(TRANS_OPT_DEPTH, depth);
- if (argc > 1) {
+ if (argc > 0) {
int j = 0;
refs = xcalloc(argc + 1, sizeof(const char *));
- for (i = 1; i < argc; i++) {
+ for (i = 0; i < argc; i++) {
if (!strcmp(argv[i], "tag")) {
char *ref;
i++;
@@ -746,3 +861,57 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
transport = NULL;
return exit_code;
}
+
+int cmd_fetch(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct string_list list = { NULL, 0, 0, 0 };
+ struct remote *remote;
+ int result = 0;
+
+ /* Record the command line for the reflog */
+ strbuf_addstr(&default_rla, "fetch");
+ for (i = 1; i < argc; i++)
+ strbuf_addf(&default_rla, " %s", argv[i]);
+
+ argc = parse_options(argc, argv, prefix,
+ builtin_fetch_options, builtin_fetch_usage, 0);
+
+ if (all) {
+ if (argc == 1)
+ die("fetch --all does not take a repository argument");
+ else if (argc > 1)
+ die("fetch --all does not make sense with refspecs");
+ (void) for_each_remote(get_one_remote_for_fetch, &list);
+ result = fetch_multiple(&list);
+ } else if (argc == 0) {
+ /* No arguments -- use default remote */
+ remote = remote_get(NULL);
+ result = fetch_one(remote, argc, argv);
+ } else if (multiple) {
+ /* All arguments are assumed to be remotes or groups */
+ for (i = 0; i < argc; i++)
+ if (!add_remote_or_group(argv[i], &list))
+ die("No such remote or remote group: %s", argv[i]);
+ result = fetch_multiple(&list);
+ } else {
+ /* Single remote or group */
+ (void) add_remote_or_group(argv[0], &list);
+ if (list.nr > 1) {
+ /* More than one remote */
+ if (argc > 1)
+ die("Fetching a group and specifying refspecs does not make sense");
+ result = fetch_multiple(&list);
+ } else {
+ /* Zero or one remotes */
+ remote = remote_get(argv[0]);
+ result = fetch_one(remote, argc-1, argv+1);
+ }
+ }
+
+ /* All names were strdup()ed or strndup()ed */
+ list.strdup_strings = 1;
+ string_list_clear(&list, 0);
+
+ return result;
+}
diff --git a/builtin-fsck.c b/builtin-fsck.c
index 2d88e4570f..0e5faae381 100644
--- a/builtin-fsck.c
+++ b/builtin-fsck.c
@@ -47,6 +47,7 @@ static void objreport(struct object *obj, const char *severity,
fputs("\n", stderr);
}
+__attribute__((format (printf, 2, 3)))
static int objerror(struct object *obj, const char *err, ...)
{
va_list params;
@@ -57,6 +58,7 @@ static int objerror(struct object *obj, const char *err, ...)
return -1;
}
+__attribute__((format (printf, 3, 4)))
static int fsck_error_func(struct object *obj, int type, const char *err, ...)
{
va_list params;
diff --git a/builtin-grep.c b/builtin-grep.c
index 1df25b07b5..a5b6719a1a 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -367,7 +367,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
push_arg("-h");
if (opt->regflags & REG_EXTENDED)
push_arg("-E");
- if (opt->regflags & REG_ICASE)
+ if (opt->ignore_case)
push_arg("-i");
if (opt->binary == GREP_BINARY_NOMATCH)
push_arg("-I");
@@ -433,7 +433,11 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
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;
@@ -706,8 +710,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
OPT_GROUP(""),
OPT_BOOLEAN('v', "invert-match", &opt.invert,
"show non-matching lines"),
- OPT_BIT('i', "ignore-case", &opt.regflags,
- "case insensitive matching", REG_ICASE),
+ OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case,
+ "case insensitive matching"),
OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp,
"match patterns only at word boundaries"),
OPT_SET_INT('a', "text", &opt.binary,
@@ -788,6 +792,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
OPT_END()
};
+ /*
+ * 'git grep -h', unlike 'git grep -h <pattern>', is a request
+ * to show usage information and exit.
+ */
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(grep_usage, options);
+
memset(&opt, 0, sizeof(opt));
opt.prefix = prefix;
opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
@@ -830,6 +841,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
external_grep_allowed = 0;
if (!opt.pattern_list)
die("no pattern given.");
+ if (!opt.fixed && opt.ignore_case)
+ opt.regflags |= REG_ICASE;
if ((opt.regflags != REG_NEWLINE) && opt.fixed)
die("cannot mix --fixed-strings and regexp");
compile_grep_patterns(&opt);
diff --git a/builtin-log.c b/builtin-log.c
index 207a36178b..33fa6ea6c8 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -50,6 +50,12 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
if (default_date_mode)
rev->date_mode = parse_date_format(default_date_mode);
+ /*
+ * Check for -h before setup_revisions(), or "git log -h" will
+ * fail when run without a git directory.
+ */
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(builtin_log_usage);
argc = setup_revisions(argc, argv, rev, "HEAD");
if (rev->diffopt.pickaxe || rev->diffopt.filter)
@@ -891,6 +897,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
struct patch_ids ids;
char *add_signoff = NULL;
struct strbuf buf = STRBUF_INIT;
+ int use_patch_format = 0;
const struct option builtin_format_patch_options[] = {
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
"use [PATCH n/m] even with a single patch",
@@ -922,6 +929,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
"don't output binary diffs"),
OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
"don't include a patch matching a commit upstream"),
+ { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL,
+ "show patch format instead of default (patch + stat)",
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG },
OPT_GROUP("Messaging"),
{ OPTION_CALLBACK, 0, "add-header", NULL, "header",
"add email header", PARSE_OPT_NONEG,
@@ -1027,9 +1037,20 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (argc > 1)
die ("unrecognized argument: %s", argv[1]);
- if (!rev.diffopt.output_format
- || rev.diffopt.output_format == DIFF_FORMAT_PATCH)
- rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH;
+ if (rev.diffopt.output_format & DIFF_FORMAT_NAME)
+ die("--name-only does not make sense");
+ if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS)
+ die("--name-status does not make sense");
+ if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF)
+ die("--check does not make sense");
+
+ if (!use_patch_format &&
+ (!rev.diffopt.output_format ||
+ rev.diffopt.output_format == DIFF_FORMAT_PATCH))
+ rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY;
+
+ /* Always generate a patch */
+ rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff)
DIFF_OPT_SET(&rev.diffopt, BINARY);
@@ -1237,6 +1258,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
argv++;
}
+ if (argc > 1 && !strcmp(argv[1], "-h"))
+ usage(cherry_usage);
+
switch (argc) {
case 4:
limit = argv[3];
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index c5c0407b0b..c9a03e5427 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -170,6 +170,10 @@ static void show_files(struct dir_struct *dir, const char *prefix)
if (show_cached | show_stage) {
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
+ int dtype = ce_to_dtype(ce);
+ if (dir->flags & DIR_SHOW_IGNORED &&
+ !excluded(dir, ce->name, &dtype))
+ continue;
if (show_unmerged && !ce_stage(ce))
continue;
if (ce->ce_flags & CE_UPDATE)
@@ -182,6 +186,10 @@ static void show_files(struct dir_struct *dir, const char *prefix)
struct cache_entry *ce = active_cache[i];
struct stat st;
int err;
+ int dtype = ce_to_dtype(ce);
+ if (dir->flags & DIR_SHOW_IGNORED &&
+ !excluded(dir, ce->name, &dtype))
+ continue;
if (ce->ce_flags & CE_UPDATE)
continue;
err = lstat(ce->name, &st);
diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c
index 78a88f7476..b5bad0c184 100644
--- a/builtin-ls-remote.c
+++ b/builtin-ls-remote.c
@@ -86,10 +86,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
pattern[j - i] = p;
}
}
- remote = nongit ? NULL : remote_get(dest);
- if (remote && !remote->url_nr)
+ remote = remote_get(dest);
+ if (!remote->url_nr)
die("remote %s has no configured URL", dest);
- transport = transport_get(remote, remote ? remote->url[0] : dest);
+ transport = transport_get(remote, remote->url[0]);
if (uploadpack != NULL)
transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c
index 22008dfa8f..4484185afc 100644
--- a/builtin-ls-tree.c
+++ b/builtin-ls-tree.c
@@ -9,6 +9,7 @@
#include "commit.h"
#include "quote.h"
#include "builtin.h"
+#include "parse-options.h"
static int line_termination = '\n';
#define LS_RECURSIVE 1
@@ -22,8 +23,10 @@ static const char **pathspec;
static int chomp_prefix;
static const char *ls_tree_prefix;
-static const char ls_tree_usage[] =
- "git ls-tree [-d] [-r] [-t] [-l] [-z] [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] <tree-ish> [path...]";
+static const char * const ls_tree_usage[] = {
+ "git ls-tree [<options>] <tree-ish> [path...]",
+ NULL
+};
static int show_recursive(const char *base, int baselen, const char *pathname)
{
@@ -117,76 +120,53 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
{
unsigned char sha1[20];
struct tree *tree;
+ int full_tree = 0;
+ const struct option ls_tree_options[] = {
+ OPT_BIT('d', NULL, &ls_options, "only show trees",
+ LS_TREE_ONLY),
+ OPT_BIT('r', NULL, &ls_options, "recurse into subtrees",
+ LS_RECURSIVE),
+ OPT_BIT('t', NULL, &ls_options, "show trees when recursing",
+ LS_SHOW_TREES),
+ OPT_SET_INT('z', NULL, &line_termination,
+ "terminate entries with NUL byte", 0),
+ OPT_BIT('l', "long", &ls_options, "include object size",
+ LS_SHOW_SIZE),
+ OPT_BIT(0, "name-only", &ls_options, "list only filenames",
+ LS_NAME_ONLY),
+ OPT_BIT(0, "name-status", &ls_options, "list only filenames",
+ LS_NAME_ONLY),
+ OPT_SET_INT(0, "full-name", &chomp_prefix,
+ "use full path names", 0),
+ OPT_BOOLEAN(0, "full-tree", &full_tree,
+ "list entire tree; not just current directory "
+ "(implies --full-name)"),
+ OPT__ABBREV(&abbrev),
+ OPT_END()
+ };
git_config(git_default_config, NULL);
ls_tree_prefix = prefix;
if (prefix && *prefix)
chomp_prefix = strlen(prefix);
- while (1 < argc && argv[1][0] == '-') {
- switch (argv[1][1]) {
- case 'z':
- line_termination = 0;
- break;
- case 'r':
- ls_options |= LS_RECURSIVE;
- break;
- case 'd':
- ls_options |= LS_TREE_ONLY;
- break;
- case 't':
- ls_options |= LS_SHOW_TREES;
- break;
- case 'l':
- ls_options |= LS_SHOW_SIZE;
- break;
- case '-':
- if (!strcmp(argv[1]+2, "name-only") ||
- !strcmp(argv[1]+2, "name-status")) {
- ls_options |= LS_NAME_ONLY;
- break;
- }
- if (!strcmp(argv[1]+2, "long")) {
- ls_options |= LS_SHOW_SIZE;
- break;
- }
- if (!strcmp(argv[1]+2, "full-name")) {
- chomp_prefix = 0;
- break;
- }
- if (!strcmp(argv[1]+2, "full-tree")) {
- ls_tree_prefix = prefix = NULL;
- chomp_prefix = 0;
- break;
- }
- if (!prefixcmp(argv[1]+2, "abbrev=")) {
- abbrev = strtoul(argv[1]+9, NULL, 10);
- if (abbrev && abbrev < MINIMUM_ABBREV)
- abbrev = MINIMUM_ABBREV;
- else if (abbrev > 40)
- abbrev = 40;
- break;
- }
- if (!strcmp(argv[1]+2, "abbrev")) {
- abbrev = DEFAULT_ABBREV;
- break;
- }
- /* otherwise fallthru */
- default:
- usage(ls_tree_usage);
- }
- argc--; argv++;
+
+ argc = parse_options(argc, argv, prefix, ls_tree_options,
+ ls_tree_usage, 0);
+ if (full_tree) {
+ ls_tree_prefix = prefix = NULL;
+ chomp_prefix = 0;
}
/* -d -r should imply -t, but -d by itself should not have to. */
if ( (LS_TREE_ONLY|LS_RECURSIVE) ==
((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
ls_options |= LS_SHOW_TREES;
- if (argc < 2)
- usage(ls_tree_usage);
- if (get_sha1(argv[1], sha1))
- die("Not a valid object name %s", argv[1]);
+ if (argc < 1)
+ usage_with_options(ls_tree_usage, ls_tree_options);
+ if (get_sha1(argv[0], sha1))
+ die("Not a valid object name %s", argv[0]);
- pathspec = get_pathspec(prefix, argv + 2);
+ pathspec = get_pathspec(prefix, argv + 1);
tree = parse_tree_indirect(sha1);
if (!tree)
die("not a tree object");
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index c90cd312ac..3c4f0753fe 100644
--- a/builtin-mailinfo.c
+++ b/builtin-mailinfo.c
@@ -26,6 +26,7 @@ static struct strbuf charset = STRBUF_INIT;
static int patch_lines;
static struct strbuf **p_hdr_data, **s_hdr_data;
static int use_scissors;
+static int use_inbody_headers = 1;
#define MAX_HDR_PARSED 10
#define MAX_BOUNDARIES 5
@@ -774,10 +775,17 @@ static int handle_commit_msg(struct strbuf *line)
strbuf_ltrim(line);
if (!line->len)
return 0;
+ }
+
+ if (use_inbody_headers && still_looking) {
still_looking = check_header(line, s_hdr_data, 0);
if (still_looking)
return 0;
- }
+ } else
+ /* Only trim the first (blank) line of the commit message
+ * when ignoring in-body headers.
+ */
+ still_looking = 0;
/* normalize the log message to UTF-8. */
if (metainfo_charset)
@@ -1033,6 +1041,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
use_scissors = 1;
else if (!strcmp(argv[1], "--no-scissors"))
use_scissors = 0;
+ else if (!strcmp(argv[1], "--no-inbody-headers"))
+ use_inbody_headers = 0;
else
usage(mailinfo_usage);
argc--; argv++;
diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c
index dfe5b151e6..207e358ed1 100644
--- a/builtin-mailsplit.c
+++ b/builtin-mailsplit.c
@@ -231,6 +231,8 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix)
continue;
} else if ( arg[1] == 'f' ) {
nr = strtol(arg+2, NULL, 10);
+ } else if ( arg[1] == 'h' ) {
+ usage(git_mailsplit_usage);
} else if ( arg[1] == 'b' && !arg[2] ) {
allow_bare = 1;
} else if (!strcmp(arg, "--keep-cr")) {
diff --git a/builtin-merge-ours.c b/builtin-merge-ours.c
index 8f5bbaf402..684411694f 100644
--- a/builtin-merge-ours.c
+++ b/builtin-merge-ours.c
@@ -10,6 +10,9 @@
#include "git-compat-util.h"
#include "builtin.h"
+static const char builtin_merge_ours_usage[] =
+ "git merge-ours <base>... -- HEAD <remote>...";
+
static const char *diff_index_args[] = {
"diff-index", "--quiet", "--cached", "HEAD", "--", NULL
};
@@ -17,6 +20,9 @@ static const char *diff_index_args[] = {
int cmd_merge_ours(int argc, const char **argv, const char *prefix)
{
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(builtin_merge_ours_usage);
+
/*
* We need to exit with 2 if the index does not match our HEAD tree,
* because the current index is what we will be committing as the
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
index d26a96e486..710674c6b2 100644
--- a/builtin-merge-recursive.c
+++ b/builtin-merge-recursive.c
@@ -33,7 +33,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
}
if (argc < 4)
- die("Usage: %s <base>... -- <head> <remote> ...", argv[0]);
+ usagef("%s <base>... -- <head> <remote> ...", argv[0]);
for (i = 1; i < argc; ++i) {
if (!strcmp(argv[i], "--"))
diff --git a/builtin-merge.c b/builtin-merge.c
index c69a3051f3..57eedd447d 100644
--- a/builtin-merge.c
+++ b/builtin-merge.c
@@ -43,6 +43,7 @@ static const char * const builtin_merge_usage[] = {
static int show_diffstat = 1, option_log, squash;
static int option_commit = 1, allow_fast_forward = 1;
+static int fast_forward_only;
static int allow_trivial = 1, have_message;
static struct strbuf merge_msg;
static struct commit_list *remoteheads;
@@ -166,7 +167,9 @@ static struct option builtin_merge_options[] = {
OPT_BOOLEAN(0, "commit", &option_commit,
"perform a commit if the merge succeeds (default)"),
OPT_BOOLEAN(0, "ff", &allow_fast_forward,
- "allow fast forward (default)"),
+ "allow fast-forward (default)"),
+ OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
+ "abort if fast-forward is not possible"),
OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
"merge strategy to use", option_parse_strategy),
OPT_CALLBACK('m', "message", &merge_msg, "message",
@@ -843,7 +846,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
const char *best_strategy = NULL, *wt_strategy = NULL;
struct commit_list **remotes = &remoteheads;
- setup_work_tree();
if (file_exists(git_path("MERGE_HEAD")))
die("You have not concluded your merge. (MERGE_HEAD exists)");
if (read_cache_unmerged())
@@ -877,6 +879,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
option_commit = 0;
}
+ if (!allow_fast_forward && fast_forward_only)
+ die("You cannot combine --no-ff with --ff-only.");
+
if (!argc)
usage_with_options(builtin_merge_usage,
builtin_merge_options);
@@ -1016,7 +1021,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
hex,
find_unique_abbrev(remoteheads->item->object.sha1,
DEFAULT_ABBREV));
- strbuf_addstr(&msg, "Fast forward");
+ strbuf_addstr(&msg, "Fast-forward");
if (have_message)
strbuf_addstr(&msg,
" (no commit created; -m option ignored)");
@@ -1034,16 +1039,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
} else if (!remoteheads->next && common->next)
;
/*
- * We are not doing octopus and not fast forward. Need
+ * We are not doing octopus and not fast-forward. Need
* a real merge.
*/
else if (!remoteheads->next && !common->next && option_commit) {
/*
- * We are not doing octopus, not fast forward, and have
+ * We are not doing octopus, not fast-forward, and have
* only one common.
*/
refresh_cache(REFRESH_QUIET);
- if (allow_trivial) {
+ if (allow_trivial && !fast_forward_only) {
/* See if it is really trivial. */
git_committer_info(IDENT_ERROR_ON_NO_NAME);
printf("Trying really trivial in-index merge...\n");
@@ -1082,6 +1087,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
}
+ if (fast_forward_only)
+ die("Not possible to fast-forward, aborting.");
+
/* We are going to make a new commit. */
git_committer_info(IDENT_ERROR_ON_NO_NAME);
diff --git a/builtin-mv.c b/builtin-mv.c
index 1b20028c67..f633d81424 100644
--- a/builtin-mv.c
+++ b/builtin-mv.c
@@ -64,15 +64,15 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
- newfd = hold_locked_index(&lock_file, 1);
- if (read_cache() < 0)
- die("index file corrupt");
-
argc = parse_options(argc, argv, prefix, builtin_mv_options,
builtin_mv_usage, 0);
if (--argc < 1)
usage_with_options(builtin_mv_usage, builtin_mv_options);
+ newfd = hold_locked_index(&lock_file, 1);
+ if (read_cache() < 0)
+ die("index file corrupt");
+
source = copy_pathspec(prefix, argv, argc, 0);
modes = xcalloc(argc, sizeof(enum update_mode));
dest_path = copy_pathspec(prefix, argv + argc, 1, 0);
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 02f9246cdb..4429d53a1e 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -24,6 +24,7 @@
static const char pack_usage[] =
"git pack-objects [{ -q | --progress | --all-progress }]\n"
+ " [--all-progress-implied]\n"
" [--max-pack-size=N] [--local] [--incremental]\n"
" [--window=N] [--window-memory=N] [--depth=N]\n"
" [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n"
@@ -1629,6 +1630,8 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
struct thread_params *p;
int i, ret, active_threads = 0;
+ 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);
return;
@@ -2122,6 +2125,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
{
int use_internal_rev_list = 0;
int thin = 0;
+ int all_progress_implied = 0;
uint32_t i;
const char **rp_av;
int rp_ac_alloc = 64;
@@ -2221,6 +2225,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
progress = 2;
continue;
}
+ if (!strcmp("--all-progress-implied", arg)) {
+ all_progress_implied = 1;
+ continue;
+ }
if (!strcmp("-q", arg)) {
progress = 0;
continue;
@@ -2324,10 +2332,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (keep_unreachable && unpack_unreachable)
die("--keep-unreachable and --unpack-unreachable are incompatible.");
-#ifdef THREADED_DELTA_SEARCH
- if (!delta_search_threads) /* --threads=0 means autodetect */
- delta_search_threads = online_cpus();
-#endif
+ if (progress && all_progress_implied)
+ progress = 2;
prepare_packed_git();
diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c
index be99eb0ac4..f9463deec2 100644
--- a/builtin-prune-packed.c
+++ b/builtin-prune-packed.c
@@ -71,7 +71,7 @@ void prune_packed_objects(int opts)
int cmd_prune_packed(int argc, const char **argv, const char *prefix)
{
- int opts = VERBOSE;
+ int opts = isatty(2) ? VERBOSE : 0;
const struct option prune_packed_options[] = {
OPT_BIT('n', "dry-run", &opts, "dry run", DRY_RUN),
OPT_NEGBIT('q', "quiet", &opts, "be quiet", VERBOSE),
diff --git a/builtin-push.c b/builtin-push.c
index 8631c06ed6..356d7c1fd3 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -66,7 +66,6 @@ static void setup_push_tracking(void)
static void setup_default_push_refspecs(void)
{
- git_config(git_default_config, NULL);
switch (push_default) {
default:
case PUSH_DEFAULT_MATCHING:
@@ -159,7 +158,7 @@ static int do_push(const char *repo, int flags)
error("failed to push some refs to '%s'", url[i]);
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"
+ "Merge the remote changes before pushing again. See the 'non-fast-forward'\n"
"section of 'git push --help' for details.\n");
}
errs++;
@@ -173,7 +172,6 @@ int cmd_push(int argc, const char **argv, const char *prefix)
int tags = 0;
int rc;
const char *repo = NULL; /* default repository */
-
struct option options[] = {
OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET),
OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE),
@@ -191,6 +189,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_END()
};
+ git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, push_usage, 0);
if (tags)
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 14c836b169..2a3a32cbfe 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -108,11 +108,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
git_config(git_default_config, NULL);
- newfd = hold_locked_index(&lock_file, 1);
-
argc = parse_options(argc, argv, unused_prefix, read_tree_options,
read_tree_usage, 0);
+ newfd = hold_locked_index(&lock_file, 1);
+
prefix_set = opts.prefix ? 1 : 0;
if (1 < opts.merge + opts.reset + prefix_set)
die("Which one? -m, --reset, or --prefix?");
diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c
index e8bde02c27..78c0e69cdc 100644
--- a/builtin-receive-pack.c
+++ b/builtin-receive-pack.c
@@ -341,9 +341,9 @@ static const char *update(struct command *cmd)
break;
free_commit_list(bases);
if (!ent) {
- error("denying non-fast forward %s"
+ error("denying non-fast-forward %s"
" (you should pull first)", name);
- return "non-fast forward";
+ return "non-fast-forward";
}
}
if (run_update_hook(cmd)) {
@@ -627,6 +627,8 @@ static void add_alternate_refs(void)
int cmd_receive_pack(int argc, const char **argv, const char *prefix)
{
+ int advertise_refs = 0;
+ int stateless_rpc = 0;
int i;
char *dir = NULL;
@@ -635,7 +637,15 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
const char *arg = *argv++;
if (*arg == '-') {
- /* Do flag handling here */
+ if (!strcmp(arg, "--advertise-refs")) {
+ advertise_refs = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--stateless-rpc")) {
+ stateless_rpc = 1;
+ continue;
+ }
+
usage(receive_pack_usage);
}
if (dir)
@@ -664,12 +674,16 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
" report-status delete-refs ofs-delta " :
" report-status delete-refs ";
- add_alternate_refs();
- write_head_info();
- clear_extra_refs();
+ if (advertise_refs || !stateless_rpc) {
+ add_alternate_refs();
+ write_head_info();
+ clear_extra_refs();
- /* EOF */
- packet_flush(1);
+ /* EOF */
+ packet_flush(1);
+ }
+ if (advertise_refs)
+ return 0;
read_head_info();
if (commands) {
diff --git a/builtin-reflog.c b/builtin-reflog.c
index e23b5ef979..749821078d 100644
--- a/builtin-reflog.c
+++ b/builtin-reflog.c
@@ -698,6 +698,9 @@ static const char reflog_usage[] =
int cmd_reflog(int argc, const char **argv, const char *prefix)
{
+ if (argc > 1 && !strcmp(argv[1], "-h"))
+ usage(reflog_usage);
+
/* With no command, we default to showing it. */
if (argc < 2 || *argv[1] == '-')
return cmd_log_reflog(argc, argv, prefix);
diff --git a/builtin-remote.c b/builtin-remote.c
index 0777dd719b..7916626218 100644
--- a/builtin-remote.c
+++ b/builtin-remote.c
@@ -12,10 +12,45 @@ static const char * const builtin_remote_usage[] = {
"git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
"git remote rename <old> <new>",
"git remote rm <name>",
- "git remote set-head <name> [-a | -d | <branch>]",
- "git remote show [-n] <name>",
+ "git remote set-head <name> (-a | -d | <branch>)",
+ "git remote [-v | --verbose] show [-n] <name>",
"git remote prune [-n | --dry-run] <name>",
- "git remote [-v | --verbose] update [-p | --prune] [group]",
+ "git remote [-v | --verbose] update [-p | --prune] [group | remote]",
+ NULL
+};
+
+static const char * const builtin_remote_add_usage[] = {
+ "git remote add [<options>] <name> <url>",
+ NULL
+};
+
+static const char * const builtin_remote_rename_usage[] = {
+ "git remote rename <old> <new>",
+ NULL
+};
+
+static const char * const builtin_remote_rm_usage[] = {
+ "git remote rm <name>",
+ NULL
+};
+
+static const char * const builtin_remote_sethead_usage[] = {
+ "git remote set-head <name> (-a | -d | <branch>])",
+ NULL
+};
+
+static const char * const builtin_remote_show_usage[] = {
+ "git remote show [<options>] <name>",
+ NULL
+};
+
+static const char * const builtin_remote_prune_usage[] = {
+ "git remote prune [<options>] <name>",
+ NULL
+};
+
+static const char * const builtin_remote_update_usage[] = {
+ "git remote update [<options>] [<group> | <remote>]...",
NULL
};
@@ -70,7 +105,6 @@ static int add(int argc, const char **argv)
int i;
struct option options[] = {
- OPT_GROUP("add specific options"),
OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"),
OPT_CALLBACK('t', "track", &track, "branch",
"branch(es) to track", opt_parse_track),
@@ -79,11 +113,11 @@ static int add(int argc, const char **argv)
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_add_usage,
0);
if (argc < 2)
- usage_with_options(builtin_remote_usage, options);
+ usage_with_options(builtin_remote_add_usage, options);
name = argv[0];
url = argv[1];
@@ -227,32 +261,10 @@ struct ref_states {
int queried;
};
-static int handle_one_branch(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data)
-{
- struct ref_states *states = cb_data;
- struct refspec refspec;
-
- memset(&refspec, 0, sizeof(refspec));
- refspec.dst = (char *)refname;
- if (!remote_find_tracking(states->remote, &refspec)) {
- struct string_list_item *item;
- const char *name = abbrev_branch(refspec.src);
- /* symbolic refs pointing nowhere were handled already */
- if ((flags & REF_ISSYMREF) ||
- string_list_has_string(&states->tracked, name) ||
- string_list_has_string(&states->new, name))
- return 0;
- item = string_list_append(name, &states->stale);
- item->util = xstrdup(refname);
- }
- return 0;
-}
-
static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)
{
struct ref *fetch_map = NULL, **tail = &fetch_map;
- struct ref *ref;
+ struct ref *ref, *stale_refs;
int i;
for (i = 0; i < states->remote->fetch_refspec_nr; i++)
@@ -268,11 +280,17 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
else
string_list_append(abbrev_branch(ref->name), &states->tracked);
}
+ stale_refs = get_stale_heads(states->remote, fetch_map);
+ for (ref = stale_refs; ref; ref = ref->next) {
+ struct string_list_item *item =
+ string_list_append(abbrev_branch(ref->name), &states->stale);
+ item->util = xstrdup(ref->name);
+ }
+ free_refs(stale_refs);
free_refs(fetch_map);
sort_string_list(&states->new);
sort_string_list(&states->tracked);
- for_each_ref(handle_one_branch, states);
sort_string_list(&states->stale);
return 0;
@@ -540,7 +558,7 @@ static int mv(int argc, const char **argv)
int i;
if (argc != 3)
- usage_with_options(builtin_remote_usage, options);
+ usage_with_options(builtin_remote_rename_usage, options);
rename.old = argv[1];
rename.new = argv[2];
@@ -681,7 +699,7 @@ static int rm(int argc, const char **argv)
int i, result;
if (argc != 2)
- usage_with_options(builtin_remote_usage, options);
+ usage_with_options(builtin_remote_rm_usage, options);
remote = remote_get(argv[1]);
if (!remote)
@@ -953,7 +971,7 @@ static int show_push_info_item(struct string_list_item *item, void *cb_data)
status = "up to date";
break;
case PUSH_STATUS_FASTFORWARD:
- status = "fast forwardable";
+ status = "fast-forwardable";
break;
case PUSH_STATUS_OUTOFDATE:
status = "local out of date";
@@ -976,7 +994,6 @@ static int show(int argc, const char **argv)
{
int no_query = 0, result = 0, query_flag = 0;
struct option options[] = {
- OPT_GROUP("show specific options"),
OPT_BOOLEAN('n', NULL, &no_query, "do not query remotes"),
OPT_END()
};
@@ -984,7 +1001,7 @@ static int show(int argc, const char **argv)
struct string_list info_list = { NULL, 0, 0, 0 };
struct show_info info;
- argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage,
0);
if (argc < 1)
@@ -1081,14 +1098,13 @@ static int set_head(int argc, const char **argv)
char *head_name = NULL;
struct option options[] = {
- OPT_GROUP("set-head specific options"),
OPT_BOOLEAN('a', "auto", &opt_a,
"set refs/remotes/<name>/HEAD according to remote"),
OPT_BOOLEAN('d', "delete", &opt_d,
"delete refs/remotes/<name>/HEAD"),
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_sethead_usage,
0);
if (argc)
strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
@@ -1114,7 +1130,7 @@ static int set_head(int argc, const char **argv)
if (delete_ref(buf.buf, NULL, REF_NODEREF))
result |= error("Could not delete %s", buf.buf);
} else
- usage_with_options(builtin_remote_usage, options);
+ usage_with_options(builtin_remote_sethead_usage, options);
if (head_name) {
unsigned char sha1[20];
@@ -1138,16 +1154,15 @@ static int prune(int argc, const char **argv)
{
int dry_run = 0, result = 0;
struct option options[] = {
- OPT_GROUP("prune specific options"),
OPT__DRY_RUN(&dry_run),
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_prune_usage,
0);
if (argc < 1)
- usage_with_options(builtin_remote_usage, options);
+ usage_with_options(builtin_remote_prune_usage, options);
for (; argc; argc--, argv++)
result |= prune_remote(*argv, dry_run);
@@ -1182,95 +1197,62 @@ static int prune_remote(const char *remote, int dry_run)
printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
abbrev_ref(refname, "refs/remotes/"));
- warn_dangling_symref(dangling_msg, refname);
+ warn_dangling_symref(stdout, dangling_msg, refname);
}
free_remote_ref_states(&states);
return result;
}
-static int get_one_remote_for_update(struct remote *remote, void *priv)
+static int get_remote_default(const char *key, const char *value, void *priv)
{
- struct string_list *list = priv;
- if (!remote->skip_default_update)
- string_list_append(remote->name, list);
- return 0;
-}
-
-static struct remote_group {
- const char *name;
- struct string_list *list;
-} remote_group;
-
-static int get_remote_group(const char *key, const char *value, void *num_hits)
-{
- if (!prefixcmp(key, "remotes.") &&
- !strcmp(key + 8, remote_group.name)) {
- /* split list by white space */
- int space = strcspn(value, " \t\n");
- while (*value) {
- if (space > 1) {
- string_list_append(xstrndup(value, space),
- remote_group.list);
- ++*((int *)num_hits);
- }
- value += space + (value[space] != '\0');
- space = strcspn(value, " \t\n");
- }
+ if (strcmp(key, "remotes.default") == 0) {
+ int *found = priv;
+ *found = 1;
}
-
return 0;
}
static int update(int argc, const char **argv)
{
- int i, result = 0, prune = 0;
- struct string_list list = { NULL, 0, 0, 0 };
- static const char *default_argv[] = { NULL, "default", NULL };
+ int i, prune = 0;
struct option options[] = {
- OPT_GROUP("update specific options"),
OPT_BOOLEAN('p', "prune", &prune,
"prune remotes after fetching"),
OPT_END()
};
+ const char **fetch_argv;
+ int fetch_argc = 0;
+ int default_defined = 0;
+
+ fetch_argv = xmalloc(sizeof(char *) * (argc+5));
- argc = parse_options(argc, argv, NULL, options, builtin_remote_usage,
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
PARSE_OPT_KEEP_ARGV0);
- if (argc < 2) {
- argc = 2;
- argv = default_argv;
- }
- remote_group.list = &list;
- for (i = 1; i < argc; i++) {
- int groups_found = 0;
- remote_group.name = argv[i];
- result = git_config(get_remote_group, &groups_found);
- if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) {
- struct remote *remote;
- if (!remote_is_configured(argv[i]))
- die("No such remote or remote group: %s",
- argv[i]);
- remote = remote_get(argv[i]);
- string_list_append(remote->name, remote_group.list);
- }
- }
+ fetch_argv[fetch_argc++] = "fetch";
- if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default"))
- result = for_each_remote(get_one_remote_for_update, &list);
+ if (prune)
+ fetch_argv[fetch_argc++] = "--prune";
+ if (verbose)
+ fetch_argv[fetch_argc++] = "-v";
+ 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 = 0; i < list.nr; i++) {
- int err = fetch_remote(list.items[i].string);
- result |= err;
- if (!err && prune)
- result |= prune_remote(list.items[i].string, 0);
+ if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) {
+ git_config(get_remote_default, &default_defined);
+ if (!default_defined)
+ fetch_argv[fetch_argc-1] = "--all";
}
- /* all names were strdup()ed or strndup()ed */
- list.strdup_strings = 1;
- string_list_clear(&list, 0);
+ fetch_argv[fetch_argc] = NULL;
- return result;
+ return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
}
static int get_one_entry(struct remote *remote, void *priv)
@@ -1334,7 +1316,7 @@ static int show_all(void)
int cmd_remote(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
- OPT__VERBOSE(&verbose),
+ OPT_BOOLEAN('v', "verbose", &verbose, "be verbose; must be placed before a subcommand"),
OPT_END()
};
int result;
diff --git a/builtin-rerere.c b/builtin-rerere.c
index adfb7b5f48..343d6cde48 100644
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
@@ -106,6 +106,9 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
if (argc < 2)
return rerere();
+ if (!strcmp(argv[1], "-h"))
+ usage(git_rerere_usage);
+
fd = setup_rerere(&merge_rr);
if (fd < 0)
return 0;
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
index f6a56f34e1..91b604289d 100644
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
@@ -319,6 +319,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
memset(&info, 0, sizeof(info));
info.revs = &revs;
+ if (revs.bisect)
+ bisect_list = 1;
quiet = DIFF_OPT_TST(&revs.diffopt, QUIET);
for (i = 1 ; i < argc; i++) {
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index 45bead6545..37d0233521 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -180,6 +180,12 @@ static int show_reference(const char *refname, const unsigned char *sha1, int fl
return 0;
}
+static int anti_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ show_rev(REVERSED, sha1, refname);
+ return 0;
+}
+
static void show_datestring(const char *flag, const char *datestr)
{
static char buffer[100];
@@ -426,6 +432,13 @@ static void die_no_single_rev(int quiet)
die("Needed a single revision");
}
+static const char builtin_rev_parse_usage[] =
+"git rev-parse --parseopt [options] -- [<args>...]\n"
+" or: git rev-parse --sq-quote [<arg>...]\n"
+" or: git rev-parse [options] [<arg>...]\n"
+"\n"
+"Run \"git rev-parse --parseopt -h\" for more information on the first usage.";
+
int cmd_rev_parse(int argc, const char **argv, const char *prefix)
{
int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
@@ -438,6 +451,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (argc > 1 && !strcmp("--sq-quote", argv[1]))
return cmd_sq_quote(argc - 2, argv + 2);
+ if (argc > 1 && !strcmp("-h", argv[1]))
+ usage(builtin_rev_parse_usage);
+
prefix = setup_git_directory();
git_config(git_default_config, NULL);
for (i = 1; i < argc; i++) {
@@ -548,6 +564,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
for_each_ref(show_reference, NULL);
continue;
}
+ if (!strcmp(arg, "--bisect")) {
+ for_each_ref_in("refs/bisect/bad", show_reference, NULL);
+ for_each_ref_in("refs/bisect/good", anti_reference, NULL);
+ continue;
+ }
if (!strcmp(arg, "--branches")) {
for_each_branch_ref(show_reference, NULL);
continue;
diff --git a/builtin-send-pack.c b/builtin-send-pack.c
index 37e528e283..8fffdbf200 100644
--- a/builtin-send-pack.c
+++ b/builtin-send-pack.c
@@ -2,9 +2,11 @@
#include "commit.h"
#include "refs.h"
#include "pkt-line.h"
+#include "sideband.h"
#include "run-command.h"
#include "remote.h"
#include "send-pack.h"
+#include "quote.h"
static const char send_pack_usage[] =
"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
@@ -38,7 +40,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
*/
const char *argv[] = {
"pack-objects",
- "--all-progress",
+ "--all-progress-implied",
"--revs",
"--stdout",
NULL,
@@ -59,7 +61,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
memset(&po, 0, sizeof(po));
po.argv = argv;
po.in = -1;
- po.out = fd;
+ po.out = args->stateless_rpc ? -1 : fd;
po.git_cmd = 1;
if (start_command(&po))
die_errno("git pack-objects failed");
@@ -83,6 +85,20 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
}
close(po.in);
+
+ if (args->stateless_rpc) {
+ char *buf = xmalloc(LARGE_PACKET_MAX);
+ while (1) {
+ ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX);
+ if (n <= 0)
+ break;
+ send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX);
+ }
+ free(buf);
+ close(po.out);
+ po.out = -1;
+ }
+
if (finish_command(&po))
return error("pack-objects died with strange error");
return 0;
@@ -246,7 +262,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count)
break;
case REF_STATUS_REJECT_NONFASTFORWARD:
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
- "non-fast forward");
+ "non-fast-forward");
break;
case REF_STATUS_REMOTE_REJECT:
print_ref_status('!', "[remote rejected]", ref,
@@ -303,6 +319,59 @@ static int refs_pushed(struct ref *ref)
return 0;
}
+static void print_helper_status(struct ref *ref)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ for (; ref; ref = ref->next) {
+ const char *msg = NULL;
+ const char *res;
+
+ switch(ref->status) {
+ case REF_STATUS_NONE:
+ res = "error";
+ msg = "no match";
+ break;
+
+ case REF_STATUS_OK:
+ res = "ok";
+ break;
+
+ case REF_STATUS_UPTODATE:
+ res = "ok";
+ msg = "up to date";
+ break;
+
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ res = "error";
+ msg = "non-fast forward";
+ break;
+
+ case REF_STATUS_REJECT_NODELETE:
+ case REF_STATUS_REMOTE_REJECT:
+ res = "error";
+ break;
+
+ case REF_STATUS_EXPECTING_REPORT:
+ default:
+ continue;
+ }
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%s %s", res, ref->name);
+ if (ref->remote_status)
+ msg = ref->remote_status;
+ if (msg) {
+ strbuf_addch(&buf, ' ');
+ quote_two_c_style(&buf, "", msg, 0);
+ }
+ strbuf_addch(&buf, '\n');
+
+ safe_write(1, buf.buf, buf.len);
+ }
+ strbuf_release(&buf);
+}
+
int send_pack(struct send_pack_args *args,
int fd[], struct child_process *conn,
struct ref *remote_refs,
@@ -310,6 +379,7 @@ int send_pack(struct send_pack_args *args,
{
int in = fd[0];
int out = fd[1];
+ struct strbuf req_buf = STRBUF_INIT;
struct ref *ref;
int new_refs;
int ask_for_status_report = 0;
@@ -391,14 +461,14 @@ int send_pack(struct send_pack_args *args,
char *new_hex = sha1_to_hex(ref->new_sha1);
if (ask_for_status_report) {
- packet_write(out, "%s %s %s%c%s",
+ packet_buf_write(&req_buf, "%s %s %s%c%s",
old_hex, new_hex, ref->name, 0,
"report-status");
ask_for_status_report = 0;
expect_status_report = 1;
}
else
- packet_write(out, "%s %s %s",
+ packet_buf_write(&req_buf, "%s %s %s",
old_hex, new_hex, ref->name);
}
ref->status = expect_status_report ?
@@ -406,7 +476,17 @@ int send_pack(struct send_pack_args *args,
REF_STATUS_OK;
}
- packet_flush(out);
+ if (args->stateless_rpc) {
+ if (!args->dry_run) {
+ packet_buf_flush(&req_buf);
+ send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
+ }
+ } else {
+ safe_write(out, req_buf.buf, req_buf.len);
+ packet_flush(out);
+ }
+ strbuf_release(&req_buf);
+
if (new_refs && !args->dry_run) {
if (pack_objects(out, remote_refs, extra_have, args) < 0) {
for (ref = remote_refs; ref; ref = ref->next)
@@ -414,11 +494,15 @@ int send_pack(struct send_pack_args *args,
return -1;
}
}
+ if (args->stateless_rpc && !args->dry_run)
+ packet_flush(out);
if (expect_status_report)
ret = receive_status(in, remote_refs);
else
ret = 0;
+ if (args->stateless_rpc)
+ packet_flush(out);
if (ret < 0)
return ret;
@@ -478,6 +562,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
struct extra_have_objects extra_have;
struct ref *remote_refs, *local_refs;
int ret;
+ int helper_status = 0;
int send_all = 0;
const char *receivepack = "git-receive-pack";
int flags;
@@ -523,6 +608,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
args.use_thin_pack = 1;
continue;
}
+ if (!strcmp(arg, "--stateless-rpc")) {
+ args.stateless_rpc = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--helper-status")) {
+ helper_status = 1;
+ continue;
+ }
usage(send_pack_usage);
}
if (!dest) {
@@ -551,7 +644,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
}
}
- conn = git_connect(fd, dest, receivepack, args.verbose ? CONNECT_VERBOSE : 0);
+ if (args.stateless_rpc) {
+ conn = NULL;
+ fd[0] = 0;
+ fd[1] = 1;
+ } else {
+ conn = git_connect(fd, dest, receivepack,
+ args.verbose ? CONNECT_VERBOSE : 0);
+ }
memset(&extra_have, 0, sizeof(extra_have));
@@ -575,12 +675,16 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
+ if (helper_status)
+ print_helper_status(remote_refs);
+
close(fd[1]);
close(fd[0]);
ret |= finish_connect(conn);
- print_push_status(dest, remote_refs);
+ if (!helper_status)
+ print_push_status(dest, remote_refs);
if (!args.dry_run && remote) {
struct ref *ref;
diff --git a/builtin-show-ref.c b/builtin-show-ref.c
index c46550c9c0..17ada88dfb 100644
--- a/builtin-show-ref.c
+++ b/builtin-show-ref.c
@@ -7,7 +7,7 @@
#include "parse-options.h"
static const char * const show_ref_usage[] = {
- "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] ",
+ "git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] ",
"git show-ref --exclude-existing[=pattern] < ref-list",
NULL
};
@@ -183,7 +183,10 @@ static const struct option show_ref_options[] = {
OPT_BOOLEAN(0, "heads", &heads_only, "only show heads (can be combined with tags)"),
OPT_BOOLEAN(0, "verify", &verify, "stricter reference checking, "
"requires exact ref path"),
- OPT_BOOLEAN('h', "head", &show_head, "show the HEAD reference"),
+ { OPTION_BOOLEAN, 'h', NULL, &show_head, NULL,
+ "show the HEAD reference",
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ OPT_BOOLEAN(0, "head", &show_head, "show the HEAD reference"),
OPT_BOOLEAN('d', "dereference", &deref_tags,
"dereference tags into object IDs"),
{ OPTION_CALLBACK, 's', "hash", &abbrev, "n",
@@ -201,6 +204,9 @@ static const struct option show_ref_options[] = {
int cmd_show_ref(int argc, const char **argv, const char *prefix)
{
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(show_ref_usage, show_ref_options);
+
argc = parse_options(argc, argv, prefix, show_ref_options,
show_ref_usage, PARSE_OPT_NO_INTERNAL_HELP);
diff --git a/builtin-stripspace.c b/builtin-stripspace.c
index 1fd2205d53..4d3b93fedb 100644
--- a/builtin-stripspace.c
+++ b/builtin-stripspace.c
@@ -73,9 +73,11 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix)
struct strbuf buf = STRBUF_INIT;
int strip_comments = 0;
- if (argc > 1 && (!strcmp(argv[1], "-s") ||
+ if (argc == 2 && (!strcmp(argv[1], "-s") ||
!strcmp(argv[1], "--strip-comments")))
strip_comments = 1;
+ else if (argc > 1)
+ usage("git stripspace [-s | --strip-comments] < <stream>");
if (strbuf_read(&buf, 0, 1024) < 0)
die_errno("could not read the input");
diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c
index 8b3a35e12d..3f1e7012db 100644
--- a/builtin-tar-tree.c
+++ b/builtin-tar-tree.c
@@ -11,6 +11,9 @@ static const char tar_tree_usage[] =
"git tar-tree [--remote=<repo>] <tree-ish> [basedir]\n"
"*** Note that this command is now deprecated; use \"git archive\" instead.";
+static const char builtin_get_tar_commit_id_usage[] =
+"git get-tar-commit-id < <tarfile>";
+
int cmd_tar_tree(int argc, const char **argv, const char *prefix)
{
/*
@@ -81,6 +84,9 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
char *content = buffer + RECORDSIZE;
ssize_t n;
+ if (argc != 1)
+ usage(builtin_get_tar_commit_id_usage);
+
n = read_in_full(0, buffer, HEADERSIZE);
if (n < HEADERSIZE)
die("git get-tar-commit-id: read error");
diff --git a/builtin-update-index.c b/builtin-update-index.c
index 92beaaf4b3..a6b7f2d636 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -27,6 +27,7 @@ static int mark_valid_only;
#define MARK_VALID 1
#define UNMARK_VALID 2
+__attribute__((format (printf, 1, 2)))
static void report(const char *fmt, ...)
{
va_list vp;
diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c
index c4cd1e1327..73f788ef24 100644
--- a/builtin-upload-archive.c
+++ b/builtin-upload-archive.c
@@ -67,6 +67,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix)
return write_archive(sent_argc, sent_argv, prefix, 0);
}
+__attribute__((format (printf, 1, 2)))
static void error_clnt(const char *fmt, ...)
{
char buf[1024];
@@ -132,7 +133,6 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
while (1) {
struct pollfd pfd[2];
- ssize_t processed[2] = { 0, 0 };
int status;
pfd[0].fd = fd1[0];
@@ -147,15 +147,14 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
}
continue;
}
- if (pfd[0].revents & POLLIN)
- /* Data stream ready */
- processed[0] = process_input(pfd[0].fd, 1);
if (pfd[1].revents & POLLIN)
/* Status stream ready */
- processed[1] = process_input(pfd[1].fd, 2);
- /* Always finish to read data when available */
- if (processed[0] || processed[1])
- continue;
+ if (process_input(pfd[1].fd, 2))
+ continue;
+ if (pfd[0].revents & POLLIN)
+ /* Data stream ready */
+ if (process_input(pfd[0].fd, 1))
+ continue;
if (waitpid(writer, &status, 0) < 0)
error_clnt("%s", lostchild);
diff --git a/builtin.h b/builtin.h
index a2174dc855..c3f83c093f 100644
--- a/builtin.h
+++ b/builtin.h
@@ -48,7 +48,6 @@ extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
extern int cmd_fetch(int argc, const char **argv, const char *prefix);
extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
-extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix);
extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);
extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix);
extern int cmd_format_patch(int argc, const char **argv, const char *prefix);
diff --git a/bundle.c b/bundle.c
index e04ab49711..ff97adcb89 100644
--- a/bundle.c
+++ b/bundle.c
@@ -343,7 +343,7 @@ int create_bundle(struct bundle_header *header, const char *path,
/* write pack */
argv_pack[0] = "pack-objects";
- argv_pack[1] = "--all-progress";
+ argv_pack[1] = "--all-progress-implied";
argv_pack[2] = "--stdout";
argv_pack[3] = "--thin";
argv_pack[4] = NULL;
diff --git a/cache.h b/cache.h
index 71a731dbc9..bf468e5235 100644
--- a/cache.h
+++ b/cache.h
@@ -369,9 +369,12 @@ static inline enum object_type object_type(unsigned int mode)
#define CONFIG_ENVIRONMENT "GIT_CONFIG"
#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
#define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES"
+#define NO_REPLACE_OBJECTS_ENVIRONMENT "GIT_NO_REPLACE_OBJECTS"
#define GITATTRIBUTES_FILE ".gitattributes"
#define INFOATTRIBUTES_FILE "info/attributes"
#define ATTRIBUTE_MACRO_PREFIX "[attr]"
+#define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
+#define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
extern int is_bare_repository_cfg;
extern int is_bare_repository(void);
@@ -568,6 +571,8 @@ enum object_creation_mode {
extern enum object_creation_mode object_creation_mode;
+extern char *notes_ref_name;
+
extern int grafts_replace_parents;
#define GIT_REPO_VERSION 0
@@ -645,6 +650,7 @@ int set_shared_perm(const char *path, int mode);
#define adjust_shared_perm(path) set_shared_perm((path), 0)
int safe_create_leading_directories(char *path);
int safe_create_leading_directories_const(const char *path);
+extern char *expand_user_path(const char *path);
char *enter_repo(char *path, int strict);
static inline int is_absolute_path(const char *path)
{
@@ -657,6 +663,7 @@ const char *make_relative_path(const char *abs, const char *base);
int normalize_path_copy(char *dst, const char *src);
int longest_ancestor_length(const char *path, const char *prefix_list);
char *strip_path_suffix(const char *path, const char *suffix);
+int daemon_avoid_alias(const char *path);
/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
extern int sha1_object_info(const unsigned char *, unsigned long *);
@@ -751,6 +758,8 @@ extern const char *git_author_info(int);
extern const char *git_committer_info(int);
extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int);
extern const char *fmt_name(const char *name, const char *email);
+extern const char *git_editor(void);
+extern const char *git_pager(void);
struct checkout {
const char *base_dir;
@@ -857,7 +866,6 @@ extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
extern int finish_connect(struct child_process *conn);
extern int path_match(const char *path, int nr, char **match);
-extern int get_ack(int fd, unsigned char *result_sha1);
struct extra_have_objects {
int nr, alloc;
unsigned char (*array)[20];
@@ -903,6 +911,7 @@ extern unsigned long git_config_ulong(const char *, const char *);
extern int git_config_bool_or_int(const char *, const char *, int *);
extern int git_config_bool(const char *, const char *);
extern int git_config_string(const char **, const char *, const char *);
+extern int git_config_pathname(const char **, const char *, const char *);
extern int git_config_set(const char *, const char *);
extern int git_config_set_multivar(const char *, const char *, const char *, int);
extern int git_config_rename_section(const char *, const char *);
@@ -961,7 +970,9 @@ extern void *alloc_object_node(void);
extern void alloc_report(void);
/* trace.c */
+__attribute__((format (printf, 1, 2)))
extern void trace_printf(const char *format, ...);
+__attribute__((format (printf, 2, 3)))
extern void trace_argv_printf(const char **argv, const char *format, ...);
/* convert.c */
diff --git a/color.h b/color.h
index 18abeb7c7d..7d8da6fe22 100644
--- a/color.h
+++ b/color.h
@@ -29,7 +29,9 @@ int git_color_default_config(const char *var, const char *value, void *cb);
int git_config_colorbool(const char *var, const char *value, int stdout_is_tty);
void color_parse(const char *value, const char *var, char *dst);
void color_parse_mem(const char *value, int len, const char *var, char *dst);
+__attribute__((format (printf, 3, 4)))
int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
+__attribute__((format (printf, 3, 4)))
int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf);
diff --git a/command-list.txt b/command-list.txt
index 59b0adc39b..cc5d48b385 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -74,6 +74,7 @@ git-mktag plumbingmanipulators
git-mktree plumbingmanipulators
git-mv mainporcelain common
git-name-rev plumbinginterrogators
+git-notes mainporcelain
git-pack-objects plumbingmanipulators
git-pack-redundant plumbinginterrogators
git-pack-refs ancillarymanipulators
diff --git a/commit.c b/commit.c
index 6393e1b362..632061c2c3 100644
--- a/commit.c
+++ b/commit.c
@@ -5,6 +5,7 @@
#include "utf8.h"
#include "diff.h"
#include "revision.h"
+#include "notes.h"
int save_commit_buffer = 1;
@@ -199,7 +200,7 @@ struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
return commit_graft[pos];
}
-int write_shallow_commits(int fd, int use_pack_protocol)
+int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
{
int i, count = 0;
for (i = 0; i < commit_graft_nr; i++)
@@ -208,12 +209,10 @@ int write_shallow_commits(int fd, int use_pack_protocol)
sha1_to_hex(commit_graft[i]->sha1);
count++;
if (use_pack_protocol)
- packet_write(fd, "shallow %s", hex);
+ packet_buf_write(out, "shallow %s", hex);
else {
- if (write_in_full(fd, hex, 40) != 40)
- break;
- if (write_str_in_full(fd, "\n") != 1)
- break;
+ strbuf_addstr(out, hex);
+ strbuf_addch(out, '\n');
}
}
return count;
diff --git a/commit.h b/commit.h
index 422f778f3f..e5332efcfc 100644
--- a/commit.h
+++ b/commit.h
@@ -139,7 +139,7 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
extern int register_shallow(const unsigned char *sha1);
extern int unregister_shallow(const unsigned char *sha1);
-extern int write_shallow_commits(int fd, int use_pack_protocol);
+extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol);
extern int is_repository_shallow(void);
extern struct commit_list *get_shallow_commits(struct object_array *heads,
int depth, int shallow_flag, int not_shallow_flag);
diff --git a/compat/mingw.c b/compat/mingw.c
index 6b5b5b2c70..15fe33eaa0 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1000,6 +1000,18 @@ repeat:
return -1;
}
+/*
+ * Note that this doesn't return the actual pagesize, but
+ * the allocation granularity. If future Windows specific git code
+ * needs the real getpagesize function, we need to find another solution.
+ */
+int mingw_getpagesize(void)
+{
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ return si.dwAllocationGranularity;
+}
+
struct passwd *getpwuid(int uid)
{
static char user_name[100];
diff --git a/compat/mingw.h b/compat/mingw.h
index 5b5258bceb..51993ef114 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -124,6 +124,27 @@ static inline int waitpid(pid_t pid, int *status, unsigned options)
return -1;
}
+#ifndef NO_OPENSSL
+#include <openssl/ssl.h>
+static inline int mingw_SSL_set_fd(SSL *ssl, int fd)
+{
+ return SSL_set_fd(ssl, _get_osfhandle(fd));
+}
+#define SSL_set_fd mingw_SSL_set_fd
+
+static inline int mingw_SSL_set_rfd(SSL *ssl, int fd)
+{
+ return SSL_set_rfd(ssl, _get_osfhandle(fd));
+}
+#define SSL_set_rfd mingw_SSL_set_rfd
+
+static inline int mingw_SSL_set_wfd(SSL *ssl, int fd)
+{
+ return SSL_set_wfd(ssl, _get_osfhandle(fd));
+}
+#define SSL_set_wfd mingw_SSL_set_wfd
+#endif
+
/*
* implementations of missing functions
*/
@@ -166,7 +187,7 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz);
int mingw_rename(const char*, const char*);
#define rename mingw_rename
-#ifdef USE_WIN32_MMAP
+#if defined(USE_WIN32_MMAP) || defined(_MSC_VER)
int mingw_getpagesize(void);
#define getpagesize mingw_getpagesize
#endif
diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl
index f9528c0ea1..8a2112f22f 100644
--- a/compat/vcbuild/scripts/clink.pl
+++ b/compat/vcbuild/scripts/clink.pl
@@ -29,6 +29,9 @@ while (@ARGV) {
push(@args, "zlib.lib");
} elsif ("$arg" eq "-liconv") {
push(@args, "iconv.lib");
+ } elsif ("$arg" eq "-lcrypto") {
+ push(@args, "libeay32.lib");
+ push(@args, "ssleay32.lib");
} elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") {
$arg =~ s/^-L/-LIBPATH:/;
push(@args, $arg);
diff --git a/compat/win32mmap.c b/compat/win32mmap.c
index 779d796cd5..1c5a14922f 100644
--- a/compat/win32mmap.c
+++ b/compat/win32mmap.c
@@ -1,17 +1,5 @@
#include "../git-compat-util.h"
-/*
- * Note that this doesn't return the actual pagesize, but
- * the allocation granularity. If future Windows specific git code
- * needs the real getpagesize function, we need to find another solution.
- */
-int mingw_getpagesize(void)
-{
- SYSTEM_INFO si;
- GetSystemInfo(&si);
- return si.dwAllocationGranularity;
-}
-
void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
{
HANDLE hmap;
diff --git a/config.c b/config.c
index c644061136..37385ce9d3 100644
--- a/config.c
+++ b/config.c
@@ -351,6 +351,16 @@ int git_config_string(const char **dest, const char *var, const char *value)
return 0;
}
+int git_config_pathname(const char **dest, const char *var, const char *value)
+{
+ if (!value)
+ return config_error_nonbool(var);
+ *dest = expand_user_path(value);
+ if (!*dest)
+ die("Failed to expand user dir in: '%s'", value);
+ return 0;
+}
+
static int git_default_core_config(const char *var, const char *value)
{
/* This needs a better name */
@@ -467,6 +477,11 @@ static int git_default_core_config(const char *var, const char *value)
return 0;
}
+ if (!strcmp(var, "core.notesref")) {
+ notes_ref_name = xstrdup(value);
+ return 0;
+ }
+
if (!strcmp(var, "core.pager"))
return git_config_string(&pager_program, var, value);
@@ -474,7 +489,7 @@ static int git_default_core_config(const char *var, const char *value)
return git_config_string(&editor_program, var, value);
if (!strcmp(var, "core.excludesfile"))
- return git_config_string(&excludes_file, var, value);
+ return git_config_pathname(&excludes_file, var, value);
if (!strcmp(var, "core.whitespace")) {
if (!value)
diff --git a/configure.ac b/configure.ac
index b09b8e446f..4625b8672b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -68,6 +68,26 @@ else \
GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \
fi \
])# GIT_PARSE_WITH
+#
+# GIT_PARSE_WITH_SET_MAKE_VAR(WITHNAME, VAR, HELP_TEXT)
+# ---------------------
+# Set VAR to the value specied by --with-WITHNAME.
+# No verification of arguments is performed, but warnings are issued
+# if either 'yes' or 'no' is specified.
+# HELP_TEXT is presented when --help is called.
+# This is a direct way to allow setting variables in the Makefile.
+AC_DEFUN([GIT_PARSE_WITH_SET_MAKE_VAR],
+[AC_ARG_WITH([$1],
+ [AS_HELP_STRING([--with-$1=VALUE], $3)],
+ if test -n "$withval"; then \
+ if test "$withval" = "yes" -o "$withval" = "no"; then \
+ AC_MSG_WARN([You likely do not want either 'yes' or 'no' as]
+ [a value for $1 ($2). Maybe you do...?]); \
+ fi; \
+ \
+ AC_MSG_NOTICE([Setting $2 to $withval]); \
+ GIT_CONF_APPEND_LINE($2=$withval); \
+ fi)])# GIT_PARSE_WITH_SET_MAKE_VAR
dnl
dnl GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE)
@@ -227,6 +247,29 @@ GIT_PARSE_WITH(iconv))
# change being considered an inode change from the update-index perspective.
#
+# Allow user to set ETC_GITCONFIG variable
+GIT_PARSE_WITH_SET_MAKE_VAR(gitconfig, ETC_GITCONFIG,
+ Use VALUE instead of /etc/gitconfig as the
+ global git configuration file.
+ If VALUE is not fully qualified it will be interpretted
+ as a path relative to the computed prefix at runtime.)
+
+#
+# Allow user to set the default pager
+GIT_PARSE_WITH_SET_MAKE_VAR(pager, DEFAULT_PAGER,
+ Use VALUE as the fall-back pager instead of 'less'.
+ This is used by things like 'git log' when the user
+ does not specify a pager to use through alternate
+ methods. eg: /usr/bin/pager)
+#
+# Allow user to set the default editor
+GIT_PARSE_WITH_SET_MAKE_VAR(editor, DEFAULT_EDITOR,
+ Use VALUE as the fall-back editor instead of 'vi'.
+ This is used by things like 'git commit' when the user
+ does not specify a preferred editor through other
+ methods. eg: /usr/bin/editor)
+
+#
# Define SHELL_PATH to provide path to shell.
GIT_ARG_SET_PATH(shell)
#
diff --git a/connect.c b/connect.c
index 7945e38ac1..db965c9982 100644
--- a/connect.c
+++ b/connect.c
@@ -107,27 +107,6 @@ int server_supports(const char *feature)
strstr(server_capabilities, feature) != NULL;
}
-int get_ack(int fd, unsigned char *result_sha1)
-{
- static char line[1000];
- int len = packet_read_line(fd, line, sizeof(line));
-
- if (!len)
- die("git fetch-pack: expected ACK/NAK, got EOF");
- if (line[len-1] == '\n')
- line[--len] = 0;
- if (!strcmp(line, "NAK"))
- return 0;
- if (!prefixcmp(line, "ACK ")) {
- if (!get_sha1_hex(line+4, result_sha1)) {
- if (strstr(line+45, "continue"))
- return 2;
- return 1;
- }
- }
- die("git fetch_pack: expected ACK/NAK, got '%s'", line);
-}
-
int path_match(const char *path, int nr, char **match)
{
int i;
@@ -630,6 +609,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
GIT_WORK_TREE_ENVIRONMENT,
GRAFT_ENVIRONMENT,
INDEX_ENVIRONMENT,
+ NO_REPLACE_OBJECTS_ENVIRONMENT,
NULL
};
conn->env = env;
diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl
index 20bd061b3e..d506717bfd 100644
--- a/contrib/buildsystems/engine.pl
+++ b/contrib/buildsystems/engine.pl
@@ -315,6 +315,9 @@ sub handleLinkLine
$appout = shift @parts;
} elsif ("$part" eq "-lz") {
push(@libs, "zlib.lib");
+ } elsif ("$part" eq "-lcrypto") {
+ push(@libs, "libeay32.lib");
+ push(@libs, "ssleay32.lib");
} elsif ($part =~ /^-/) {
push(@lflags, $part);
} elsif ($part =~ /\.(a|lib)$/) {
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e3ddecc995..11bf17a86c 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -21,13 +21,7 @@
# 2) Added the following line to your .bashrc:
# source ~/.git-completion.sh
#
-# 3) You may want to make sure the git executable is available
-# in your PATH before this script is sourced, as some caching
-# is performed while the script loads. If git isn't found
-# at source time then all lookups will be done on demand,
-# which may be slightly slower.
-#
-# 4) Consider changing your PS1 to also show the current branch:
+# 3) Consider changing your PS1 to also show the current branch:
# PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
#
# The argument to __git_ps1 will be displayed only if you
@@ -324,12 +318,8 @@ __git_remotes ()
done
}
-__git_merge_strategies ()
+__git_list_merge_strategies ()
{
- if [ -n "${__git_merge_strategylist-}" ]; then
- echo "$__git_merge_strategylist"
- return
- fi
git merge -s help 2>&1 |
sed -n -e '/[Aa]vailable strategies are: /,/^$/{
s/\.$//
@@ -339,8 +329,17 @@ __git_merge_strategies ()
p
}'
}
-__git_merge_strategylist=
-__git_merge_strategylist=$(__git_merge_strategies 2>/dev/null)
+
+__git_merge_strategies=
+# 'git merge -s help' (and thus detection of the merge strategy
+# list) fails, unfortunately, if run outside of any git working
+# tree. __git_merge_strategies is set to the empty string in
+# that case, and the detection will be repeated the next time it
+# is needed.
+__git_compute_merge_strategies ()
+{
+ : ${__git_merge_strategies:=$(__git_list_merge_strategies)}
+}
__git_complete_file ()
{
@@ -474,27 +473,24 @@ __git_complete_remote_or_refspec ()
__git_complete_strategy ()
{
+ __git_compute_merge_strategies
case "${COMP_WORDS[COMP_CWORD-1]}" in
-s|--strategy)
- __gitcomp "$(__git_merge_strategies)"
+ __gitcomp "$__git_merge_strategies"
return 0
esac
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--strategy=*)
- __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
+ __gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}"
return 0
;;
esac
return 1
}
-__git_all_commands ()
+__git_list_all_commands ()
{
- if [ -n "${__git_all_commandlist-}" ]; then
- echo "$__git_all_commandlist"
- return
- fi
local i IFS=" "$'\n'
for i in $(git help -a|egrep '^ [a-zA-Z0-9]')
do
@@ -504,17 +500,18 @@ __git_all_commands ()
esac
done
}
-__git_all_commandlist=
-__git_all_commandlist="$(__git_all_commands 2>/dev/null)"
-__git_porcelain_commands ()
+__git_all_commands=
+__git_compute_all_commands ()
+{
+ : ${__git_all_commands:=$(__git_list_all_commands)}
+}
+
+__git_list_porcelain_commands ()
{
- if [ -n "${__git_porcelain_commandlist-}" ]; then
- echo "$__git_porcelain_commandlist"
- return
- fi
local i IFS=" "$'\n'
- for i in "help" $(__git_all_commands)
+ __git_compute_all_commands
+ for i in "help" $__git_all_commands
do
case $i in
*--*) : helper pattern;;
@@ -595,8 +592,13 @@ __git_porcelain_commands ()
esac
done
}
-__git_porcelain_commandlist=
-__git_porcelain_commandlist="$(__git_porcelain_commands 2>/dev/null)"
+
+__git_porcelain_commands=
+__git_compute_porcelain_commands ()
+{
+ __git_compute_all_commands
+ : ${__git_porcelain_commands:=$(__git_list_porcelain_commands)}
+}
__git_aliases ()
{
@@ -953,7 +955,7 @@ _git_diff ()
}
__git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff
- tkdiff vimdiff gvimdiff xxdiff araxis
+ tkdiff vimdiff gvimdiff xxdiff araxis p4merge
"
_git_difftool ()
@@ -1088,7 +1090,8 @@ _git_help ()
return
;;
esac
- __gitcomp "$(__git_all_commands)
+ __git_compute_all_commands
+ __gitcomp "$__git_all_commands
attributes cli core-tutorial cvs-migration
diffcore gitk glossary hooks ignore modules
repository-layout tutorial tutorial-2
@@ -1224,7 +1227,7 @@ _git_log ()
__git_merge_options="
--no-commit --no-stat --log --no-log --squash --strategy
- --commit --stat --no-squash --ff --no-ff
+ --commit --stat --no-squash --ff --no-ff --ff-only
"
_git_merge ()
@@ -1444,7 +1447,8 @@ _git_config ()
return
;;
pull.twohead|pull.octopus)
- __gitcomp "$(__git_merge_strategies)"
+ __git_compute_merge_strategies
+ __gitcomp "$__git_merge_strategies"
return
;;
color.branch|color.diff|color.interactive|\
@@ -1545,7 +1549,8 @@ _git_config ()
pager.*)
local pfx="${cur%.*}."
cur="${cur#*.}"
- __gitcomp "$(__git_all_commands)" "$pfx" "$cur"
+ __git_compute_all_commands
+ __gitcomp "$__git_all_commands" "$pfx" "$cur"
return
;;
remote.*.*)
@@ -2142,7 +2147,8 @@ _git ()
--help
"
;;
- *) __gitcomp "$(__git_porcelain_commands) $(__git_aliases)" ;;
+ *) __git_compute_porcelain_commands
+ __gitcomp "$__git_porcelain_commands $(__git_aliases)" ;;
esac
return
fi
diff --git a/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c
index 3dbdf7a288..cd10dbcbc9 100644
--- a/builtin-fetch--tool.c
+++ b/contrib/examples/builtin-fetch--tool.c
@@ -97,21 +97,21 @@ static int update_local_ref(const char *name,
strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
if (in_merge_bases(current, &updated, 1)) {
- fprintf(stderr, "* %s: fast forward to %s\n",
+ fprintf(stderr, "* %s: fast-forward to %s\n",
name, note);
fprintf(stderr, " old..new: %s..%s\n", oldh, newh);
- return update_ref_env("fast forward", name, sha1_new, sha1_old);
+ return update_ref_env("fast-forward", name, sha1_new, sha1_old);
}
if (!force) {
fprintf(stderr,
- "* %s: not updating to non-fast forward %s\n",
+ "* %s: not updating to non-fast-forward %s\n",
name, note);
fprintf(stderr,
" old...new: %s...%s\n", oldh, newh);
return 1;
}
fprintf(stderr,
- "* %s: forcing update to non-fast forward %s\n",
+ "* %s: forcing update to non-fast-forward %s\n",
name, note);
fprintf(stderr, " old...new: %s...%s\n", oldh, newh);
return update_ref_env("forced-update", name, sha1_new, sha1_old);
diff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh
index e9588eec33..500635fe4b 100755
--- a/contrib/examples/git-merge.sh
+++ b/contrib/examples/git-merge.sh
@@ -14,7 +14,7 @@ summary (synonym to --stat)
log add list of one-line log to merge commit message
squash create a single commit instead of doing a merge
commit perform a commit if the merge succeeds (default)
-ff allow fast forward (default)
+ff allow fast-forward (default)
s,strategy= merge strategy to use
m,message= message to be used for the merge commit (if any)
"
@@ -353,7 +353,7 @@ t,1,"$head",*)
# Again the most common case of merging one remote.
echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)"
git update-index --refresh 2>/dev/null
- msg="Fast forward"
+ msg="Fast-forward"
if test -n "$have_message"
then
msg="$msg (no commit created; -m option ignored)"
@@ -365,11 +365,11 @@ t,1,"$head",*)
exit 0
;;
?,1,?*"$LF"?*,*)
- # We are not doing octopus and not fast forward. Need a
+ # We are not doing octopus and not fast-forward. Need a
# real merge.
;;
?,1,*,)
- # We are not doing octopus, not fast forward, and have only
+ # We are not doing octopus, not fast-forward, and have only
# one common.
git update-index --refresh 2>/dev/null
case "$allow_trivial_merge" in
diff --git a/contrib/examples/git-resolve.sh b/contrib/examples/git-resolve.sh
index 0ee1bd898e..8f98142f77 100755
--- a/contrib/examples/git-resolve.sh
+++ b/contrib/examples/git-resolve.sh
@@ -48,7 +48,7 @@ case "$common" in
"$head")
echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $merge)"
git read-tree -u -m $head $merge || exit 1
- git update-ref -m "resolve $merge_name: Fast forward" \
+ git update-ref -m "resolve $merge_name: Fast-forward" \
HEAD "$merge" "$head"
git diff-tree -p $head $merge | git apply --stat
dropheads
diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
index e710219ca5..48059d0aa7 100755
--- a/contrib/fast-import/git-p4
+++ b/contrib/fast-import/git-p4
@@ -729,13 +729,10 @@ class P4Submit(Command):
tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
tmpFile.close()
mtime = os.stat(fileName).st_mtime
- defaultEditor = "vi"
- if platform.system() == "Windows":
- defaultEditor = "notepad"
if os.environ.has_key("P4EDITOR"):
editor = os.environ.get("P4EDITOR")
else:
- editor = os.environ.get("EDITOR", defaultEditor);
+ editor = read_pipe("git var GIT_EDITOR")
system(editor + " " + fileName)
response = "y"
diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email
index 2a66063e44..58a35c8287 100755
--- a/contrib/hooks/post-receive-email
+++ b/contrib/hooks/post-receive-email
@@ -315,8 +315,8 @@ generate_update_branch_email()
# "remotes/" will be ignored as well.
# List all of the revisions that were removed by this update, in a
- # fast forward update, this list will be empty, because rev-list O
- # ^N is empty. For a non fast forward, O ^N is the list of removed
+ # fast-forward update, this list will be empty, because rev-list O
+ # ^N is empty. For a non-fast-forward, O ^N is the list of removed
# revisions
fast_forward=""
rev=""
@@ -411,7 +411,7 @@ generate_update_branch_email()
# revision because the base is effectively a random revision at this
# point - the user will be interested in what this revision changed
# - including the undoing of previous revisions in the case of
- # non-fast forward updates.
+ # non-fast-forward updates.
echo ""
echo "Summary of changes:"
git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev
diff --git a/daemon.c b/daemon.c
index 1b5ada6648..5783e24011 100644
--- a/daemon.c
+++ b/daemon.c
@@ -77,6 +77,7 @@ static void logreport(int priority, const char *err, va_list params)
}
}
+__attribute__((format (printf, 1, 2)))
static void logerror(const char *err, ...)
{
va_list params;
@@ -85,6 +86,7 @@ static void logerror(const char *err, ...)
va_end(params);
}
+__attribute__((format (printf, 1, 2)))
static void loginfo(const char *err, ...)
{
va_list params;
@@ -101,53 +103,6 @@ static void NORETURN daemon_die(const char *err, va_list params)
exit(1);
}
-static int avoid_alias(char *p)
-{
- int sl, ndot;
-
- /*
- * This resurrects the belts and suspenders paranoia check by HPA
- * done in <435560F7.4080006@zytor.com> thread, now enter_repo()
- * does not do getcwd() based path canonicalizations.
- *
- * sl becomes true immediately after seeing '/' and continues to
- * be true as long as dots continue after that without intervening
- * non-dot character.
- */
- if (!p || (*p != '/' && *p != '~'))
- return -1;
- sl = 1; ndot = 0;
- p++;
-
- while (1) {
- char ch = *p++;
- if (sl) {
- if (ch == '.')
- ndot++;
- else if (ch == '/') {
- if (ndot < 3)
- /* reject //, /./ and /../ */
- return -1;
- ndot = 0;
- }
- else if (ch == 0) {
- if (0 < ndot && ndot < 3)
- /* reject /.$ and /..$ */
- return -1;
- return 0;
- }
- else
- sl = ndot = 0;
- }
- else if (ch == 0)
- return 0;
- else if (ch == '/') {
- sl = 1;
- ndot = 0;
- }
- }
-}
-
static char *path_ok(char *directory)
{
static char rpath[PATH_MAX];
@@ -157,7 +112,7 @@ static char *path_ok(char *directory)
dir = directory;
- if (avoid_alias(dir)) {
+ if (daemon_avoid_alias(dir)) {
logerror("'%s': aliased", dir);
return NULL;
}
diff --git a/diff-lib.c b/diff-lib.c
index 0c74ef5cbe..adf1c5fdee 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -383,7 +383,7 @@ static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_op
* For diffing, the index is more important, and we only have a
* single tree.
*
- * We're supposed to return how many index entries we want to skip.
+ * We're supposed to advance o->pos to skip what we have already processed.
*
* This wrapper makes it all more readable, and takes care of all
* the fairly complex unpack_trees() semantic requirements, including
diff --git a/diff-no-index.c b/diff-no-index.c
index 4ebc1dbd87..aae8e7accc 100644
--- a/diff-no-index.c
+++ b/diff-no-index.c
@@ -201,8 +201,8 @@ void diff_no_index(struct rev_info *revs,
return;
}
if (argc != i + 2)
- die("git diff %s takes two paths",
- no_index ? "--no-index" : "[--no-index]");
+ usagef("git diff %s <path> <path>",
+ no_index ? "--no-index" : "[--no-index]");
diff_setup(&revs->diffopt);
for (i = 1; i < argc - 2; ) {
diff --git a/diff.c b/diff.c
index 3ad1f8c310..0d7f5ea4a8 100644
--- a/diff.c
+++ b/diff.c
@@ -686,14 +686,18 @@ static void diff_words_show(struct diff_words_data *diff_words)
diff_words->minus.text.size = diff_words->plus.text.size = 0;
}
+/* In "color-words" mode, show word-diff of words accumulated in the buffer */
+static void diff_words_flush(struct emit_callback *ecbdata)
+{
+ if (ecbdata->diff_words->minus.text.size ||
+ ecbdata->diff_words->plus.text.size)
+ diff_words_show(ecbdata->diff_words);
+}
+
static void free_diff_words_data(struct emit_callback *ecbdata)
{
if (ecbdata->diff_words) {
- /* flush buffers */
- if (ecbdata->diff_words->minus.text.size ||
- ecbdata->diff_words->plus.text.size)
- diff_words_show(ecbdata->diff_words);
-
+ diff_words_flush(ecbdata);
free (ecbdata->diff_words->minus.text.ptr);
free (ecbdata->diff_words->minus.orig);
free (ecbdata->diff_words->plus.text.ptr);
@@ -773,6 +777,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
}
if (line[0] == '@') {
+ if (ecbdata->diff_words)
+ diff_words_flush(ecbdata);
len = sane_truncate_line(ecbdata, line, len);
find_lno(line, ecbdata);
emit_line(ecbdata->file,
@@ -798,9 +804,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
&ecbdata->diff_words->plus);
return;
}
- if (ecbdata->diff_words->minus.text.size ||
- ecbdata->diff_words->plus.text.size)
- diff_words_show(ecbdata->diff_words);
+ diff_words_flush(ecbdata);
line++;
len--;
emit_line(ecbdata->file, plain, reset, line, len);
diff --git a/diffcore-break.c b/diffcore-break.c
index d7097bb576..3a7b60a037 100644
--- a/diffcore-break.c
+++ b/diffcore-break.c
@@ -69,7 +69,7 @@ static int should_break(struct diff_filespec *src,
return 0; /* we do not break too small filepair */
if (diffcore_count_changes(src, dst,
- NULL, NULL,
+ &src->cnt_data, &dst->cnt_data,
0,
&src_copied, &literal_added))
return 0;
@@ -204,12 +204,16 @@ void diffcore_break(int break_score)
dp->score = score;
dp->broken_pair = 1;
+ diff_free_filespec_blob(p->one);
+ diff_free_filespec_blob(p->two);
free(p); /* not diff_free_filepair(), we are
* reusing one and two here.
*/
continue;
}
}
+ diff_free_filespec_data(p->one);
+ diff_free_filespec_data(p->two);
diff_q(&outq, p);
}
free(q->queue);
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 63ac998bfa..d6fd3cacd6 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -523,10 +523,13 @@ void diffcore_rename(struct diff_options *options)
this_src.dst = i;
this_src.src = j;
record_if_better(m, &this_src);
+ /*
+ * Once we run estimate_similarity,
+ * We do not need the text anymore.
+ */
diff_free_filespec_blob(one);
+ diff_free_filespec_blob(two);
}
- /* We do not need the text anymore */
- diff_free_filespec_blob(two);
dst_cnt++;
}
diff --git a/editor.c b/editor.c
index 4d469d076b..615f5754d6 100644
--- a/editor.c
+++ b/editor.c
@@ -2,24 +2,38 @@
#include "strbuf.h"
#include "run-command.h"
-int launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
+#ifndef DEFAULT_EDITOR
+#define DEFAULT_EDITOR "vi"
+#endif
+
+const char *git_editor(void)
{
- const char *editor, *terminal;
+ const char *editor = getenv("GIT_EDITOR");
+ const char *terminal = getenv("TERM");
+ int terminal_is_dumb = !terminal || !strcmp(terminal, "dumb");
- editor = getenv("GIT_EDITOR");
if (!editor && editor_program)
editor = editor_program;
- if (!editor)
+ if (!editor && !terminal_is_dumb)
editor = getenv("VISUAL");
if (!editor)
editor = getenv("EDITOR");
- terminal = getenv("TERM");
- if (!editor && (!terminal || !strcmp(terminal, "dumb")))
- return error("Terminal is dumb but no VISUAL nor EDITOR defined.");
+ if (!editor && terminal_is_dumb)
+ return NULL;
+
+ if (!editor)
+ editor = DEFAULT_EDITOR;
+
+ return editor;
+}
+
+int launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
+{
+ const char *editor = git_editor();
if (!editor)
- editor = "vi";
+ return error("Terminal is dumb, but EDITOR unset");
if (strcmp(editor, ":")) {
size_t len = strlen(editor);
@@ -28,7 +42,7 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
const char *args[6];
struct strbuf arg0 = STRBUF_INIT;
- if (strcspn(editor, "$ \t'") != len) {
+ if (strcspn(editor, "|&;<>()$`\\\"' \t\n*?[#~=%") != len) {
/* there are specials */
strbuf_addf(&arg0, "%s \"$@\"", editor);
args[i++] = "sh";
diff --git a/environment.c b/environment.c
index 5de6837840..5171d9f9a4 100644
--- a/environment.c
+++ b/environment.c
@@ -49,6 +49,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING;
#define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS
#endif
enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
+char *notes_ref_name;
int grafts_replace_parents = 1;
/* Parallel index stat data preload? */
@@ -83,6 +84,8 @@ static void setup_git_env(void)
git_graft_file = getenv(GRAFT_ENVIRONMENT);
if (!git_graft_file)
git_graft_file = git_pathdup("info/grafts");
+ if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
+ read_replace_refs = 0;
}
int is_bare_repository(void)
diff --git a/fast-import.c b/fast-import.c
index 6faaaacb68..dd3c99d60d 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -22,8 +22,8 @@ Format of STDIN stream:
('author' sp name sp '<' email '>' sp when lf)?
'committer' sp name sp '<' email '>' sp when lf
commit_msg
- ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?
- ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)*
+ ('from' sp committish lf)?
+ ('merge' sp committish lf)*
file_change*
lf?;
commit_msg ::= data;
@@ -41,15 +41,18 @@ Format of STDIN stream:
file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
data;
+ note_obm ::= 'N' sp (hexsha1 | idnum) sp committish lf;
+ note_inm ::= 'N' sp 'inline' sp committish lf
+ data;
new_tag ::= 'tag' sp tag_str lf
- 'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf
+ 'from' sp committish lf
('tagger' sp name sp '<' email '>' sp when lf)?
tag_msg;
tag_msg ::= data;
reset_branch ::= 'reset' sp ref_str lf
- ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?
+ ('from' sp committish lf)?
lf?;
checkpoint ::= 'checkpoint' lf
@@ -88,6 +91,7 @@ Format of STDIN stream:
# stream formatting is: \, " and LF. Otherwise these values
# are UTF8.
#
+ committish ::= (ref_str | hexsha1 | sha1exp_str | idnum);
ref_str ::= ref;
sha1exp_str ::= sha1exp;
tag_str ::= tag;
@@ -2006,6 +2010,80 @@ static void file_change_cr(struct branch *b, int rename)
leaf.tree);
}
+static void note_change_n(struct branch *b)
+{
+ 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];
+ uint16_t inline_data = 0;
+
+ /* <dataref> or 'inline' */
+ if (*p == ':') {
+ char *x;
+ oe = find_mark(strtoumax(p + 1, &x, 10));
+ hashcpy(sha1, oe->sha1);
+ p = x;
+ } else if (!prefixcmp(p, "inline")) {
+ inline_data = 1;
+ p += 6;
+ } else {
+ if (get_sha1_hex(p, sha1))
+ die("Invalid SHA1: %s", command_buf.buf);
+ oe = find_object(sha1);
+ p += 40;
+ }
+ if (*p++ != ' ')
+ die("Missing space after SHA1: %s", command_buf.buf);
+
+ /* <committish> */
+ s = lookup_branch(p);
+ if (s) {
+ hashcpy(commit_sha1, s->sha1);
+ } else if (*p == ':') {
+ uintmax_t commit_mark = strtoumax(p + 1, NULL, 10);
+ struct object_entry *commit_oe = find_mark(commit_mark);
+ if (commit_oe->type != OBJ_COMMIT)
+ die("Mark :%" PRIuMAX " not a commit", commit_mark);
+ hashcpy(commit_sha1, commit_oe->sha1);
+ } else if (!get_sha1(p, commit_sha1)) {
+ unsigned long size;
+ char *buf = read_object_with_reference(commit_sha1,
+ commit_type, &size, commit_sha1);
+ if (!buf || size < 46)
+ die("Not a valid commit: %s", p);
+ free(buf);
+ } else
+ die("Invalid ref name or SHA1 expression: %s", p);
+
+ if (inline_data) {
+ static struct strbuf buf = STRBUF_INIT;
+
+ if (p != uq.buf) {
+ strbuf_addstr(&uq, p);
+ p = uq.buf;
+ }
+ read_next_command();
+ parse_data(&buf);
+ store_object(OBJ_BLOB, &buf, &last_blob, sha1, 0);
+ } else if (oe) {
+ if (oe->type != OBJ_BLOB)
+ die("Not a blob (actually a %s): %s",
+ typename(oe->type), command_buf.buf);
+ } else {
+ enum object_type type = sha1_object_info(sha1, NULL);
+ if (type < 0)
+ die("Blob not found: %s", command_buf.buf);
+ if (type != OBJ_BLOB)
+ die("Not a blob (actually a %s): %s",
+ typename(type), command_buf.buf);
+ }
+
+ tree_content_set(&b->branch_tree, sha1_to_hex(commit_sha1), sha1,
+ S_IFREG | 0644, NULL);
+}
+
static void file_change_deleteall(struct branch *b)
{
release_tree_content_recursive(b->branch_tree.tree);
@@ -2175,6 +2253,8 @@ static void parse_new_commit(void)
file_change_cr(b, 1);
else if (!prefixcmp(command_buf.buf, "C "))
file_change_cr(b, 0);
+ else if (!prefixcmp(command_buf.buf, "N "))
+ note_change_n(b);
else if (!strcmp("deleteall", command_buf.buf))
file_change_deleteall(b);
else {
@@ -2405,6 +2485,9 @@ int main(int argc, const char **argv)
git_extract_argv0_path(argv[0]);
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(fast_import_usage);
+
setup_git_directory();
git_config(git_pack_config, NULL);
if (!pack_compression_seen && core_compression_seen)
diff --git a/fetch-pack.h b/fetch-pack.h
index 8bd9c32561..fbe85ac05f 100644
--- a/fetch-pack.h
+++ b/fetch-pack.h
@@ -13,7 +13,8 @@ struct fetch_pack_args
fetch_all:1,
verbose:1,
no_progress:1,
- include_tag:1;
+ include_tag:1,
+ stateless_rpc:1;
};
struct ref *fetch_pack(struct fetch_pack_args *args,
diff --git a/fsck.h b/fsck.h
index 008456b675..1e4f527318 100644
--- a/fsck.h
+++ b/fsck.h
@@ -17,6 +17,7 @@ typedef int (*fsck_walk_func)(struct object *obj, int type, void *data);
/* callback for fsck_object, type is FSCK_ERROR or FSCK_WARN */
typedef int (*fsck_error)(struct object *obj, int type, const char *err, ...);
+__attribute__((format (printf, 3, 4)))
int fsck_error_function(struct object *obj, int type, const char *fmt, ...);
/* descend in all linked child objects
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 69aeaf03ec..f813ffdaa1 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -731,14 +731,17 @@ sub parse_diff_header {
my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
+ my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
- my $dest = $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ?
- $mode : $head;
+ my $dest =
+ $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
+ $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
+ $head;
push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
}
- return ($head, $mode);
+ return ($head, $mode, $deletion);
}
sub hunk_splittable {
@@ -987,8 +990,7 @@ sub edit_hunk_manually {
EOF
close $fh;
- my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor")
- || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
+ chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
if ($? != 0) {
@@ -1206,7 +1208,7 @@ sub patch_update_file {
my ($ix, $num);
my $path = shift;
my ($head, @hunk) = parse_diff($path);
- ($head, my $mode) = parse_diff_header($head);
+ ($head, my $mode, my $deletion) = parse_diff_header($head);
for (@{$head->{DISPLAY}}) {
print;
}
@@ -1214,6 +1216,9 @@ sub patch_update_file {
if (@{$mode->{TEXT}}) {
unshift @hunk, $mode;
}
+ if (@{$deletion->{TEXT}} && !@hunk) {
+ @hunk = ($deletion);
+ }
$num = scalar @hunk;
$ix = 0;
@@ -1267,7 +1272,9 @@ sub patch_update_file {
print;
}
print colored $prompt_color, $patch_mode_flavour{VERB},
- ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'),
+ ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' :
+ $hunk[$ix]{TYPE} eq 'deletion' ? ' deletion' :
+ ' this hunk'),
$patch_mode_flavour{TARGET},
" [y,n,q,a,d,/$other,?]? ";
my $line = prompt_single_character;
diff --git a/git-am.sh b/git-am.sh
index c132f50da5..151512afd3 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -289,7 +289,7 @@ split_patches () {
prec=4
dotest="$GIT_DIR/rebase-apply"
sign= utf8=t keep= skip= interactive= resolved= rebasing= abort=
-resolvemsg= resume= scissors=
+resolvemsg= resume= scissors= no_inbody_headers=
git_apply_opt=
committer_date_is_author_date=
ignore_date=
@@ -322,7 +322,7 @@ do
--abort)
abort=t ;;
--rebasing)
- rebasing=t threeway=t keep=t scissors=f ;;
+ rebasing=t threeway=t keep=t scissors=f no_inbody_headers=t ;;
-d|--dotest)
die "-d option is no longer supported. Do not use."
;;
@@ -448,6 +448,7 @@ else
echo "$utf8" >"$dotest/utf8"
echo "$keep" >"$dotest/keep"
echo "$scissors" >"$dotest/scissors"
+ echo "$no_inbody_headers" >"$dotest/no_inbody_headers"
echo "$GIT_QUIET" >"$dotest/quiet"
echo 1 >"$dotest/next"
if test -n "$rebasing"
@@ -495,6 +496,12 @@ t)
f)
scissors=--no-scissors ;;
esac
+if test "$(cat "$dotest/no_inbody_headers")" = t
+then
+ no_inbody_headers=--no-inbody-headers
+else
+ no_inbody_headers=
+fi
if test "$(cat "$dotest/quiet")" = t
then
GIT_QUIET=t
@@ -549,7 +556,7 @@ do
# by the user, or the user can tell us to do so by --resolved flag.
case "$resume" in
'')
- git mailinfo $keep $scissors $utf8 "$dotest/msg" "$dotest/patch" \
+ git mailinfo $keep $no_inbody_headers $scissors $utf8 "$dotest/msg" "$dotest/patch" \
<"$dotest/$msgnum" >"$dotest/info" ||
stop_here $this
@@ -649,7 +656,10 @@ do
[eE]*) git_editor "$dotest/final-commit"
action=again ;;
[vV]*) action=again
- LESS=-S ${PAGER:-less} "$dotest/patch" ;;
+ : ${GIT_PAGER=$(git var GIT_PAGER)}
+ : ${LESS=-FRSX}
+ export LESS
+ $GIT_PAGER "$dotest/patch" ;;
*) action=again ;;
esac
done
diff --git a/git-bisect.sh b/git-bisect.sh
index 8b3c5858a9..a5ea843fbf 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -300,8 +300,7 @@ bisect_visualize() {
esac
fi
- not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*")
- eval '"$@"' refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
+ eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
}
bisect_reset() {
diff --git a/git-compat-util.h b/git-compat-util.h
index ef60803384..5c596875c2 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -189,6 +189,7 @@ extern char *gitbasename(char *);
/* General helper functions */
extern NORETURN void usage(const char *err);
+extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf, 1, 2)));
extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2)));
extern NORETURN void die_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));
extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index a480d6fc70..6b8b6a4f26 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -125,6 +125,7 @@ filter_subdir=
orig_namespace=refs/original/
force=
prune_empty=
+remap_to_ancestor=
while :
do
case "$1" in
@@ -137,6 +138,11 @@ do
force=t
continue
;;
+ --remap-to-ancestor)
+ shift
+ remap_to_ancestor=t
+ continue
+ ;;
--prune-empty)
shift
prune_empty=t
@@ -182,6 +188,7 @@ do
;;
--subdirectory-filter)
filter_subdir="$OPTARG"
+ remap_to_ancestor=t
;;
--original)
orig_namespace=$(expr "$OPTARG/" : '\(.*[^/]\)/*$')/
@@ -257,15 +264,24 @@ git read-tree || die "Could not seed the index"
# map old->new commit ids for rewriting parents
mkdir ../map || die "Could not create map/ directory"
+# we need "--" only if there are no path arguments in $@
+nonrevs=$(git rev-parse --no-revs "$@") || exit
+test -z "$nonrevs" && dashdash=-- || dashdash=
+rev_args=$(git rev-parse --revs-only "$@")
+
case "$filter_subdir" in
"")
- git rev-list --reverse --topo-order --default HEAD \
- --parents --simplify-merges "$@"
+ eval set -- "$(git rev-parse --sq --no-revs "$@")"
;;
*)
- git rev-list --reverse --topo-order --default HEAD \
- --parents --simplify-merges "$@" -- "$filter_subdir"
-esac > ../revs || die "Could not get the commits"
+ eval set -- "$(git rev-parse --sq --no-revs "$@" $dashdash \
+ "$filter_subdir")"
+ ;;
+esac
+
+git rev-list --reverse --topo-order --default HEAD \
+ --parents --simplify-merges $rev_args "$@" > ../revs ||
+ die "Could not get the commits"
commits=$(wc -l <../revs | tr -d " ")
test $commits -eq 0 && die "Found nothing to rewrite"
@@ -345,19 +361,19 @@ while read commit parents; do
die "could not write rewritten commit"
done <../revs
-# In case of a subdirectory filter, it is possible that a specified head
-# is not in the set of rewritten commits, because it was pruned by the
-# revision walker. Fix it by mapping these heads to the unique nearest
-# ancestor that survived the pruning.
+# If we are filtering for paths, as in the case of a subdirectory
+# filter, it is possible that a specified head is not in the set of
+# rewritten commits, because it was pruned by the revision walker.
+# Ancestor remapping fixes this by mapping these heads to the unique
+# nearest ancestor that survived the pruning.
-if test "$filter_subdir"
+if test "$remap_to_ancestor" = t
then
while read ref
do
sha1=$(git rev-parse "$ref"^0)
test -f "$workdir"/../map/$sha1 && continue
- ancestor=$(git rev-list --simplify-merges -1 \
- $ref -- "$filter_subdir")
+ ancestor=$(git rev-list --simplify-merges -1 "$ref" "$@")
test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1
done < "$tempdir"/heads
fi
diff --git a/git-instaweb.sh b/git-instaweb.sh
index 622a5f0eb2..ffc2ab62bc 100755
--- a/git-instaweb.sh
+++ b/git-instaweb.sh
@@ -73,6 +73,11 @@ resolve_full_httpd () {
}
start_httpd () {
+ if test -f "$fqgitdir/pid"; then
+ say "Instance already running. Restarting..."
+ stop_httpd
+ fi
+
# here $httpd should have a meaningful value
resolve_full_httpd
diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh
index 1dadbb4966..825c52c243 100755
--- a/git-merge-octopus.sh
+++ b/git-merge-octopus.sh
@@ -81,7 +81,7 @@ do
# tree as the intermediate result of the merge.
# We still need to count this as part of the parent set.
- echo "Fast forwarding to: $SHA1"
+ echo "Fast-forwarding to: $SHA1"
git read-tree -u -m $head $SHA1 || exit
MRC=$SHA1 MRT=$(git write-tree)
continue
diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh
index 9c2c1b7202..d067894bf4 100755
--- a/git-merge-one-file.sh
+++ b/git-merge-one-file.sh
@@ -16,6 +16,18 @@
# been handled already by git read-tree, but that one doesn't
# do any merges that might change the tree layout.
+USAGE='<orig blob> <our blob> <their blob> <path>'
+USAGE="$USAGE <orig mode> <our mode> <their mode>"
+LONG_USAGE="Usage: git merge-one-file $USAGE
+
+Blob ids and modes should be empty for missing files."
+
+if ! test "$#" -eq 7
+then
+ echo "$LONG_USAGE"
+ exit 1
+fi
+
case "${1:-.}${2:-.}${3:-.}" in
#
# Deleted in both or deleted in one and unchanged in the other
diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh
index bfb01f7842..f7c571e73c 100644
--- a/git-mergetool--lib.sh
+++ b/git-mergetool--lib.sh
@@ -46,7 +46,7 @@ check_unchanged () {
valid_tool () {
case "$1" in
kdiff3 | tkdiff | xxdiff | meld | opendiff | \
- emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis)
+ emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis | p4merge)
;; # happy
tortoisemerge)
if ! merge_mode; then
@@ -130,6 +130,19 @@ run_merge_tool () {
"$merge_tool_path" "$LOCAL" "$REMOTE"
fi
;;
+ p4merge)
+ if merge_mode; then
+ touch "$BACKUP"
+ if $base_present; then
+ "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
+ else
+ "$merge_tool_path" "$LOCAL" "$LOCAL" "$REMOTE" "$MERGED"
+ fi
+ check_unchanged
+ else
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
+ fi
+ ;;
meld)
if merge_mode; then
touch "$BACKUP"
@@ -323,7 +336,7 @@ guess_merge_tool () {
else
tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
fi
- tools="$tools gvimdiff diffuse ecmerge araxis"
+ tools="$tools gvimdiff diffuse ecmerge p4merge araxis"
fi
if echo "${VISUAL:-$EDITOR}" | grep emacs > /dev/null 2>&1; then
# $EDITOR is emacs so add emerge as a candidate
diff --git a/git-notes.sh b/git-notes.sh
new file mode 100755
index 0000000000..e642e47d9f
--- /dev/null
+++ b/git-notes.sh
@@ -0,0 +1,121 @@
+#!/bin/sh
+
+USAGE="(edit [-F <file> | -m <msg>] | show) [commit]"
+. git-sh-setup
+
+test -z "$1" && usage
+ACTION="$1"; shift
+
+test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="$(git config core.notesref)"
+test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="refs/notes/commits"
+
+MESSAGE=
+while test $# != 0
+do
+ case "$1" in
+ -m)
+ test "$ACTION" = "edit" || usage
+ shift
+ if test "$#" = "0"; then
+ die "error: option -m needs an argument"
+ else
+ if [ -z "$MESSAGE" ]; then
+ MESSAGE="$1"
+ else
+ MESSAGE="$MESSAGE
+
+$1"
+ fi
+ shift
+ fi
+ ;;
+ -F)
+ test "$ACTION" = "edit" || usage
+ shift
+ if test "$#" = "0"; then
+ die "error: option -F needs an argument"
+ else
+ if [ -z "$MESSAGE" ]; then
+ MESSAGE="$(cat "$1")"
+ else
+ MESSAGE="$MESSAGE
+
+$(cat "$1")"
+ fi
+ shift
+ fi
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+done
+
+COMMIT=$(git rev-parse --verify --default HEAD "$@") ||
+die "Invalid commit: $@"
+
+case "$ACTION" in
+edit)
+ if [ "${GIT_NOTES_REF#refs/notes/}" = "$GIT_NOTES_REF" ]; then
+ die "Refusing to edit notes in $GIT_NOTES_REF (outside of refs/notes/)"
+ fi
+
+ MSG_FILE="$GIT_DIR/new-notes-$COMMIT"
+ GIT_INDEX_FILE="$MSG_FILE.idx"
+ export GIT_INDEX_FILE
+
+ trap '
+ test -f "$MSG_FILE" && rm "$MSG_FILE"
+ test -f "$GIT_INDEX_FILE" && rm "$GIT_INDEX_FILE"
+ ' 0
+
+ CURRENT_HEAD=$(git show-ref "$GIT_NOTES_REF" | cut -f 1 -d ' ')
+ if [ -z "$CURRENT_HEAD" ]; then
+ PARENT=
+ else
+ PARENT="-p $CURRENT_HEAD"
+ git read-tree "$GIT_NOTES_REF" || die "Could not read index"
+ fi
+
+ if [ -z "$MESSAGE" ]; then
+ GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MSG_FILE"
+ if [ ! -z "$CURRENT_HEAD" ]; then
+ git cat-file blob :$COMMIT >> "$MSG_FILE" 2> /dev/null
+ fi
+ core_editor="$(git config core.editor)"
+ ${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} "$MSG_FILE"
+ else
+ echo "$MESSAGE" > "$MSG_FILE"
+ fi
+
+ grep -v ^# < "$MSG_FILE" | git stripspace > "$MSG_FILE".processed
+ mv "$MSG_FILE".processed "$MSG_FILE"
+ if [ -s "$MSG_FILE" ]; then
+ BLOB=$(git hash-object -w "$MSG_FILE") ||
+ die "Could not write into object database"
+ git update-index --add --cacheinfo 0644 $BLOB $COMMIT ||
+ die "Could not write index"
+ else
+ test -z "$CURRENT_HEAD" &&
+ die "Will not initialise with empty tree"
+ git update-index --force-remove $COMMIT ||
+ die "Could not update index"
+ fi
+
+ TREE=$(git write-tree) || die "Could not write tree"
+ NEW_HEAD=$(echo Annotate $COMMIT | git commit-tree $TREE $PARENT) ||
+ die "Could not annotate"
+ git update-ref -m "Annotate $COMMIT" \
+ "$GIT_NOTES_REF" $NEW_HEAD $CURRENT_HEAD
+;;
+show)
+ git rev-parse -q --verify "$GIT_NOTES_REF":$COMMIT > /dev/null ||
+ die "No note for commit $COMMIT."
+ git show "$GIT_NOTES_REF":$COMMIT
+;;
+*)
+ usage
+esac
diff --git a/git-pull.sh b/git-pull.sh
index fc78592ae0..bfeb4a0ff6 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -16,7 +16,8 @@ cd_to_toplevel
test -z "$(git ls-files -u)" ||
die "You are in the middle of a conflicted merge."
-strategy_args= diffstat= no_commit= squash= no_ff= log_arg= verbosity=
+strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
+log_arg= verbosity=
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)
@@ -45,6 +46,8 @@ do
no_ff=--ff ;;
--no-ff)
no_ff=--no-ff ;;
+ --ff-only)
+ ff_only=--ff-only ;;
-s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
--strateg=*|--strategy=*|\
-s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
@@ -171,7 +174,7 @@ then
# First update the working tree to match $curr_head.
echo >&2 "Warning: fetch updated the current branch head."
- echo >&2 "Warning: fast forwarding your working tree from"
+ echo >&2 "Warning: fast-forwarding your working tree from"
echo >&2 "Warning: commit $orig_head."
git update-index -q --refresh
git read-tree -u -m "$orig_head" "$curr_head" ||
@@ -215,5 +218,5 @@ 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 $log_arg $strategy_args \
+exec git-merge $diffstat $no_commit $squash $no_ff $ff_only $log_arg $strategy_args \
"$merge_name" HEAD $merge_head $verbosity
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 53ad248ee5..27daaa9ded 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -168,7 +168,7 @@ pick_one () {
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 $sha1
else
output git cherry-pick "$@"
fi
@@ -248,9 +248,9 @@ pick_one_preserving_merges () {
done
case $fast_forward in
t)
- output warn "Fast forward to $sha1"
+ output warn "Fast-forward to $sha1"
output git reset --hard $sha1 ||
- die "Cannot fast forward to $sha1"
+ die "Cannot fast-forward to $sha1"
;;
f)
first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
diff --git a/git-rebase.sh b/git-rebase.sh
index 6ec155cf03..6830e1627d 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -496,7 +496,7 @@ then
fi
# If the $onto is a proper descendant of the tip of the branch, then
-# we just fast forwarded.
+# we just fast-forwarded.
if test "$mb" = "$branch"
then
say "Fast-forwarded $branch_name to $onto_name."
diff --git a/git-send-email.perl b/git-send-email.perl
index a0279de687..4f5da4ecf2 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -162,7 +162,8 @@ my $compose_filename;
# Handle interactive edition of files.
my $multiedit;
-my $editor = $ENV{GIT_EDITOR} || Git::config(@repo, "core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
+my $editor = Git::command_oneline('var', 'GIT_EDITOR');
+
sub do_edit {
if (defined($multiedit) && !$multiedit) {
map {
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index c41c2f7439..99cceeb858 100755
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -99,19 +99,12 @@ set_reflog_action() {
}
git_editor() {
- : "${GIT_EDITOR:=$(git config core.editor)}"
- : "${GIT_EDITOR:=${VISUAL:-${EDITOR}}}"
- case "$GIT_EDITOR,$TERM" in
- ,dumb)
- echo >&2 "No editor specified in GIT_EDITOR, core.editor, VISUAL,"
- echo >&2 "or EDITOR. Tried to fall back to vi but terminal is dumb."
- echo >&2 "Please set one of these variables to an appropriate"
- echo >&2 "editor or run $0 with options that will not cause an"
- echo >&2 "editor to be invoked (e.g., -m or -F for git-commit)."
- exit 1
- ;;
- esac
- eval "${GIT_EDITOR:=vi}" '"$@"'
+ if test -z "${GIT_EDITOR:+set}"
+ then
+ GIT_EDITOR="$(git var GIT_EDITOR)" || return $?
+ fi
+
+ eval "$GIT_EDITOR" '"$@"'
}
is_bare_repository () {
diff --git a/git-submodule.sh b/git-submodule.sh
index 0462e529d9..850d4235a0 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -5,7 +5,7 @@
# Copyright (c) 2007 Lars Hjemli
dashless=$(basename "$0" | sed -e 's/-/ /')
-USAGE="[--quiet] add [-b branch] [--reference <repository>] [--] <repository> <path>
+USAGE="[--quiet] add [-b branch] [--reference <repository>] [--] <repository> [<path>]
or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
or: $dashless [--quiet] init [--] [<path>...]
or: $dashless [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
@@ -160,6 +160,11 @@ cmd_add()
repo=$1
path=$2
+ if test -z "$path"; then
+ path=$(echo "$repo" |
+ sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
+ fi
+
if test -z "$repo" -o -z "$path"; then
usage
fi
diff --git a/git-svn.perl b/git-svn.perl
index 6a3b501d24..957d44e630 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -168,6 +168,9 @@ my %cmd = (
'Create a .gitignore per svn:ignore',
{ 'revision|r=i' => \$_revision
} ],
+ 'mkdirs' => [ \&cmd_mkdirs ,
+ "recreate empty directories after a checkout",
+ { 'revision|r=i' => \$_revision } ],
'propget' => [ \&cmd_propget,
'Print the value of a property on a file or directory',
{ 'revision|r=i' => \$_revision } ],
@@ -274,7 +277,7 @@ unless ($cmd && $cmd =~ /(?:clone|init|multi-init)$/) {
my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
-read_repo_config(\%opts);
+read_git_config(\%opts);
if ($cmd && ($cmd eq 'log' || $cmd eq 'blame')) {
Getopt::Long::Configure('pass_through');
}
@@ -425,6 +428,7 @@ sub cmd_fetch {
if (@_ > 1) {
die "Usage: $0 fetch [--all] [--parent] [svn-remote]\n";
}
+ $Git::SVN::no_reuse_existing = undef;
if ($_fetch_parent) {
my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
unless ($gs) {
@@ -769,6 +773,7 @@ sub cmd_rebase {
$_fetch_all ? $gs->fetch_all : $gs->fetch;
}
command_noisy(rebase_cmd(), $gs->refname);
+ $gs->mkemptydirs;
}
sub cmd_show_ignore {
@@ -830,6 +835,12 @@ sub cmd_create_ignore {
});
}
+sub cmd_mkdirs {
+ my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
+ $gs ||= Git::SVN->new;
+ $gs->mkemptydirs($_revision);
+}
+
sub canonicalize_path {
my ($path) = @_;
my $dot_slash_added = 0;
@@ -946,6 +957,7 @@ sub cmd_multi_init {
}
sub cmd_multi_fetch {
+ $Git::SVN::no_reuse_existing = undef;
my $remotes = Git::SVN::read_all_remotes();
foreach my $repo_id (sort keys %$remotes) {
if ($remotes->{$repo_id}->{url}) {
@@ -1196,6 +1208,7 @@ sub post_fetch_checkout {
command_noisy(qw/read-tree -m -u -v HEAD HEAD/);
print STDERR "Checked out HEAD:\n ",
$gs->full_url, " r", $gs->last_rev, "\n";
+ $gs->mkemptydirs($gs->last_rev);
}
sub complete_svn_url {
@@ -1321,9 +1334,8 @@ sub get_commit_entry {
close $log_fh or croak $!;
if ($_edit || ($type eq 'tree')) {
- my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi';
- # TODO: strip out spaces, comments, like git-commit.sh
- system($editor, $commit_editmsg);
+ chomp(my $editor = command_oneline(qw(var GIT_EDITOR)));
+ system('sh', '-c', $editor.' "$@"', $editor, $commit_editmsg);
}
rename $commit_editmsg, $commit_msg or croak $!;
{
@@ -1390,8 +1402,7 @@ sub load_authors {
}
# convert GetOpt::Long specs for use by git-config
-sub read_repo_config {
- return unless -d $ENV{GIT_DIR};
+sub read_git_config {
my $opts = shift;
my @config_only;
foreach my $o (keys %$opts) {
@@ -2725,6 +2736,37 @@ sub do_fetch {
$self->make_log_entry($rev, \@parents, $ed);
}
+sub mkemptydirs {
+ my ($self, $r) = @_;
+ my %empty_dirs = ();
+
+ open my $fh, '<', "$self->{dir}/unhandled.log" or return;
+ binmode $fh or croak "binmode: $!";
+ while (<$fh>) {
+ if (defined $r && /^r(\d+)$/) {
+ last if $1 > $r;
+ } elsif (/^ \+empty_dir: (.+)$/) {
+ $empty_dirs{$1} = 1;
+ } elsif (/^ \-empty_dir: (.+)$/) {
+ delete $empty_dirs{$1};
+ }
+ }
+ close $fh;
+
+ my $strip = qr/\A\Q$self->{path}\E(?:\/|$)/;
+ foreach my $d (sort keys %empty_dirs) {
+ $d = uri_decode($d);
+ $d =~ s/$strip//;
+ next if -d $d;
+ if (-e _) {
+ warn "$d exists but is not a directory\n";
+ } else {
+ print "creating empty directory: $d\n";
+ mkpath([$d]);
+ }
+ }
+}
+
sub get_untracked {
my ($self, $ed) = @_;
my @out;
@@ -2950,8 +2992,11 @@ sub find_extra_svn_parents {
my $bottom_commit =
$gs->rev_map_get($bottom, $self->ra_uuid) ||
$gs->rev_map_get($bottom+1, $self->ra_uuid);
- my $top_commit =
- $gs->rev_map_get($top, $self->ra_uuid);
+ my $top_commit;
+ for (; !$top_commit && $top >= $bottom; --$top) {
+ $top_commit =
+ $gs->rev_map_get($top, $self->ra_uuid);
+ }
unless ($top_commit and $bottom_commit) {
warn "W:unknown path/rev in svn:mergeinfo "
@@ -3554,6 +3599,12 @@ sub uri_encode {
$f
}
+sub uri_decode {
+ my ($f) = @_;
+ $f =~ s#%([0-9a-fA-F]{2})#chr(hex($1))#eg;
+ $f
+}
+
sub remove_username {
$_[0] =~ s{^([^:]*://)[^@]+@}{$1};
}
@@ -5172,10 +5223,8 @@ sub git_svn_log_cmd {
# adapted from pager.c
sub config_pager {
- $pager ||= $ENV{GIT_PAGER} || $ENV{PAGER};
- if (!defined $pager) {
- $pager = 'less';
- } elsif (length $pager == 0 || $pager eq 'cat') {
+ chomp(my $pager = command_oneline(qw(var GIT_PAGER)));
+ if ($pager eq 'cat') {
$pager = undef;
}
$ENV{GIT_PAGER_IN_USE} = defined($pager);
diff --git a/git.c b/git.c
index bd2c5fe77b..11544cdb40 100644
--- a/git.c
+++ b/git.c
@@ -89,6 +89,9 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
*envchanged = 1;
} else if (!strcmp(cmd, "--no-replace-objects")) {
read_replace_refs = 0;
+ setenv(NO_REPLACE_OBJECTS_ENVIRONMENT, "1", 1);
+ if (envchanged)
+ *envchanged = 1;
} else if (!strcmp(cmd, "--git-dir")) {
if (*argc < 2) {
fprintf(stderr, "No directory given for --git-dir.\n" );
@@ -229,21 +232,24 @@ struct cmd_struct {
static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
{
- int status;
+ int status, help;
struct stat st;
const char *prefix;
prefix = NULL;
- if (p->option & RUN_SETUP)
- prefix = setup_git_directory();
-
- if (use_pager == -1 && p->option & RUN_SETUP)
- use_pager = check_pager_config(p->cmd);
- if (use_pager == -1 && p->option & USE_PAGER)
- use_pager = 1;
+ help = argc == 2 && !strcmp(argv[1], "-h");
+ if (!help) {
+ if (p->option & RUN_SETUP)
+ prefix = setup_git_directory();
+
+ if (use_pager == -1 && p->option & RUN_SETUP)
+ use_pager = check_pager_config(p->cmd);
+ if (use_pager == -1 && p->option & USE_PAGER)
+ use_pager = 1;
+ }
commit_pager_choice();
- if (p->option & NEED_WORK_TREE)
+ if (!help && p->option & NEED_WORK_TREE)
setup_work_tree();
trace_argv_printf(argv, "trace: built-in: git");
@@ -304,7 +310,6 @@ static void handle_internal_command(int argc, const char **argv)
{ "fast-export", cmd_fast_export, RUN_SETUP },
{ "fetch", cmd_fetch, RUN_SETUP },
{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
- { "fetch--tool", cmd_fetch__tool, RUN_SETUP },
{ "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
{ "for-each-ref", cmd_for_each_ref, RUN_SETUP },
{ "format-patch", cmd_format_patch, RUN_SETUP },
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index e4cbfc35a7..f94536c680 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -2020,16 +2020,27 @@ sub quote_command {
# get HEAD ref of given project as hash
sub git_get_head_hash {
- my $project = shift;
+ return git_get_full_hash(shift, 'HEAD');
+}
+
+sub git_get_full_hash {
+ return git_get_hash(@_);
+}
+
+sub git_get_short_hash {
+ return git_get_hash(@_, '--short=7');
+}
+
+sub git_get_hash {
+ my ($project, $hash, @options) = @_;
my $o_git_dir = $git_dir;
my $retval = undef;
$git_dir = "$projectroot/$project";
- if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") {
- my $head = <$fd>;
+ if (open my $fd, '-|', git_cmd(), 'rev-parse',
+ '--verify', '-q', @options, $hash) {
+ $retval = <$fd>;
+ chomp $retval if defined $retval;
close $fd;
- if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) {
- $retval = $1;
- }
}
if (defined $o_git_dir) {
$git_dir = $o_git_dir;
@@ -3363,22 +3374,18 @@ sub git_print_page_nav {
}
sub format_paging_nav {
- my ($action, $hash, $head, $page, $has_next_link) = @_;
+ my ($action, $page, $has_next_link) = @_;
my $paging_nav;
- if ($hash ne $head || $page) {
- $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD");
- } else {
- $paging_nav .= "HEAD";
- }
-
if ($page > 0) {
- $paging_nav .= " &sdot; " .
+ $paging_nav .=
+ $cgi->a({-href => href(-replay=>1, page=>undef)}, "first") .
+ " &sdot; " .
$cgi->a({-href => href(-replay=>1, page=>$page-1),
-accesskey => "p", -title => "Alt-p"}, "prev");
} else {
- $paging_nav .= " &sdot; prev";
+ $paging_nav .= "first &sdot; prev";
}
if ($has_next_link) {
@@ -4361,6 +4368,46 @@ sub git_project_list_body {
print "</table>\n";
}
+sub git_log_body {
+ # uses global variable $project
+ my ($commitlist, $from, $to, $refs, $extra) = @_;
+
+ $from = 0 unless defined $from;
+ $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
+
+ for (my $i = 0; $i <= $to; $i++) {
+ my %co = %{$commitlist->[$i]};
+ next if !%co;
+ my $commit = $co{'id'};
+ my $ref = format_ref_marker($refs, $commit);
+ my %ad = parse_date($co{'author_epoch'});
+ git_print_header_div('commit',
+ "<span class=\"age\">$co{'age_string'}</span>" .
+ esc_html($co{'title'}) . $ref,
+ $commit);
+ print "<div class=\"title_text\">\n" .
+ "<div class=\"log_link\">\n" .
+ $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
+ " | " .
+ $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
+ " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
+ "<br/>\n" .
+ "</div>\n";
+ git_print_authorship(\%co, -tag => 'span');
+ print "<br/>\n</div>\n";
+
+ print "<div class=\"log_body\">\n";
+ git_print_log($co{'comment'}, -final_empty_line=> 1);
+ print "</div>\n";
+ }
+ if ($extra) {
+ print "<div class=\"page_nav\">\n";
+ print "$extra\n";
+ print "</div>\n";
+ }
+}
+
sub git_shortlog_body {
# uses global variable $project
my ($commitlist, $from, $to, $refs, $extra) = @_;
@@ -4407,7 +4454,8 @@ sub git_shortlog_body {
sub git_history_body {
# Warning: assumes constant type (blob or tree) during history
- my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
+ my ($commitlist, $from, $to, $refs, $extra,
+ $file_name, $file_hash, $ftype) = @_;
$from = 0 unless defined $from;
$to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
@@ -4441,7 +4489,7 @@ sub git_history_body {
$cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
if ($ftype eq 'blob') {
- my $blob_current = git_get_hash_by_path($hash_base, $file_name);
+ my $blob_current = $file_hash;
my $blob_parent = git_get_hash_by_path($commit, $file_name);
if (defined $blob_current && defined $blob_parent &&
$blob_current ne $blob_parent) {
@@ -5127,7 +5175,8 @@ sub git_blob {
chomp $line;
$nr++;
$line = untabify($line);
- printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
+ printf "<div class=\"pre\"><a id=\"l%i\" href=\"" . href(-replay => 1)
+ . "#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
$nr, $nr, $nr, esc_html($line, -nbsp=>1);
}
}
@@ -5247,6 +5296,43 @@ sub git_tree {
git_footer_html();
}
+sub snapshot_name {
+ my ($project, $hash) = @_;
+
+ # path/to/project.git -> project
+ # path/to/project/.git -> project
+ my $name = to_utf8($project);
+ $name =~ s,([^/])/*\.git$,$1,;
+ $name = basename($name);
+ # sanitize name
+ $name =~ s/[[:cntrl:]]/?/g;
+
+ my $ver = $hash;
+ if ($hash =~ /^[0-9a-fA-F]+$/) {
+ # shorten SHA-1 hash
+ my $full_hash = git_get_full_hash($project, $hash);
+ if ($full_hash =~ /^$hash/ && length($hash) > 7) {
+ $ver = git_get_short_hash($project, $hash);
+ }
+ } elsif ($hash =~ m!^refs/tags/(.*)$!) {
+ # tags don't need shortened SHA-1 hash
+ $ver = $1;
+ } else {
+ # branches and other need shortened SHA-1 hash
+ if ($hash =~ m!^refs/(?:heads|remotes)/(.*)$!) {
+ $ver = $1;
+ }
+ $ver .= '-' . git_get_short_hash($project, $hash);
+ }
+ # in case of hierarchical branch names
+ $ver =~ s!/!.!g;
+
+ # name = project-version_string
+ $name = "$name-$ver";
+
+ return wantarray ? ($name, $name) : $name;
+}
+
sub git_snapshot {
my $format = $input_params{'snapshot_format'};
if (!@snapshot_fmts) {
@@ -5264,28 +5350,27 @@ sub git_snapshot {
die_error(403, "Unsupported snapshot format");
}
- if (!defined $hash) {
- $hash = git_get_head_hash($project);
+ my $type = git_get_type("$hash^{}");
+ if (!$type) {
+ die_error(404, 'Object does not exist');
+ } elsif ($type eq 'blob') {
+ die_error(400, 'Object is not a tree-ish');
}
- my $name = $project;
- $name =~ s,([^/])/*\.git$,$1,;
- $name = basename($name);
- my $filename = to_utf8($name);
- $name =~ s/\047/\047\\\047\047/g;
- my $cmd;
- $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}";
- $cmd = quote_command(
+ my ($name, $prefix) = snapshot_name($project, $hash);
+ my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
+ my $cmd = quote_command(
git_cmd(), 'archive',
"--format=$known_snapshot_formats{$format}{'format'}",
- "--prefix=$name/", $hash);
+ "--prefix=$prefix/", $hash);
if (exists $known_snapshot_formats{$format}{'compressor'}) {
$cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}});
}
+ $filename =~ s/(["\\])/\\$1/g;
print $cgi->header(
-type => $known_snapshot_formats{$format}{'type'},
- -content_disposition => 'inline; filename="' . "$filename" . '"',
+ -content_disposition => 'inline; filename="' . $filename . '"',
-status => '200 OK');
open my $fd, "-|", $cmd
@@ -5296,22 +5381,57 @@ sub git_snapshot {
close $fd;
}
-sub git_log {
+sub git_log_generic {
+ my ($fmt_name, $body_subr, $base, $parent, $file_name, $file_hash) = @_;
+
my $head = git_get_head_hash($project);
- if (!defined $hash) {
- $hash = $head;
+ if (!defined $base) {
+ $base = $head;
}
if (!defined $page) {
$page = 0;
}
my $refs = git_get_references();
- my @commitlist = parse_commits($hash, 101, (100 * $page));
+ my $commit_hash = $base;
+ if (defined $parent) {
+ $commit_hash = "$parent..$base";
+ }
+ my @commitlist =
+ parse_commits($commit_hash, 101, (100 * $page),
+ defined $file_name ? ($file_name, "--full-history") : ());
- my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100);
+ my $ftype;
+ if (!defined $file_hash && defined $file_name) {
+ # some commits could have deleted file in question,
+ # and not have it in tree, but one of them has to have it
+ for (my $i = 0; $i < @commitlist; $i++) {
+ $file_hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
+ last if defined $file_hash;
+ }
+ }
+ if (defined $file_hash) {
+ $ftype = git_get_type($file_hash);
+ }
+ if (defined $file_name && !defined $ftype) {
+ die_error(500, "Unknown type of object");
+ }
+ my %co;
+ if (defined $file_name) {
+ %co = parse_commit($base)
+ or die_error(404, "Unknown commit object");
+ }
- my ($patch_max) = gitweb_get_feature('patches');
- if ($patch_max) {
+
+ my $paging_nav = format_paging_nav($fmt_name, $page, $#commitlist >= 100);
+ my $next_link = '';
+ if ($#commitlist >= 100) {
+ $next_link =
+ $cgi->a({-href => href(-replay=>1, page=>$page+1),
+ -accesskey => "n", -title => "Alt-n"}, "next");
+ }
+ my $patch_max = gitweb_get_feature('patches');
+ if ($patch_max && !defined $file_name) {
if ($patch_max < 0 || @commitlist <= $patch_max) {
$paging_nav .= " &sdot; " .
$cgi->a({-href => href(action=>"patches", -replay=>1)},
@@ -5320,50 +5440,26 @@ sub git_log {
}
git_header_html();
- git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
-
- if (!@commitlist) {
- my %co = parse_commit($hash);
-
- git_print_header_div('summary', $project);
- print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
+ git_print_page_nav($fmt_name,'', $hash,$hash,$hash, $paging_nav);
+ if (defined $file_name) {
+ git_print_header_div('commit', esc_html($co{'title'}), $base);
+ } else {
+ git_print_header_div('summary', $project)
}
- my $to = ($#commitlist >= 99) ? (99) : ($#commitlist);
- for (my $i = 0; $i <= $to; $i++) {
- my %co = %{$commitlist[$i]};
- next if !%co;
- my $commit = $co{'id'};
- my $ref = format_ref_marker($refs, $commit);
- my %ad = parse_date($co{'author_epoch'});
- git_print_header_div('commit',
- "<span class=\"age\">$co{'age_string'}</span>" .
- esc_html($co{'title'}) . $ref,
- $commit);
- print "<div class=\"title_text\">\n" .
- "<div class=\"log_link\">\n" .
- $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
- " | " .
- $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
- " | " .
- $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
- "<br/>\n" .
- "</div>\n";
- git_print_authorship(\%co, -tag => 'span');
- print "<br/>\n</div>\n";
+ git_print_page_path($file_name, $ftype, $hash_base)
+ if (defined $file_name);
+
+ $body_subr->(\@commitlist, 0, 99, $refs, $next_link,
+ $file_name, $file_hash, $ftype);
- print "<div class=\"log_body\">\n";
- git_print_log($co{'comment'}, -final_empty_line=> 1);
- print "</div>\n";
- }
- if ($#commitlist >= 100) {
- print "<div class=\"page_nav\">\n";
- print $cgi->a({-href => href(-replay=>1, page=>$page+1),
- -accesskey => "n", -title => "Alt-n"}, "next");
- print "</div>\n";
- }
git_footer_html();
}
+sub git_log {
+ git_log_generic('log', \&git_log_body,
+ $hash, $hash_parent);
+}
+
sub git_commit {
$hash ||= $hash_base || "HEAD";
my %co = parse_commit($hash)
@@ -5900,70 +5996,9 @@ sub git_patches {
}
sub git_history {
- if (!defined $hash_base) {
- $hash_base = git_get_head_hash($project);
- }
- if (!defined $page) {
- $page = 0;
- }
- my $ftype;
- my %co = parse_commit($hash_base)
- or die_error(404, "Unknown commit object");
-
- my $refs = git_get_references();
- my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-
- my @commitlist = parse_commits($hash_base, 101, (100 * $page),
- $file_name, "--full-history")
- or die_error(404, "No such file or directory on given branch");
-
- if (!defined $hash && defined $file_name) {
- # some commits could have deleted file in question,
- # and not have it in tree, but one of them has to have it
- for (my $i = 0; $i <= @commitlist; $i++) {
- $hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
- last if defined $hash;
- }
- }
- if (defined $hash) {
- $ftype = git_get_type($hash);
- }
- if (!defined $ftype) {
- die_error(500, "Unknown type of object");
- }
-
- my $paging_nav = '';
- if ($page > 0) {
- $paging_nav .=
- $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
- file_name=>$file_name)},
- "first");
- $paging_nav .= " &sdot; " .
- $cgi->a({-href => href(-replay=>1, page=>$page-1),
- -accesskey => "p", -title => "Alt-p"}, "prev");
- } else {
- $paging_nav .= "first";
- $paging_nav .= " &sdot; prev";
- }
- my $next_link = '';
- if ($#commitlist >= 100) {
- $next_link =
- $cgi->a({-href => href(-replay=>1, page=>$page+1),
- -accesskey => "n", -title => "Alt-n"}, "next");
- $paging_nav .= " &sdot; $next_link";
- } else {
- $paging_nav .= " &sdot; next";
- }
-
- git_header_html();
- git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav);
- git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
- git_print_page_path($file_name, $ftype, $hash_base);
-
- git_history_body(\@commitlist, 0, 99,
- $refs, $hash_base, $ftype, $next_link);
-
- git_footer_html();
+ git_log_generic('history', \&git_history_body,
+ $hash_base, $hash_parent_base,
+ $file_name, $hash);
}
sub git_search {
@@ -6227,44 +6262,8 @@ EOT
}
sub git_shortlog {
- my $head = git_get_head_hash($project);
- if (!defined $hash) {
- $hash = $head;
- }
- if (!defined $page) {
- $page = 0;
- }
- my $refs = git_get_references();
-
- my $commit_hash = $hash;
- if (defined $hash_parent) {
- $commit_hash = "$hash_parent..$hash";
- }
- my @commitlist = parse_commits($commit_hash, 101, (100 * $page));
-
- my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#commitlist >= 100);
- my $next_link = '';
- if ($#commitlist >= 100) {
- $next_link =
- $cgi->a({-href => href(-replay=>1, page=>$page+1),
- -accesskey => "n", -title => "Alt-n"}, "next");
- }
- my $patch_max = gitweb_check_feature('patches');
- if ($patch_max) {
- if ($patch_max < 0 || @commitlist <= $patch_max) {
- $paging_nav .= " &sdot; " .
- $cgi->a({-href => href(action=>"patches", -replay=>1)},
- "patches");
- }
- }
-
- git_header_html();
- git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
- git_print_header_div('summary', $project);
-
- git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link);
-
- git_footer_html();
+ git_log_generic('shortlog', \&git_shortlog_body,
+ $hash, $hash_parent);
}
## ......................................................................
diff --git a/graph.h b/graph.h
index bc30d687c0..b82ae87a49 100644
--- a/graph.h
+++ b/graph.h
@@ -6,7 +6,6 @@ struct git_graph;
/*
* Create a new struct git_graph.
- * The graph should be freed with graph_release() when no longer needed.
*/
struct git_graph *graph_init(struct rev_info *opt);
diff --git a/grep.c b/grep.c
index 5d162dae6e..bdadf2c0cc 100644
--- a/grep.c
+++ b/grep.c
@@ -41,6 +41,7 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
int err;
p->word_regexp = opt->word_regexp;
+ p->ignore_case = opt->ignore_case;
if (opt->fixed || is_fixed(p->pattern))
p->fixed = 1;
@@ -262,9 +263,15 @@ static void show_name(struct grep_opt *opt, const char *name)
printf("%s%c", name, opt->null_following_name ? '\0' : '\n');
}
-static int fixmatch(const char *pattern, char *line, regmatch_t *match)
+
+static int fixmatch(const char *pattern, char *line, int ignore_case, regmatch_t *match)
{
- char *hit = strstr(line, pattern);
+ char *hit;
+ if (ignore_case)
+ hit = strcasestr(line, pattern);
+ else
+ hit = strstr(line, pattern);
+
if (!hit) {
match->rm_so = match->rm_eo = -1;
return REG_NOMATCH;
@@ -326,7 +333,7 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
again:
if (p->fixed)
- hit = !fixmatch(p->pattern, bol, pmatch);
+ hit = !fixmatch(p->pattern, bol, p->ignore_case, pmatch);
else
hit = !regexec(&p->regexp, bol, 1, pmatch, eflags);
diff --git a/grep.h b/grep.h
index f6eecc62c0..75370f60d7 100644
--- a/grep.h
+++ b/grep.h
@@ -32,6 +32,7 @@ struct grep_pat {
enum grep_header_field field;
regex_t regexp;
unsigned fixed:1;
+ unsigned ignore_case:1;
unsigned word_regexp:1;
};
@@ -64,6 +65,7 @@ struct grep_opt {
regex_t regexp;
int linenum;
int invert;
+ int ignore_case;
int status_only;
int name_only;
int unmatch_name_only;
diff --git a/http-backend.c b/http-backend.c
new file mode 100644
index 0000000000..f729488fc5
--- /dev/null
+++ b/http-backend.c
@@ -0,0 +1,655 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "object.h"
+#include "tag.h"
+#include "exec_cmd.h"
+#include "run-command.h"
+#include "string-list.h"
+
+static const char content_type[] = "Content-Type";
+static const char content_length[] = "Content-Length";
+static const char last_modified[] = "Last-Modified";
+static int getanyfile = 1;
+
+static struct string_list *query_params;
+
+struct rpc_service {
+ const char *name;
+ const char *config_name;
+ signed enabled : 2;
+};
+
+static struct rpc_service rpc_service[] = {
+ { "upload-pack", "uploadpack", 1 },
+ { "receive-pack", "receivepack", -1 },
+};
+
+static int decode_char(const char *q)
+{
+ int i;
+ unsigned char val = 0;
+ for (i = 0; i < 2; i++) {
+ unsigned char c = *q++;
+ val <<= 4;
+ if (c >= '0' && c <= '9')
+ val += c - '0';
+ else if (c >= 'a' && c <= 'f')
+ val += c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F')
+ val += c - 'A' + 10;
+ else
+ return -1;
+ }
+ return val;
+}
+
+static char *decode_parameter(const char **query, int is_name)
+{
+ const char *q = *query;
+ struct strbuf out;
+
+ strbuf_init(&out, 16);
+ do {
+ unsigned char c = *q;
+
+ if (!c)
+ break;
+ if (c == '&' || (is_name && c == '=')) {
+ q++;
+ break;
+ }
+
+ if (c == '%') {
+ int val = decode_char(q + 1);
+ if (0 <= val) {
+ strbuf_addch(&out, val);
+ q += 3;
+ continue;
+ }
+ }
+
+ if (c == '+')
+ strbuf_addch(&out, ' ');
+ else
+ strbuf_addch(&out, c);
+ q++;
+ } while (1);
+ *query = q;
+ return strbuf_detach(&out, NULL);
+}
+
+static struct string_list *get_parameters(void)
+{
+ if (!query_params) {
+ const char *query = getenv("QUERY_STRING");
+
+ query_params = xcalloc(1, sizeof(*query_params));
+ while (query && *query) {
+ char *name = decode_parameter(&query, 1);
+ char *value = decode_parameter(&query, 0);
+ struct string_list_item *i;
+
+ i = string_list_lookup(name, query_params);
+ if (!i)
+ i = string_list_insert(name, query_params);
+ else
+ free(i->util);
+ i->util = value;
+ }
+ }
+ return query_params;
+}
+
+static const char *get_parameter(const char *name)
+{
+ struct string_list_item *i;
+ i = string_list_lookup(name, get_parameters());
+ return i ? i->util : NULL;
+}
+
+__attribute__((format (printf, 2, 3)))
+static void format_write(int fd, const char *fmt, ...)
+{
+ static char buffer[1024];
+
+ va_list args;
+ unsigned n;
+
+ va_start(args, fmt);
+ n = vsnprintf(buffer, sizeof(buffer), fmt, args);
+ va_end(args);
+ if (n >= sizeof(buffer))
+ die("protocol error: impossibly long line");
+
+ safe_write(fd, buffer, n);
+}
+
+static void http_status(unsigned code, const char *msg)
+{
+ format_write(1, "Status: %u %s\r\n", code, msg);
+}
+
+static void hdr_str(const char *name, const char *value)
+{
+ format_write(1, "%s: %s\r\n", name, value);
+}
+
+static void hdr_int(const char *name, uintmax_t value)
+{
+ format_write(1, "%s: %" PRIuMAX "\r\n", name, value);
+}
+
+static void hdr_date(const char *name, unsigned long when)
+{
+ const char *value = show_date(when, 0, DATE_RFC2822);
+ hdr_str(name, value);
+}
+
+static void hdr_nocache(void)
+{
+ hdr_str("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
+ hdr_str("Pragma", "no-cache");
+ hdr_str("Cache-Control", "no-cache, max-age=0, must-revalidate");
+}
+
+static void hdr_cache_forever(void)
+{
+ unsigned long now = time(NULL);
+ hdr_date("Date", now);
+ hdr_date("Expires", now + 31536000);
+ hdr_str("Cache-Control", "public, max-age=31536000");
+}
+
+static void end_headers(void)
+{
+ safe_write(1, "\r\n", 2);
+}
+
+__attribute__((format (printf, 1, 2)))
+static NORETURN void not_found(const char *err, ...)
+{
+ va_list params;
+
+ http_status(404, "Not Found");
+ hdr_nocache();
+ end_headers();
+
+ va_start(params, err);
+ if (err && *err)
+ vfprintf(stderr, err, params);
+ va_end(params);
+ exit(0);
+}
+
+__attribute__((format (printf, 1, 2)))
+static NORETURN void forbidden(const char *err, ...)
+{
+ va_list params;
+
+ http_status(403, "Forbidden");
+ hdr_nocache();
+ end_headers();
+
+ va_start(params, err);
+ if (err && *err)
+ vfprintf(stderr, err, params);
+ va_end(params);
+ exit(0);
+}
+
+static void select_getanyfile(void)
+{
+ if (!getanyfile)
+ forbidden("Unsupported service: getanyfile");
+}
+
+static void send_strbuf(const char *type, struct strbuf *buf)
+{
+ hdr_int(content_length, buf->len);
+ hdr_str(content_type, type);
+ end_headers();
+ safe_write(1, buf->buf, buf->len);
+}
+
+static void send_local_file(const char *the_type, const char *name)
+{
+ const char *p = git_path("%s", name);
+ size_t buf_alloc = 8192;
+ char *buf = xmalloc(buf_alloc);
+ int fd;
+ struct stat sb;
+
+ fd = open(p, O_RDONLY);
+ if (fd < 0)
+ not_found("Cannot open '%s': %s", p, strerror(errno));
+ if (fstat(fd, &sb) < 0)
+ die_errno("Cannot stat '%s'", p);
+
+ hdr_int(content_length, sb.st_size);
+ hdr_str(content_type, the_type);
+ hdr_date(last_modified, sb.st_mtime);
+ end_headers();
+
+ for (;;) {
+ ssize_t n = xread(fd, buf, buf_alloc);
+ if (n < 0)
+ die_errno("Cannot read '%s'", p);
+ if (!n)
+ break;
+ safe_write(1, buf, n);
+ }
+ close(fd);
+ free(buf);
+}
+
+static void get_text_file(char *name)
+{
+ select_getanyfile();
+ hdr_nocache();
+ send_local_file("text/plain", name);
+}
+
+static void get_loose_object(char *name)
+{
+ select_getanyfile();
+ hdr_cache_forever();
+ send_local_file("application/x-git-loose-object", name);
+}
+
+static void get_pack_file(char *name)
+{
+ select_getanyfile();
+ hdr_cache_forever();
+ send_local_file("application/x-git-packed-objects", name);
+}
+
+static void get_idx_file(char *name)
+{
+ select_getanyfile();
+ hdr_cache_forever();
+ send_local_file("application/x-git-packed-objects-toc", name);
+}
+
+static int http_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "http.getanyfile")) {
+ getanyfile = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (!prefixcmp(var, "http.")) {
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
+ struct rpc_service *svc = &rpc_service[i];
+ if (!strcmp(var + 5, svc->config_name)) {
+ svc->enabled = git_config_bool(var, value);
+ return 0;
+ }
+ }
+ }
+
+ /* we are not interested in parsing any other configuration here */
+ return 0;
+}
+
+static struct rpc_service *select_service(const char *name)
+{
+ struct rpc_service *svc = NULL;
+ int i;
+
+ if (prefixcmp(name, "git-"))
+ forbidden("Unsupported service: '%s'", name);
+
+ for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
+ struct rpc_service *s = &rpc_service[i];
+ if (!strcmp(s->name, name + 4)) {
+ svc = s;
+ break;
+ }
+ }
+
+ if (!svc)
+ forbidden("Unsupported service: '%s'", name);
+
+ if (svc->enabled < 0) {
+ const char *user = getenv("REMOTE_USER");
+ svc->enabled = (user && *user) ? 1 : 0;
+ }
+ if (!svc->enabled)
+ forbidden("Service not enabled: '%s'", svc->name);
+ return svc;
+}
+
+static void inflate_request(const char *prog_name, int out)
+{
+ z_stream stream;
+ unsigned char in_buf[8192];
+ unsigned char out_buf[8192];
+ unsigned long cnt = 0;
+ int ret;
+
+ memset(&stream, 0, sizeof(stream));
+ ret = inflateInit2(&stream, (15 + 16));
+ if (ret != Z_OK)
+ die("cannot start zlib inflater, zlib err %d", ret);
+
+ while (1) {
+ ssize_t n = xread(0, in_buf, sizeof(in_buf));
+ if (n <= 0)
+ die("request ended in the middle of the gzip stream");
+
+ stream.next_in = in_buf;
+ stream.avail_in = n;
+
+ while (0 < stream.avail_in) {
+ int ret;
+
+ stream.next_out = out_buf;
+ stream.avail_out = sizeof(out_buf);
+
+ ret = inflate(&stream, Z_NO_FLUSH);
+ if (ret != Z_OK && ret != Z_STREAM_END)
+ die("zlib error inflating request, result %d", ret);
+
+ n = stream.total_out - cnt;
+ if (write_in_full(out, out_buf, n) != n)
+ die("%s aborted reading request", prog_name);
+ cnt += n;
+
+ if (ret == Z_STREAM_END)
+ goto done;
+ }
+ }
+
+done:
+ inflateEnd(&stream);
+ close(out);
+}
+
+static void run_service(const char **argv)
+{
+ const char *encoding = getenv("HTTP_CONTENT_ENCODING");
+ const char *user = getenv("REMOTE_USER");
+ const char *host = getenv("REMOTE_ADDR");
+ char *env[3];
+ struct strbuf buf = STRBUF_INIT;
+ int gzipped_request = 0;
+ struct child_process cld;
+
+ if (encoding && !strcmp(encoding, "gzip"))
+ gzipped_request = 1;
+ else if (encoding && !strcmp(encoding, "x-gzip"))
+ gzipped_request = 1;
+
+ if (!user || !*user)
+ user = "anonymous";
+ if (!host || !*host)
+ host = "(none)";
+
+ memset(&env, 0, sizeof(env));
+ strbuf_addf(&buf, "GIT_COMMITTER_NAME=%s", user);
+ env[0] = strbuf_detach(&buf, NULL);
+
+ strbuf_addf(&buf, "GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
+ env[1] = strbuf_detach(&buf, NULL);
+ env[2] = NULL;
+
+ memset(&cld, 0, sizeof(cld));
+ cld.argv = argv;
+ cld.env = (const char *const *)env;
+ if (gzipped_request)
+ cld.in = -1;
+ cld.git_cmd = 1;
+ if (start_command(&cld))
+ exit(1);
+
+ close(1);
+ if (gzipped_request)
+ inflate_request(argv[0], cld.in);
+ else
+ close(0);
+
+ if (finish_command(&cld))
+ exit(1);
+ free(env[0]);
+ free(env[1]);
+ strbuf_release(&buf);
+}
+
+static int show_text_ref(const char *name, const unsigned char *sha1,
+ int flag, void *cb_data)
+{
+ struct strbuf *buf = cb_data;
+ struct object *o = parse_object(sha1);
+ if (!o)
+ return 0;
+
+ strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name);
+ if (o->type == OBJ_TAG) {
+ o = deref_tag(o, name, 0);
+ if (!o)
+ return 0;
+ strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), name);
+ }
+ return 0;
+}
+
+static void get_info_refs(char *arg)
+{
+ const char *service_name = get_parameter("service");
+ struct strbuf buf = STRBUF_INIT;
+
+ hdr_nocache();
+
+ if (service_name) {
+ const char *argv[] = {NULL /* service name */,
+ "--stateless-rpc", "--advertise-refs",
+ ".", NULL};
+ struct rpc_service *svc = select_service(service_name);
+
+ strbuf_addf(&buf, "application/x-git-%s-advertisement",
+ svc->name);
+ hdr_str(content_type, buf.buf);
+ end_headers();
+
+ packet_write(1, "# service=git-%s\n", svc->name);
+ packet_flush(1);
+
+ argv[0] = svc->name;
+ run_service(argv);
+
+ } else {
+ select_getanyfile();
+ for_each_ref(show_text_ref, &buf);
+ send_strbuf("text/plain", &buf);
+ }
+ strbuf_release(&buf);
+}
+
+static void get_info_packs(char *arg)
+{
+ size_t objdirlen = strlen(get_object_directory());
+ struct strbuf buf = STRBUF_INIT;
+ struct packed_git *p;
+ size_t cnt = 0;
+
+ select_getanyfile();
+ prepare_packed_git();
+ for (p = packed_git; p; p = p->next) {
+ if (p->pack_local)
+ cnt++;
+ }
+
+ strbuf_grow(&buf, cnt * 53 + 2);
+ for (p = packed_git; p; p = p->next) {
+ if (p->pack_local)
+ strbuf_addf(&buf, "P %s\n", p->pack_name + objdirlen + 6);
+ }
+ strbuf_addch(&buf, '\n');
+
+ hdr_nocache();
+ send_strbuf("text/plain; charset=utf-8", &buf);
+ strbuf_release(&buf);
+}
+
+static void check_content_type(const char *accepted_type)
+{
+ const char *actual_type = getenv("CONTENT_TYPE");
+
+ if (!actual_type)
+ actual_type = "";
+
+ if (strcmp(actual_type, accepted_type)) {
+ http_status(415, "Unsupported Media Type");
+ hdr_nocache();
+ end_headers();
+ format_write(1,
+ "Expected POST with Content-Type '%s',"
+ " but received '%s' instead.\n",
+ accepted_type, actual_type);
+ exit(0);
+ }
+}
+
+static void service_rpc(char *service_name)
+{
+ const char *argv[] = {NULL, "--stateless-rpc", ".", NULL};
+ struct rpc_service *svc = select_service(service_name);
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "application/x-git-%s-request", svc->name);
+ check_content_type(buf.buf);
+
+ hdr_nocache();
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "application/x-git-%s-result", svc->name);
+ hdr_str(content_type, buf.buf);
+
+ end_headers();
+
+ argv[0] = svc->name;
+ run_service(argv);
+ strbuf_release(&buf);
+}
+
+static NORETURN void die_webcgi(const char *err, va_list params)
+{
+ char buffer[1000];
+
+ http_status(500, "Internal Server Error");
+ hdr_nocache();
+ end_headers();
+
+ vsnprintf(buffer, sizeof(buffer), err, params);
+ fprintf(stderr, "fatal: %s\n", buffer);
+ exit(0);
+}
+
+static char* getdir(void)
+{
+ struct strbuf buf = STRBUF_INIT;
+ char *pathinfo = getenv("PATH_INFO");
+ char *root = getenv("GIT_PROJECT_ROOT");
+ char *path = getenv("PATH_TRANSLATED");
+
+ if (root && *root) {
+ if (!pathinfo || !*pathinfo)
+ die("GIT_PROJECT_ROOT is set but PATH_INFO is not");
+ if (daemon_avoid_alias(pathinfo))
+ die("'%s': aliased", pathinfo);
+ strbuf_addstr(&buf, root);
+ if (buf.buf[buf.len - 1] != '/')
+ strbuf_addch(&buf, '/');
+ if (pathinfo[0] == '/')
+ pathinfo++;
+ strbuf_addstr(&buf, pathinfo);
+ return strbuf_detach(&buf, NULL);
+ } else if (path && *path) {
+ return xstrdup(path);
+ } else
+ die("No GIT_PROJECT_ROOT or PATH_TRANSLATED from server");
+ return NULL;
+}
+
+static struct service_cmd {
+ const char *method;
+ const char *pattern;
+ void (*imp)(char *);
+} services[] = {
+ {"GET", "/HEAD$", get_text_file},
+ {"GET", "/info/refs$", get_info_refs},
+ {"GET", "/objects/info/alternates$", get_text_file},
+ {"GET", "/objects/info/http-alternates$", get_text_file},
+ {"GET", "/objects/info/packs$", get_info_packs},
+ {"GET", "/objects/[0-9a-f]{2}/[0-9a-f]{38}$", get_loose_object},
+ {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.pack$", get_pack_file},
+ {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file},
+
+ {"POST", "/git-upload-pack$", service_rpc},
+ {"POST", "/git-receive-pack$", service_rpc}
+};
+
+int main(int argc, char **argv)
+{
+ char *method = getenv("REQUEST_METHOD");
+ char *dir;
+ struct service_cmd *cmd = NULL;
+ char *cmd_arg = NULL;
+ int i;
+
+ git_extract_argv0_path(argv[0]);
+ set_die_routine(die_webcgi);
+
+ if (!method)
+ die("No REQUEST_METHOD from server");
+ if (!strcmp(method, "HEAD"))
+ method = "GET";
+ dir = getdir();
+
+ for (i = 0; i < ARRAY_SIZE(services); i++) {
+ struct service_cmd *c = &services[i];
+ regex_t re;
+ regmatch_t out[1];
+
+ if (regcomp(&re, c->pattern, REG_EXTENDED))
+ die("Bogus regex in service table: %s", c->pattern);
+ if (!regexec(&re, dir, 1, out, 0)) {
+ size_t n;
+
+ if (strcmp(method, c->method)) {
+ const char *proto = getenv("SERVER_PROTOCOL");
+ if (proto && !strcmp(proto, "HTTP/1.1"))
+ http_status(405, "Method Not Allowed");
+ else
+ http_status(400, "Bad Request");
+ hdr_nocache();
+ end_headers();
+ return 0;
+ }
+
+ cmd = c;
+ n = out[0].rm_eo - out[0].rm_so;
+ cmd_arg = xmalloc(n);
+ memcpy(cmd_arg, dir + out[0].rm_so + 1, n-1);
+ cmd_arg[n-1] = '\0';
+ dir[out[0].rm_so] = 0;
+ break;
+ }
+ regfree(&re);
+ }
+
+ if (!cmd)
+ not_found("Request not supported: '%s'", dir);
+
+ setup_path();
+ if (!enter_repo(dir, 0))
+ not_found("Not a git repository: '%s'", dir);
+
+ git_config(http_config, NULL);
+ cmd->imp(cmd_arg);
+ return 0;
+}
diff --git a/http-fetch.c b/http-fetch.c
index e8f44babd9..ffd0ad7e29 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -1,6 +1,10 @@
#include "cache.h"
+#include "exec_cmd.h"
#include "walker.h"
+static const char http_fetch_usage[] = "git http-fetch "
+"[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url";
+
int main(int argc, const char **argv)
{
const char *prefix;
@@ -19,9 +23,7 @@ int main(int argc, const char **argv)
int get_verbosely = 0;
int get_recover = 0;
- prefix = setup_git_directory();
-
- git_config(git_default_config, NULL);
+ git_extract_argv0_path(argv[0]);
while (arg < argc && argv[arg][0] == '-') {
if (argv[arg][1] == 't') {
@@ -37,6 +39,8 @@ int main(int argc, const char **argv)
} else if (argv[arg][1] == 'w') {
write_ref = &argv[arg + 1];
arg++;
+ } else if (argv[arg][1] == 'h') {
+ usage(http_fetch_usage);
} else if (!strcmp(argv[arg], "--recover")) {
get_recover = 1;
} else if (!strcmp(argv[arg], "--stdin")) {
@@ -44,10 +48,8 @@ int main(int argc, const char **argv)
}
arg++;
}
- if (argc < arg + 2 - commits_on_stdin) {
- usage("git http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url");
- return 1;
- }
+ if (argc != arg + 2 - commits_on_stdin)
+ usage(http_fetch_usage);
if (commits_on_stdin) {
commits = walker_targets_stdin(&commit_id, &write_ref);
} else {
@@ -55,6 +57,11 @@ int main(int argc, const char **argv)
commits = 1;
}
url = argv[arg];
+
+ prefix = setup_git_directory();
+
+ git_config(git_default_config, NULL);
+
if (url && url[strlen(url)-1] != '/') {
rewritten_url = xmalloc(strlen(url)+2);
strcpy(rewritten_url, url);
diff --git a/http-push.c b/http-push.c
index 00e83dcec1..432b20f2d9 100644
--- a/http-push.c
+++ b/http-push.c
@@ -78,6 +78,7 @@ static int push_verbosely;
static int push_all = MATCH_REFS_NONE;
static int force_all;
static int dry_run;
+static int helper_status;
static struct object_list *objects;
@@ -407,10 +408,10 @@ static void start_put(struct transfer_request *request)
curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &request->buffer);
#endif
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+ curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
- curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
if (start_active_slot(slot)) {
@@ -604,7 +605,7 @@ static void finish_request(struct transfer_request *request)
preq = (struct http_pack_request *)request->userData;
if (preq) {
- if (finish_http_pack_request(preq) > 0)
+ if (finish_http_pack_request(preq) == 0)
fail = 0;
release_http_pack_request(preq);
}
@@ -1792,8 +1793,6 @@ int main(int argc, char **argv)
git_extract_argv0_path(argv[0]);
- setup_git_directory();
-
repo = xcalloc(sizeof(*repo), 1);
argv++;
@@ -1813,6 +1812,10 @@ int main(int argc, char **argv)
dry_run = 1;
continue;
}
+ if (!strcmp(arg, "--helper-status")) {
+ helper_status = 1;
+ continue;
+ }
if (!strcmp(arg, "--verbose")) {
push_verbosely = 1;
http_is_verbose = 1;
@@ -1827,6 +1830,8 @@ int main(int argc, char **argv)
force_delete = 1;
continue;
}
+ if (!strcmp(arg, "-h"))
+ usage(http_push_usage);
}
if (!repo->url) {
char *path = strstr(arg, "//");
@@ -1854,6 +1859,8 @@ int main(int argc, char **argv)
if (delete_branch && nr_refspec != 1)
die("You must specify only one branch name when deleting a remote branch");
+ setup_git_directory();
+
memset(remote_dir_exists, -1, 256);
/*
@@ -1911,9 +1918,12 @@ int main(int argc, char **argv)
/* Remove a remote branch if -d or -D was specified */
if (delete_branch) {
- if (delete_remote_branch(refspec[0], force_delete) == -1)
+ if (delete_remote_branch(refspec[0], force_delete) == -1) {
fprintf(stderr, "Unable to delete remote branch %s\n",
refspec[0]);
+ if (helper_status)
+ printf("error %s cannot remove\n", refspec[0]);
+ }
goto cleanup;
}
@@ -1925,6 +1935,8 @@ int main(int argc, char **argv)
}
if (!remote_refs) {
fprintf(stderr, "No refs in common and none specified; doing nothing.\n");
+ if (helper_status)
+ printf("error null no match\n");
rc = 0;
goto cleanup;
}
@@ -1942,8 +1954,12 @@ int main(int argc, char **argv)
if (is_null_sha1(ref->peer_ref->new_sha1)) {
if (delete_remote_branch(ref->name, 1) == -1) {
error("Could not remove %s", ref->name);
+ if (helper_status)
+ printf("error %s cannot remove\n", ref->name);
rc = -4;
}
+ else if (helper_status)
+ printf("ok %s\n", ref->name);
new_refs++;
continue;
}
@@ -1951,6 +1967,8 @@ int main(int argc, char **argv)
if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
if (push_verbosely || 1)
fprintf(stderr, "'%s': up-to-date\n", ref->name);
+ if (helper_status)
+ printf("ok %s up to date\n", ref->name);
continue;
}
@@ -1974,6 +1992,8 @@ int main(int argc, char **argv)
"need to pull first?",
ref->name,
ref->peer_ref->name);
+ if (helper_status)
+ printf("error %s non-fast forward\n", ref->name);
rc = -2;
continue;
}
@@ -1987,14 +2007,19 @@ int main(int argc, char **argv)
if (strcmp(ref->name, ref->peer_ref->name))
fprintf(stderr, " using '%s'", ref->peer_ref->name);
fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex);
- if (dry_run)
+ if (dry_run) {
+ if (helper_status)
+ printf("ok %s\n", ref->name);
continue;
+ }
/* Lock remote branch ref */
ref_lock = lock_remote(ref->name, LOCK_TIME);
if (ref_lock == NULL) {
fprintf(stderr, "Unable to lock remote branch %s\n",
ref->name);
+ if (helper_status)
+ printf("error %s lock error\n", ref->name);
rc = 1;
continue;
}
@@ -2045,6 +2070,8 @@ int main(int argc, char **argv)
if (!rc)
fprintf(stderr, " done\n");
+ if (helper_status)
+ printf("%s %s\n", !rc ? "ok" : "error", ref->name);
unlock_remote(ref_lock);
check_locks();
}
diff --git a/http.c b/http.c
index 23b2a1932c..ed6414a2aa 100644
--- a/http.c
+++ b/http.c
@@ -1,9 +1,11 @@
#include "http.h"
#include "pack.h"
+#include "sideband.h"
int data_received;
int active_requests;
int http_is_verbose;
+size_t http_post_buffer = 16 * LARGE_PACKET_MAX;
#ifdef USE_CURL_MULTI
static int max_requests = -1;
@@ -97,8 +99,6 @@ size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf)
return eltsize * nmemb;
}
-static void finish_active_slot(struct active_request_slot *slot);
-
#ifdef USE_CURL_MULTI
static void process_curl_messages(void)
{
@@ -174,6 +174,13 @@ static int http_options(const char *var, const char *value, void *cb)
if (!strcmp("http.proxy", var))
return git_config_string(&curl_http_proxy, var, value);
+ if (!strcmp("http.postbuffer", var)) {
+ http_post_buffer = git_config_int(var, value);
+ if (http_post_buffer < LARGE_PACKET_MAX)
+ http_post_buffer = LARGE_PACKET_MAX;
+ return 0;
+ }
+
/* Fall back on the default ones */
return git_default_config(var, value, cb);
}
@@ -638,7 +645,7 @@ void release_active_slot(struct active_request_slot *slot)
#endif
}
-static void finish_active_slot(struct active_request_slot *slot)
+void finish_active_slot(struct active_request_slot *slot)
{
closedown_active_slot(slot);
curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
diff --git a/http.h b/http.h
index 4c4e99c2f6..f828e1d806 100644
--- a/http.h
+++ b/http.h
@@ -79,6 +79,7 @@ extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp);
extern struct active_request_slot *get_active_slot(void);
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);
@@ -94,6 +95,7 @@ extern void http_cleanup(void);
extern int data_received;
extern int active_requests;
extern int http_is_verbose;
+extern size_t http_post_buffer;
extern char curl_errorstr[CURL_ERROR_SIZE];
diff --git a/ident.c b/ident.c
index 99f1c85ea5..26409b2a1b 100644
--- a/ident.c
+++ b/ident.c
@@ -205,7 +205,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);
- env_hint = NULL; /* warn only once, for "git var -l" */
+ env_hint = NULL; /* warn only once */
}
if (error_on_no_name)
die("empty ident %s <%s> not allowed", name, email);
diff --git a/imap-send.c b/imap-send.c
index 3847fd151d..de8114bac0 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -24,6 +24,7 @@
#include "cache.h"
#include "exec_cmd.h"
+#include "run-command.h"
#ifdef NO_OPENSSL
typedef void *SSL;
#endif
@@ -93,6 +94,9 @@ struct msg_data {
unsigned int crlf:1;
};
+static const char imap_send_usage[] = "git imap-send < <mbox>";
+
+#undef DRV_OK
#define DRV_OK 0
#define DRV_MSG_BAD -1
#define DRV_BOX_BAD -2
@@ -100,13 +104,16 @@ struct msg_data {
static int Verbose, Quiet;
+__attribute__((format (printf, 1, 2)))
static void imap_info(const char *, ...);
+__attribute__((format (printf, 1, 2)))
static void imap_warn(const char *, ...);
static char *next_arg(char **);
static void free_generic_messages(struct message *);
+__attribute__((format (printf, 3, 4)))
static int nfsnprintf(char *buf, int blen, const char *fmt, ...);
static int nfvasprintf(char **strp, const char *fmt, va_list ap)
@@ -123,9 +130,6 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap)
return len;
}
-static void arc4_init(void);
-static unsigned char arc4_getbyte(void);
-
struct imap_server_conf {
char *name;
char *tunnel;
@@ -154,7 +158,7 @@ struct imap_list {
};
struct imap_socket {
- int fd;
+ int fd[2];
SSL *ssl;
};
@@ -273,7 +277,11 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
fprintf(stderr, "SSL requested but SSL support not compiled in\n");
return -1;
#else
+#if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
+ const SSL_METHOD *meth;
+#else
SSL_METHOD *meth;
+#endif
SSL_CTX *ctx;
int ret;
@@ -304,8 +312,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
ssl_socket_perror("SSL_new");
return -1;
}
- if (!SSL_set_fd(sock->ssl, sock->fd)) {
- ssl_socket_perror("SSL_set_fd");
+ if (!SSL_set_rfd(sock->ssl, sock->fd[0])) {
+ ssl_socket_perror("SSL_set_rfd");
+ return -1;
+ }
+ if (!SSL_set_wfd(sock->ssl, sock->fd[1])) {
+ ssl_socket_perror("SSL_set_wfd");
return -1;
}
@@ -327,11 +339,12 @@ static int socket_read(struct imap_socket *sock, char *buf, int len)
n = SSL_read(sock->ssl, buf, len);
else
#endif
- n = xread(sock->fd, buf, len);
+ n = xread(sock->fd[0], buf, len);
if (n <= 0) {
socket_perror("read", sock, n);
- close(sock->fd);
- sock->fd = -1;
+ close(sock->fd[0]);
+ close(sock->fd[1]);
+ sock->fd[0] = sock->fd[1] = -1;
}
return n;
}
@@ -344,11 +357,12 @@ static int socket_write(struct imap_socket *sock, const char *buf, int len)
n = SSL_write(sock->ssl, buf, len);
else
#endif
- n = write_in_full(sock->fd, buf, len);
+ n = write_in_full(sock->fd[1], buf, len);
if (n != len) {
socket_perror("write", sock, n);
- close(sock->fd);
- sock->fd = -1;
+ close(sock->fd[0]);
+ close(sock->fd[1]);
+ sock->fd[0] = sock->fd[1] = -1;
}
return n;
}
@@ -361,7 +375,8 @@ static void socket_shutdown(struct imap_socket *sock)
SSL_free(sock->ssl);
}
#endif
- close(sock->fd);
+ close(sock->fd[0]);
+ close(sock->fd[1]);
}
/* simple line buffering */
@@ -489,52 +504,6 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
return ret;
}
-static struct {
- unsigned char i, j, s[256];
-} rs;
-
-static void arc4_init(void)
-{
- int i, fd;
- unsigned char j, si, dat[128];
-
- if ((fd = open("/dev/urandom", O_RDONLY)) < 0 && (fd = open("/dev/random", O_RDONLY)) < 0) {
- fprintf(stderr, "Fatal: no random number source available.\n");
- exit(3);
- }
- if (read_in_full(fd, dat, 128) != 128) {
- fprintf(stderr, "Fatal: cannot read random number source.\n");
- exit(3);
- }
- close(fd);
-
- for (i = 0; i < 256; i++)
- rs.s[i] = i;
- for (i = j = 0; i < 256; i++) {
- si = rs.s[i];
- j += si + dat[i & 127];
- rs.s[i] = rs.s[j];
- rs.s[j] = si;
- }
- rs.i = rs.j = 0;
-
- for (i = 0; i < 256; i++)
- arc4_getbyte();
-}
-
-static unsigned char arc4_getbyte(void)
-{
- unsigned char si, sj;
-
- rs.i++;
- si = rs.s[rs.i];
- rs.j += si;
- sj = rs.s[rs.j];
- rs.s[rs.i] = sj;
- rs.s[rs.j] = si;
- return rs.s[(si + sj) & 0xff];
-}
-
static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx,
struct imap_cmd_cb *cb,
const char *fmt, va_list ap)
@@ -596,6 +565,7 @@ static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx,
return cmd;
}
+__attribute__((format (printf, 3, 4)))
static struct imap_cmd *issue_imap_cmd(struct imap_store *ctx,
struct imap_cmd_cb *cb,
const char *fmt, ...)
@@ -609,6 +579,7 @@ static struct imap_cmd *issue_imap_cmd(struct imap_store *ctx,
return ret;
}
+__attribute__((format (printf, 3, 4)))
static int imap_exec(struct imap_store *ctx, struct imap_cmd_cb *cb,
const char *fmt, ...)
{
@@ -624,6 +595,7 @@ static int imap_exec(struct imap_store *ctx, struct imap_cmd_cb *cb,
return get_cmd_result(ctx, cmdp);
}
+__attribute__((format (printf, 3, 4)))
static int imap_exec_m(struct imap_store *ctx, struct imap_cmd_cb *cb,
const char *fmt, ...)
{
@@ -914,7 +886,7 @@ static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd)
if (!strcmp("NO", arg)) {
if (cmdp->cb.create && cmd && (cmdp->cb.trycreate || !memcmp(cmd, "[TRYCREATE]", 11))) { /* SELECT, APPEND or UID COPY */
p = strchr(cmdp->cmd, '"');
- if (!issue_imap_cmd(ctx, NULL, "CREATE \"%.*s\"", strchr(p + 1, '"') - p + 1, p)) {
+ if (!issue_imap_cmd(ctx, NULL, "CREATE \"%.*s\"", (int)(strchr(p + 1, '"') - p + 1), p)) {
resp = RESP_BAD;
goto normal;
}
@@ -960,7 +932,7 @@ static void imap_close_server(struct imap_store *ictx)
{
struct imap *imap = ictx->imap;
- if (imap->buf.sock.fd != -1) {
+ if (imap->buf.sock.fd[0] != -1) {
imap_exec(ictx, NULL, "LOGOUT");
socket_shutdown(&imap->buf.sock);
}
@@ -982,40 +954,35 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
struct imap_store *ctx;
struct imap *imap;
char *arg, *rsp;
- int s = -1, a[2], preauth;
- pid_t pid;
+ int s = -1, preauth;
ctx = xcalloc(sizeof(*ctx), 1);
ctx->imap = imap = xcalloc(sizeof(*imap), 1);
- imap->buf.sock.fd = -1;
+ imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;
imap->in_progress_append = &imap->in_progress;
/* open connection to IMAP server */
if (srvc->tunnel) {
- imap_info("Starting tunnel '%s'... ", srvc->tunnel);
+ const char *argv[4];
+ struct child_process tunnel = {0};
- if (socketpair(PF_UNIX, SOCK_STREAM, 0, a)) {
- perror("socketpair");
- exit(1);
- }
+ imap_info("Starting tunnel '%s'... ", srvc->tunnel);
- pid = fork();
- if (pid < 0)
- _exit(127);
- if (!pid) {
- if (dup2(a[0], 0) == -1 || dup2(a[0], 1) == -1)
- _exit(127);
- close(a[0]);
- close(a[1]);
- execl("/bin/sh", "sh", "-c", srvc->tunnel, NULL);
- _exit(127);
- }
+ argv[0] = "sh";
+ argv[1] = "-c";
+ argv[2] = srvc->tunnel;
+ argv[3] = NULL;
- close(a[0]);
+ tunnel.argv = argv;
+ tunnel.in = -1;
+ tunnel.out = -1;
+ if (start_command(&tunnel))
+ die("cannot start proxy %s", argv[0]);
- imap->buf.sock.fd = a[1];
+ imap->buf.sock.fd[0] = tunnel.out;
+ imap->buf.sock.fd[1] = tunnel.in;
imap_info("ok\n");
} else {
@@ -1092,7 +1059,8 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
goto bail;
}
- imap->buf.sock.fd = s;
+ imap->buf.sock.fd[0] = s;
+ imap->buf.sock.fd[1] = dup(s);
if (srvc->use_ssl &&
ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {
@@ -1198,88 +1166,20 @@ static int imap_make_flags(int flags, char *buf)
return d;
}
-#define TUIDL 8
-
-static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid)
+static int imap_store_msg(struct store *gctx, struct msg_data *data)
{
struct imap_store *ctx = (struct imap_store *)gctx;
struct imap *imap = ctx->imap;
struct imap_cmd_cb cb;
- char *fmap, *buf;
const char *prefix, *box;
- int ret, i, j, d, len, extra, nocr;
- int start, sbreak = 0, ebreak = 0;
- char flagstr[128], tuid[TUIDL * 2 + 1];
+ int ret, d;
+ char flagstr[128];
memset(&cb, 0, sizeof(cb));
- fmap = data->data;
- len = data->len;
- nocr = !data->crlf;
- extra = 0, i = 0;
- if (!CAP(UIDPLUS) && uid) {
- nloop:
- start = i;
- while (i < len)
- if (fmap[i++] == '\n') {
- extra += nocr;
- if (i - 2 + nocr == start) {
- sbreak = ebreak = i - 2 + nocr;
- goto mktid;
- }
- if (!memcmp(fmap + start, "X-TUID: ", 8)) {
- extra -= (ebreak = i) - (sbreak = start) + nocr;
- goto mktid;
- }
- goto nloop;
- }
- /* invalid message */
- free(fmap);
- return DRV_MSG_BAD;
- mktid:
- for (j = 0; j < TUIDL; j++)
- sprintf(tuid + j * 2, "%02x", arc4_getbyte());
- extra += 8 + TUIDL * 2 + 2;
- }
- if (nocr)
- for (; i < len; i++)
- if (fmap[i] == '\n')
- extra++;
-
- cb.dlen = len + extra;
- buf = cb.data = xmalloc(cb.dlen);
- i = 0;
- if (!CAP(UIDPLUS) && uid) {
- if (nocr) {
- for (; i < sbreak; i++)
- if (fmap[i] == '\n') {
- *buf++ = '\r';
- *buf++ = '\n';
- } else
- *buf++ = fmap[i];
- } else {
- memcpy(buf, fmap, sbreak);
- buf += sbreak;
- }
- memcpy(buf, "X-TUID: ", 8);
- buf += 8;
- memcpy(buf, tuid, TUIDL * 2);
- buf += TUIDL * 2;
- *buf++ = '\r';
- *buf++ = '\n';
- i = ebreak;
- }
- if (nocr) {
- for (; i < len; i++)
- if (fmap[i] == '\n') {
- *buf++ = '\r';
- *buf++ = '\n';
- } else
- *buf++ = fmap[i];
- } else
- memcpy(buf, fmap + i, len - i);
-
- free(fmap);
+ cb.dlen = data->len;
+ cb.data = xmalloc(cb.dlen);
+ memcpy(cb.data, data->data, data->len);
d = 0;
if (data->flags) {
@@ -1288,26 +1188,14 @@ static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid)
}
flagstr[d] = 0;
- if (!uid) {
- box = gctx->conf->trash;
- prefix = ctx->prefix;
- cb.create = 1;
- if (ctx->trashnc)
- imap->caps = imap->rcaps & ~(1 << LITERALPLUS);
- } else {
- box = gctx->name;
- prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix;
- cb.create = 0;
- }
- cb.ctx = uid;
+ box = gctx->name;
+ prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix;
+ cb.create = 0;
ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr);
imap->caps = imap->rcaps;
if (ret != DRV_OK)
return ret;
- if (!uid)
- ctx->trashnc = 0;
- else
- gctx->count++;
+ gctx->count++;
return DRV_OK;
}
@@ -1483,7 +1371,6 @@ int main(int argc, char **argv)
{
struct msg_data all_msgs, msg;
struct store *ctx = NULL;
- int uid = 0;
int ofs = 0;
int r;
int total, n = 0;
@@ -1491,8 +1378,8 @@ int main(int argc, char **argv)
git_extract_argv0_path(argv[0]);
- /* init the random number generator */
- arc4_init();
+ if (argc != 1)
+ usage(imap_send_usage);
setup_git_directory_gently(&nongit_ok);
git_config(git_imap_config, NULL);
@@ -1540,7 +1427,7 @@ int main(int argc, char **argv)
break;
if (server.use_html)
wrap_in_html(&msg);
- r = imap_store_msg(ctx, &msg, &uid);
+ r = imap_store_msg(ctx, &msg);
if (r != DRV_OK)
break;
n++;
diff --git a/index-pack.c b/index-pack.c
index b4f8278659..190f372dd8 100644
--- a/index-pack.c
+++ b/index-pack.c
@@ -882,6 +882,9 @@ int main(int argc, char **argv)
git_extract_argv0_path(argv[0]);
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(index_pack_usage);
+
/*
* We wish to read the repository's config file if any, and
* for that it is necessary to call setup_git_directory_gently().
diff --git a/merge-recursive.c b/merge-recursive.c
index f55b7ebe11..baabe2c356 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -86,6 +86,7 @@ static void flush_output(struct merge_options *o)
}
}
+__attribute__((format (printf, 3, 4)))
static void output(struct merge_options *o, int v, const char *fmt, ...)
{
int len;
@@ -214,7 +215,8 @@ struct tree *write_tree_from_memory(struct merge_options *o)
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), ce_namelen(ce), ce->name);
+ output(o, 0, "%d %.*s", ce_stage(ce),
+ (int)ce_namelen(ce), ce->name);
}
return NULL;
}
diff --git a/notes.c b/notes.c
new file mode 100644
index 0000000000..50a4672d7c
--- /dev/null
+++ b/notes.c
@@ -0,0 +1,429 @@
+#include "cache.h"
+#include "commit.h"
+#include "notes.h"
+#include "refs.h"
+#include "utf8.h"
+#include "strbuf.h"
+#include "tree-walk.h"
+
+/*
+ * Use a non-balancing simple 16-tree structure with struct int_node as
+ * internal nodes, and struct leaf_node as leaf nodes. Each int_node has a
+ * 16-array of pointers to its children.
+ * The bottom 2 bits of each pointer is used to identify the pointer type
+ * - ptr & 3 == 0 - NULL pointer, assert(ptr == NULL)
+ * - ptr & 3 == 1 - pointer to next internal node - cast to struct int_node *
+ * - ptr & 3 == 2 - pointer to note entry - cast to struct leaf_node *
+ * - ptr & 3 == 3 - pointer to subtree entry - cast to struct leaf_node *
+ *
+ * The root node is a statically allocated struct int_node.
+ */
+struct int_node {
+ void *a[16];
+};
+
+/*
+ * Leaf nodes come in two variants, note entries and subtree entries,
+ * distinguished by the LSb of the leaf node pointer (see above).
+ * As a note entry, the key is the SHA1 of the referenced commit, and the
+ * value is the SHA1 of the note object.
+ * As a subtree entry, the key is the prefix SHA1 (w/trailing NULs) of the
+ * referenced commit, using the last byte of the key to store the length of
+ * the prefix. The value is the SHA1 of the tree object containing the notes
+ * subtree.
+ */
+struct leaf_node {
+ unsigned char key_sha1[20];
+ unsigned char val_sha1[20];
+};
+
+#define PTR_TYPE_NULL 0
+#define PTR_TYPE_INTERNAL 1
+#define PTR_TYPE_NOTE 2
+#define PTR_TYPE_SUBTREE 3
+
+#define GET_PTR_TYPE(ptr) ((uintptr_t) (ptr) & 3)
+#define CLR_PTR_TYPE(ptr) ((void *) ((uintptr_t) (ptr) & ~3))
+#define SET_PTR_TYPE(ptr, type) ((void *) ((uintptr_t) (ptr) | (type)))
+
+#define GET_NIBBLE(n, sha1) (((sha1[n >> 1]) >> ((~n & 0x01) << 2)) & 0x0f)
+
+#define SUBTREE_SHA1_PREFIXCMP(key_sha1, subtree_sha1) \
+ (memcmp(key_sha1, subtree_sha1, subtree_sha1[19]))
+
+static struct int_node root_node;
+
+static int initialized;
+
+static void load_subtree(struct leaf_node *subtree, struct int_node *node,
+ unsigned int n);
+
+/*
+ * Search the tree until the appropriate location for the given key is found:
+ * 1. Start at the root node, with n = 0
+ * 2. If a[0] at the current level is a matching subtree entry, unpack that
+ * subtree entry and remove it; restart search at the current level.
+ * 3. Use the nth nibble of the key as an index into a:
+ * - If a[n] is an int_node, recurse from #2 into that node and increment n
+ * - If a matching subtree entry, unpack that subtree entry (and remove it);
+ * restart search at the current level.
+ * - Otherwise, we have found one of the following:
+ * - a subtree entry which does not match the key
+ * - a note entry which may or may not match the key
+ * - an unused leaf node (NULL)
+ * In any case, set *tree and *n, and return pointer to the tree location.
+ */
+static void **note_tree_search(struct int_node **tree,
+ unsigned char *n, const unsigned char *key_sha1)
+{
+ struct leaf_node *l;
+ unsigned char i;
+ void *p = (*tree)->a[0];
+
+ if (GET_PTR_TYPE(p) == PTR_TYPE_SUBTREE) {
+ l = (struct leaf_node *) CLR_PTR_TYPE(p);
+ if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) {
+ /* unpack tree and resume search */
+ (*tree)->a[0] = NULL;
+ load_subtree(l, *tree, *n);
+ free(l);
+ return note_tree_search(tree, n, key_sha1);
+ }
+ }
+
+ i = GET_NIBBLE(*n, key_sha1);
+ p = (*tree)->a[i];
+ switch(GET_PTR_TYPE(p)) {
+ case PTR_TYPE_INTERNAL:
+ *tree = CLR_PTR_TYPE(p);
+ (*n)++;
+ return note_tree_search(tree, n, key_sha1);
+ case PTR_TYPE_SUBTREE:
+ l = (struct leaf_node *) CLR_PTR_TYPE(p);
+ if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) {
+ /* unpack tree and resume search */
+ (*tree)->a[i] = NULL;
+ load_subtree(l, *tree, *n);
+ free(l);
+ return note_tree_search(tree, n, key_sha1);
+ }
+ /* fall through */
+ default:
+ return &((*tree)->a[i]);
+ }
+}
+
+/*
+ * To find a leaf_node:
+ * Search to the tree location appropriate for the given key:
+ * If a note entry with matching key, return the note entry, else return NULL.
+ */
+static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n,
+ const unsigned char *key_sha1)
+{
+ void **p = note_tree_search(&tree, &n, key_sha1);
+ if (GET_PTR_TYPE(*p) == PTR_TYPE_NOTE) {
+ struct leaf_node *l = (struct leaf_node *) CLR_PTR_TYPE(*p);
+ if (!hashcmp(key_sha1, l->key_sha1))
+ return l;
+ }
+ return NULL;
+}
+
+/* Create a new blob object by concatenating the two given blob objects */
+static int concatenate_notes(unsigned char *cur_sha1,
+ const unsigned char *new_sha1)
+{
+ char *cur_msg, *new_msg, *buf;
+ unsigned long cur_len, new_len, buf_len;
+ enum object_type cur_type, new_type;
+ int ret;
+
+ /* read in both note blob objects */
+ new_msg = read_sha1_file(new_sha1, &new_type, &new_len);
+ if (!new_msg || !new_len || new_type != OBJ_BLOB) {
+ free(new_msg);
+ return 0;
+ }
+ cur_msg = read_sha1_file(cur_sha1, &cur_type, &cur_len);
+ if (!cur_msg || !cur_len || cur_type != OBJ_BLOB) {
+ free(cur_msg);
+ free(new_msg);
+ hashcpy(cur_sha1, new_sha1);
+ return 0;
+ }
+
+ /* we will separate the notes by a newline anyway */
+ if (cur_msg[cur_len - 1] == '\n')
+ cur_len--;
+
+ /* concatenate cur_msg and new_msg into buf */
+ buf_len = cur_len + 1 + new_len;
+ buf = (char *) xmalloc(buf_len);
+ memcpy(buf, cur_msg, cur_len);
+ buf[cur_len] = '\n';
+ memcpy(buf + cur_len + 1, new_msg, new_len);
+
+ free(cur_msg);
+ free(new_msg);
+
+ /* create a new blob object from buf */
+ ret = write_sha1_file(buf, buf_len, "blob", cur_sha1);
+ free(buf);
+ return ret;
+}
+
+/*
+ * To insert a leaf_node:
+ * Search to the tree location appropriate for the given leaf_node's key:
+ * - If location is unused (NULL), store the tweaked pointer directly there
+ * - If location holds a note entry that matches the note-to-be-inserted, then
+ * concatenate the two notes.
+ * - If location holds a note entry that matches the subtree-to-be-inserted,
+ * then unpack the subtree-to-be-inserted into the location.
+ * - If location holds a matching subtree entry, unpack the subtree at that
+ * location, and restart the insert operation from that level.
+ * - Else, create a new int_node, holding both the node-at-location and the
+ * node-to-be-inserted, and store the new int_node into the location.
+ */
+static void note_tree_insert(struct int_node *tree, unsigned char n,
+ struct leaf_node *entry, unsigned char type)
+{
+ struct int_node *new_node;
+ struct leaf_node *l;
+ void **p = note_tree_search(&tree, &n, entry->key_sha1);
+
+ assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */
+ l = (struct leaf_node *) CLR_PTR_TYPE(*p);
+ switch(GET_PTR_TYPE(*p)) {
+ case PTR_TYPE_NULL:
+ assert(!*p);
+ *p = SET_PTR_TYPE(entry, type);
+ return;
+ case PTR_TYPE_NOTE:
+ switch (type) {
+ case PTR_TYPE_NOTE:
+ if (!hashcmp(l->key_sha1, entry->key_sha1)) {
+ /* skip concatenation if l == entry */
+ if (!hashcmp(l->val_sha1, entry->val_sha1))
+ return;
+
+ if (concatenate_notes(l->val_sha1,
+ entry->val_sha1))
+ die("failed to concatenate note %s "
+ "into note %s for commit %s",
+ sha1_to_hex(entry->val_sha1),
+ sha1_to_hex(l->val_sha1),
+ sha1_to_hex(l->key_sha1));
+ free(entry);
+ return;
+ }
+ break;
+ case PTR_TYPE_SUBTREE:
+ if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1,
+ entry->key_sha1)) {
+ /* unpack 'entry' */
+ load_subtree(entry, tree, n);
+ free(entry);
+ return;
+ }
+ break;
+ }
+ break;
+ case PTR_TYPE_SUBTREE:
+ if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) {
+ /* unpack 'l' and restart insert */
+ *p = NULL;
+ load_subtree(l, tree, n);
+ free(l);
+ note_tree_insert(tree, n, entry, type);
+ return;
+ }
+ break;
+ }
+
+ /* non-matching leaf_node */
+ assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE ||
+ GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE);
+ new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1);
+ note_tree_insert(new_node, n + 1, l, GET_PTR_TYPE(*p));
+ *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);
+ note_tree_insert(new_node, n + 1, entry, type);
+}
+
+/* Free the entire notes data contained in the given tree */
+static void note_tree_free(struct int_node *tree)
+{
+ unsigned int i;
+ for (i = 0; i < 16; i++) {
+ void *p = tree->a[i];
+ switch(GET_PTR_TYPE(p)) {
+ case PTR_TYPE_INTERNAL:
+ note_tree_free(CLR_PTR_TYPE(p));
+ /* fall through */
+ case PTR_TYPE_NOTE:
+ case PTR_TYPE_SUBTREE:
+ free(CLR_PTR_TYPE(p));
+ }
+ }
+}
+
+/*
+ * Convert a partial SHA1 hex string to the corresponding partial SHA1 value.
+ * - hex - Partial SHA1 segment in ASCII hex format
+ * - hex_len - Length of above segment. Must be multiple of 2 between 0 and 40
+ * - sha1 - Partial SHA1 value is written here
+ * - sha1_len - Max #bytes to store in sha1, Must be >= hex_len / 2, and < 20
+ * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format).
+ * Otherwise, returns number of bytes written to sha1 (i.e. hex_len / 2).
+ * Pads sha1 with NULs up to sha1_len (not included in returned length).
+ */
+static int get_sha1_hex_segment(const char *hex, unsigned int hex_len,
+ unsigned char *sha1, unsigned int sha1_len)
+{
+ unsigned int i, len = hex_len >> 1;
+ if (hex_len % 2 != 0 || len > sha1_len)
+ return -1;
+ for (i = 0; i < len; i++) {
+ unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
+ if (val & ~0xff)
+ return -1;
+ *sha1++ = val;
+ hex += 2;
+ }
+ for (; i < sha1_len; i++)
+ *sha1++ = 0;
+ return len;
+}
+
+static void load_subtree(struct leaf_node *subtree, struct int_node *node,
+ unsigned int n)
+{
+ unsigned char commit_sha1[20];
+ unsigned int prefix_len;
+ void *buf;
+ struct tree_desc desc;
+ struct name_entry entry;
+
+ buf = fill_tree_descriptor(&desc, subtree->val_sha1);
+ if (!buf)
+ die("Could not read %s for notes-index",
+ sha1_to_hex(subtree->val_sha1));
+
+ prefix_len = subtree->key_sha1[19];
+ assert(prefix_len * 2 >= n);
+ memcpy(commit_sha1, subtree->key_sha1, prefix_len);
+ while (tree_entry(&desc, &entry)) {
+ int len = get_sha1_hex_segment(entry.path, strlen(entry.path),
+ commit_sha1 + prefix_len, 20 - prefix_len);
+ if (len < 0)
+ continue; /* entry.path is not a SHA1 sum. Skip */
+ len += prefix_len;
+
+ /*
+ * If commit SHA1 is complete (len == 20), assume note object
+ * If commit SHA1 is incomplete (len < 20), assume note subtree
+ */
+ if (len <= 20) {
+ unsigned char type = PTR_TYPE_NOTE;
+ struct leaf_node *l = (struct leaf_node *)
+ xcalloc(sizeof(struct leaf_node), 1);
+ hashcpy(l->key_sha1, commit_sha1);
+ hashcpy(l->val_sha1, entry.sha1);
+ if (len < 20) {
+ l->key_sha1[19] = (unsigned char) len;
+ type = PTR_TYPE_SUBTREE;
+ }
+ note_tree_insert(node, n, l, type);
+ }
+ }
+ free(buf);
+}
+
+static void initialize_notes(const char *notes_ref_name)
+{
+ unsigned char sha1[20], commit_sha1[20];
+ unsigned mode;
+ struct leaf_node root_tree;
+
+ if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) ||
+ get_tree_entry(commit_sha1, "", sha1, &mode))
+ return;
+
+ hashclr(root_tree.key_sha1);
+ hashcpy(root_tree.val_sha1, sha1);
+ load_subtree(&root_tree, &root_node, 0);
+}
+
+static unsigned char *lookup_notes(const unsigned char *commit_sha1)
+{
+ struct leaf_node *found = note_tree_find(&root_node, 0, commit_sha1);
+ if (found)
+ return found->val_sha1;
+ return NULL;
+}
+
+void free_notes(void)
+{
+ note_tree_free(&root_node);
+ memset(&root_node, 0, sizeof(struct int_node));
+ initialized = 0;
+}
+
+void get_commit_notes(const struct commit *commit, struct strbuf *sb,
+ const char *output_encoding, int flags)
+{
+ static const char utf8[] = "utf-8";
+ unsigned char *sha1;
+ char *msg, *msg_p;
+ unsigned long linelen, msglen;
+ enum object_type type;
+
+ if (!initialized) {
+ const char *env = getenv(GIT_NOTES_REF_ENVIRONMENT);
+ if (env)
+ notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT);
+ else if (!notes_ref_name)
+ notes_ref_name = GIT_NOTES_DEFAULT_REF;
+ initialize_notes(notes_ref_name);
+ initialized = 1;
+ }
+
+ sha1 = lookup_notes(commit->object.sha1);
+ if (!sha1)
+ return;
+
+ if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen ||
+ type != OBJ_BLOB) {
+ free(msg);
+ return;
+ }
+
+ if (output_encoding && *output_encoding &&
+ strcmp(utf8, output_encoding)) {
+ char *reencoded = reencode_string(msg, output_encoding, utf8);
+ if (reencoded) {
+ free(msg);
+ msg = reencoded;
+ msglen = strlen(msg);
+ }
+ }
+
+ /* we will end the annotation by a newline anyway */
+ if (msglen && msg[msglen - 1] == '\n')
+ msglen--;
+
+ if (flags & NOTES_SHOW_HEADER)
+ strbuf_addstr(sb, "\nNotes:\n");
+
+ for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
+ linelen = strchrnul(msg_p, '\n') - msg_p;
+
+ if (flags & NOTES_INDENT)
+ strbuf_addstr(sb, " ");
+ strbuf_add(sb, msg_p, linelen);
+ strbuf_addch(sb, '\n');
+ }
+
+ free(msg);
+}
diff --git a/notes.h b/notes.h
new file mode 100644
index 0000000000..a1421e351a
--- /dev/null
+++ b/notes.h
@@ -0,0 +1,13 @@
+#ifndef NOTES_H
+#define NOTES_H
+
+/* Free (and de-initialize) the internal notes tree structure */
+void free_notes(void);
+
+#define NOTES_SHOW_HEADER 1
+#define NOTES_INDENT 2
+
+void get_commit_notes(const struct commit *commit, struct strbuf *sb,
+ const char *output_encoding, int flags);
+
+#endif
diff --git a/pack-redundant.c b/pack-redundant.c
index 69a7ab2e27..21c61dbbe9 100644
--- a/pack-redundant.c
+++ b/pack-redundant.c
@@ -603,6 +603,9 @@ int main(int argc, char **argv)
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++) {
diff --git a/pager.c b/pager.c
index 86facec7b4..92c03f654a 100644
--- a/pager.c
+++ b/pager.c
@@ -2,6 +2,10 @@
#include "run-command.h"
#include "sigchain.h"
+#ifndef DEFAULT_PAGER
+#define DEFAULT_PAGER "less"
+#endif
+
/*
* This is split up from the rest of git so that we can do
* something different on Windows.
@@ -44,12 +48,14 @@ static void wait_for_pager_signal(int signo)
raise(signo);
}
-void setup_pager(void)
+const char *git_pager(void)
{
- const char *pager = getenv("GIT_PAGER");
+ const char *pager;
if (!isatty(1))
- return;
+ return NULL;
+
+ pager = getenv("GIT_PAGER");
if (!pager) {
if (!pager_program)
git_config(git_default_config, NULL);
@@ -58,8 +64,18 @@ void setup_pager(void)
if (!pager)
pager = getenv("PAGER");
if (!pager)
- pager = "less";
+ pager = DEFAULT_PAGER;
else if (!*pager || !strcmp(pager, "cat"))
+ pager = NULL;
+
+ return pager;
+}
+
+void setup_pager(void)
+{
+ const char *pager = git_pager();
+
+ if (!pager)
return;
spawned_pager = 1; /* means we are emitting to terminal */
diff --git a/path.c b/path.c
index 047fdb0a1f..2ec950b27f 100644
--- a/path.c
+++ b/path.c
@@ -11,6 +11,7 @@
* which is what it's designed for.
*/
#include "cache.h"
+#include "strbuf.h"
static char bad_path[] = "/bad-path/";
@@ -207,43 +208,49 @@ int validate_headref(const char *path)
return -1;
}
-static char *user_path(char *buf, char *path, int sz)
+static struct passwd *getpw_str(const char *username, size_t len)
{
struct passwd *pw;
- char *slash;
- int len, baselen;
+ char *username_z = xmalloc(len + 1);
+ memcpy(username_z, username, len);
+ username_z[len] = '\0';
+ pw = getpwnam(username_z);
+ free(username_z);
+ return pw;
+}
- if (!path || path[0] != '~')
- return NULL;
- path++;
- slash = strchr(path, '/');
- if (path[0] == '/' || !path[0]) {
- pw = getpwuid(getuid());
- }
- else {
- if (slash) {
- *slash = 0;
- pw = getpwnam(path);
- *slash = '/';
+/*
+ * Return a string with ~ and ~user expanded via getpw*. If buf != NULL,
+ * then it is a newly allocated string. Returns NULL on getpw failure or
+ * if path is NULL.
+ */
+char *expand_user_path(const char *path)
+{
+ struct strbuf user_path = STRBUF_INIT;
+ const char *first_slash = strchrnul(path, '/');
+ const char *to_copy = path;
+
+ if (path == NULL)
+ goto return_null;
+ if (path[0] == '~') {
+ const char *username = path + 1;
+ size_t username_len = first_slash - username;
+ if (username_len == 0) {
+ const char *home = getenv("HOME");
+ strbuf_add(&user_path, home, strlen(home));
+ } else {
+ struct passwd *pw = getpw_str(username, username_len);
+ if (!pw)
+ goto return_null;
+ strbuf_add(&user_path, pw->pw_dir, strlen(pw->pw_dir));
}
- else
- pw = getpwnam(path);
+ to_copy = first_slash;
}
- if (!pw || !pw->pw_dir || sz <= strlen(pw->pw_dir))
- return NULL;
- baselen = strlen(pw->pw_dir);
- memcpy(buf, pw->pw_dir, baselen);
- while ((1 < baselen) && (buf[baselen-1] == '/')) {
- buf[baselen-1] = 0;
- baselen--;
- }
- if (slash && slash[1]) {
- len = strlen(slash);
- if (sz <= baselen + len)
- return NULL;
- memcpy(buf + baselen, slash, len + 1);
- }
- return buf;
+ strbuf_add(&user_path, to_copy, strlen(to_copy));
+ return strbuf_detach(&user_path, NULL);
+return_null:
+ strbuf_release(&user_path);
+ return NULL;
}
/*
@@ -291,8 +298,18 @@ char *enter_repo(char *path, int strict)
if (PATH_MAX <= len)
return NULL;
if (path[0] == '~') {
- if (!user_path(used_path, path, PATH_MAX))
+ char *newpath = expand_user_path(path);
+ if (!newpath || (PATH_MAX - 10 < strlen(newpath))) {
+ free(newpath);
return NULL;
+ }
+ /*
+ * Copy back into the static buffer. A pity
+ * since newpath was not bounded, but other
+ * branches of the if are limited by PATH_MAX
+ * anyway.
+ */
+ strcpy(used_path, newpath); free(newpath);
strcpy(validated_path, path);
path = used_path;
}
@@ -564,3 +581,50 @@ char *strip_path_suffix(const char *path, const char *suffix)
return NULL;
return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
}
+
+int daemon_avoid_alias(const char *p)
+{
+ int sl, ndot;
+
+ /*
+ * This resurrects the belts and suspenders paranoia check by HPA
+ * done in <435560F7.4080006@zytor.com> thread, now enter_repo()
+ * does not do getcwd() based path canonicalizations.
+ *
+ * sl becomes true immediately after seeing '/' and continues to
+ * be true as long as dots continue after that without intervening
+ * non-dot character.
+ */
+ if (!p || (*p != '/' && *p != '~'))
+ return -1;
+ sl = 1; ndot = 0;
+ p++;
+
+ while (1) {
+ char ch = *p++;
+ if (sl) {
+ if (ch == '.')
+ ndot++;
+ else if (ch == '/') {
+ if (ndot < 3)
+ /* reject //, /./ and /../ */
+ return -1;
+ ndot = 0;
+ }
+ else if (ch == 0) {
+ if (0 < ndot && ndot < 3)
+ /* reject /.$ and /..$ */
+ return -1;
+ return 0;
+ }
+ else
+ sl = ndot = 0;
+ }
+ else if (ch == 0)
+ return 0;
+ else if (ch == '/') {
+ sl = 1;
+ ndot = 0;
+ }
+ }
+}
diff --git a/pkt-line.c b/pkt-line.c
index b691abebd7..295ba2b16c 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -42,17 +42,19 @@ void packet_flush(int fd)
safe_write(fd, "0000", 4);
}
+void packet_buf_flush(struct strbuf *buf)
+{
+ strbuf_add(buf, "0000", 4);
+}
+
#define hex(a) (hexchar[(a) & 15])
-void packet_write(int fd, const char *fmt, ...)
+static char buffer[1000];
+static unsigned format_packet(const char *fmt, va_list args)
{
- static char buffer[1000];
static char hexchar[] = "0123456789abcdef";
- va_list args;
unsigned n;
- va_start(args, fmt);
n = vsnprintf(buffer + 4, sizeof(buffer) - 4, fmt, args);
- va_end(args);
if (n >= sizeof(buffer)-4)
die("protocol error: impossibly long line");
n += 4;
@@ -60,9 +62,31 @@ void packet_write(int fd, const char *fmt, ...)
buffer[1] = hex(n >> 8);
buffer[2] = hex(n >> 4);
buffer[3] = hex(n);
+ return n;
+}
+
+void packet_write(int fd, const char *fmt, ...)
+{
+ va_list args;
+ unsigned n;
+
+ va_start(args, fmt);
+ n = format_packet(fmt, args);
+ va_end(args);
safe_write(fd, buffer, n);
}
+void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
+{
+ va_list args;
+ unsigned n;
+
+ va_start(args, fmt);
+ n = format_packet(fmt, args);
+ va_end(args);
+ strbuf_add(buf, buffer, n);
+}
+
static void safe_read(int fd, void *buffer, unsigned size)
{
ssize_t ret = read_in_full(fd, buffer, size);
@@ -72,15 +96,11 @@ static void safe_read(int fd, void *buffer, unsigned size)
die("The remote end hung up unexpectedly");
}
-int packet_read_line(int fd, char *buffer, unsigned size)
+static int packet_length(const char *linelen)
{
int n;
- unsigned len;
- char linelen[4];
+ int len = 0;
- safe_read(fd, linelen, 4);
-
- len = 0;
for (n = 0; n < 4; n++) {
unsigned char c = linelen[n];
len <<= 4;
@@ -96,8 +116,20 @@ int packet_read_line(int fd, char *buffer, unsigned size)
len += c - 'A' + 10;
continue;
}
- die("protocol error: bad line length character");
+ return -1;
}
+ return len;
+}
+
+int packet_read_line(int fd, char *buffer, unsigned size)
+{
+ int len;
+ char linelen[4];
+
+ safe_read(fd, linelen, 4);
+ len = packet_length(linelen);
+ if (len < 0)
+ die("protocol error: bad line length character: %.4s", linelen);
if (!len)
return 0;
len -= 4;
@@ -107,3 +139,31 @@ int packet_read_line(int fd, char *buffer, unsigned size)
buffer[len] = 0;
return len;
}
+
+int packet_get_line(struct strbuf *out,
+ char **src_buf, size_t *src_len)
+{
+ int len;
+
+ if (*src_len < 4)
+ return -1;
+ len = packet_length(*src_buf);
+ if (len < 0)
+ return -1;
+ if (!len) {
+ *src_buf += 4;
+ *src_len -= 4;
+ return 0;
+ }
+ if (*src_len < len)
+ return -2;
+
+ *src_buf += 4;
+ *src_len -= 4;
+ len -= 4;
+
+ strbuf_add(out, *src_buf, len);
+ *src_buf += len;
+ *src_len -= len;
+ return len;
+}
diff --git a/pkt-line.h b/pkt-line.h
index 9df653f6f5..1e5dcfe87c 100644
--- a/pkt-line.h
+++ b/pkt-line.h
@@ -2,14 +2,18 @@
#define PKTLINE_H
#include "git-compat-util.h"
+#include "strbuf.h"
/*
* Silly packetized line writing interface
*/
void packet_flush(int fd);
void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
+void packet_buf_flush(struct strbuf *buf);
+void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
int packet_read_line(int fd, char *buffer, unsigned size);
+int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len);
ssize_t safe_write(int, const void *, ssize_t);
#endif
diff --git a/pretty.c b/pretty.c
index da15cf2a80..5661cba595 100644
--- a/pretty.c
+++ b/pretty.c
@@ -6,6 +6,7 @@
#include "string-list.h"
#include "mailmap.h"
#include "log-tree.h"
+#include "notes.h"
#include "color.h"
#include "reflog-walk.h"
@@ -446,6 +447,7 @@ struct format_commit_context {
const struct pretty_print_context *pretty_ctx;
unsigned commit_header_parsed:1;
unsigned commit_message_parsed:1;
+ size_t width, indent1, indent2;
/* These offsets are relative to the start of the commit message. */
struct chunk author;
@@ -459,6 +461,7 @@ struct format_commit_context {
struct chunk abbrev_commit_hash;
struct chunk abbrev_tree_hash;
struct chunk abbrev_parent_hashes;
+ size_t wrap_start;
};
static int add_again(struct strbuf *sb, struct chunk *chunk)
@@ -596,6 +599,35 @@ static void format_decoration(struct strbuf *sb, const struct commit *commit)
strbuf_addch(sb, ')');
}
+static void strbuf_wrap(struct strbuf *sb, size_t pos,
+ size_t width, size_t indent1, size_t indent2)
+{
+ struct strbuf tmp = STRBUF_INIT;
+
+ if (pos)
+ strbuf_add(&tmp, sb->buf, pos);
+ strbuf_add_wrapped_text(&tmp, sb->buf + pos,
+ (int) indent1, (int) indent2, (int) width);
+ strbuf_swap(&tmp, sb);
+ strbuf_release(&tmp);
+}
+
+static void rewrap_message_tail(struct strbuf *sb,
+ struct format_commit_context *c,
+ size_t new_width, size_t new_indent1,
+ size_t new_indent2)
+{
+ if (c->width == new_width && c->indent1 == new_indent1 &&
+ c->indent2 == new_indent2)
+ return;
+ if (c->wrap_start < sb->len)
+ strbuf_wrap(sb, c->wrap_start, c->width, c->indent1, c->indent2);
+ c->wrap_start = sb->len;
+ c->width = new_width;
+ c->indent1 = new_indent1;
+ c->indent2 = new_indent2;
+}
+
static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
void *context)
{
@@ -646,6 +678,30 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
return 3;
} else
return 0;
+ case 'w':
+ if (placeholder[1] == '(') {
+ unsigned long width = 0, indent1 = 0, indent2 = 0;
+ char *next;
+ const char *start = placeholder + 2;
+ const char *end = strchr(start, ')');
+ if (!end)
+ return 0;
+ if (end > start) {
+ width = strtoul(start, &next, 10);
+ if (*next == ',') {
+ indent1 = strtoul(next + 1, &next, 10);
+ if (*next == ',') {
+ indent2 = strtoul(next + 1,
+ &next, 10);
+ }
+ }
+ if (*next != ')')
+ return 0;
+ }
+ rewrap_message_tail(sb, c, width, indent1, indent2);
+ return end - placeholder + 1;
+ } else
+ return 0;
}
/* these depend on the commit */
@@ -718,6 +774,10 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
return 2;
}
return 0; /* unknown %g placeholder */
+ case 'N':
+ get_commit_notes(commit, sb, git_log_output_encoding ?
+ git_log_output_encoding : git_commit_encoding, 0);
+ return 1;
}
/* For the rest we have to parse the commit header. */
@@ -765,7 +825,9 @@ void format_commit_message(const struct commit *commit,
memset(&context, 0, sizeof(context));
context.commit = commit;
context.pretty_ctx = pretty_ctx;
+ context.wrap_start = sb->len;
strbuf_expand(sb, format, format_commit_item, &context);
+ rewrap_message_tail(sb, &context, 0, 0, 0);
}
static void pp_header(enum cmit_fmt fmt,
@@ -993,5 +1055,10 @@ 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)
+ get_commit_notes(commit, sb, encoding,
+ NOTES_SHOW_HEADER | NOTES_INDENT);
+
free(reencoded);
}
diff --git a/refs.c b/refs.c
index 808f56bb27..3e73a0a36d 100644
--- a/refs.c
+++ b/refs.c
@@ -286,6 +286,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
}
struct warn_if_dangling_data {
+ FILE *fp;
const char *refname;
const char *msg_fmt;
};
@@ -304,13 +305,13 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha
if (!resolves_to || strcmp(resolves_to, d->refname))
return 0;
- printf(d->msg_fmt, refname);
+ fprintf(d->fp, d->msg_fmt, refname);
return 0;
}
-void warn_dangling_symref(const char *msg_fmt, const char *refname)
+void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
{
- struct warn_if_dangling_data data = { refname, msg_fmt };
+ struct warn_if_dangling_data data = { fp, refname, msg_fmt };
for_each_rawref(warn_if_dangling_symref, &data);
}
diff --git a/refs.h b/refs.h
index 777b5b7ca6..e141991851 100644
--- a/refs.h
+++ b/refs.h
@@ -29,7 +29,7 @@ extern int for_each_replace_ref(each_ref_fn, void *);
/* can be used to learn about broken ref and symref */
extern int for_each_rawref(each_ref_fn, void *);
-extern void warn_dangling_symref(const char *msg_fmt, const char *refname);
+extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
/*
* Extra refs will be listed by for_each_ref() before any actual refs
diff --git a/remote-curl.c b/remote-curl.c
index 2faf1c6344..a331bae6c8 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -4,23 +4,122 @@
#include "walker.h"
#include "http.h"
#include "exec_cmd.h"
+#include "run-command.h"
+#include "pkt-line.h"
+#include "sideband.h"
-static struct ref *get_refs(struct walker *walker, const char *url)
+static struct remote *remote;
+static const char *url;
+static struct walker *walker;
+
+struct options {
+ int verbosity;
+ unsigned long depth;
+ unsigned progress : 1,
+ followtags : 1,
+ dry_run : 1,
+ thin : 1;
+};
+static struct options options;
+
+static void init_walker(void)
+{
+ if (!walker)
+ walker = get_http_walker(url, remote);
+}
+
+static int set_option(const char *name, const char *value)
+{
+ if (!strcmp(name, "verbosity")) {
+ char *end;
+ int v = strtol(value, &end, 10);
+ if (value == end || *end)
+ return -1;
+ options.verbosity = v;
+ return 0;
+ }
+ else if (!strcmp(name, "progress")) {
+ if (!strcmp(value, "true"))
+ options.progress = 1;
+ else if (!strcmp(value, "false"))
+ options.progress = 0;
+ else
+ return -1;
+ return 0;
+ }
+ else if (!strcmp(name, "depth")) {
+ char *end;
+ unsigned long v = strtoul(value, &end, 10);
+ if (value == end || *end)
+ return -1;
+ options.depth = v;
+ return 0;
+ }
+ else if (!strcmp(name, "followtags")) {
+ if (!strcmp(value, "true"))
+ options.followtags = 1;
+ else if (!strcmp(value, "false"))
+ options.followtags = 0;
+ else
+ return -1;
+ return 0;
+ }
+ else if (!strcmp(name, "dry-run")) {
+ if (!strcmp(value, "true"))
+ options.dry_run = 1;
+ else if (!strcmp(value, "false"))
+ options.dry_run = 0;
+ else
+ return -1;
+ return 0;
+ }
+ else {
+ return 1 /* unsupported */;
+ }
+}
+
+struct discovery {
+ const char *service;
+ char *buf_alloc;
+ char *buf;
+ size_t len;
+ unsigned proto_git : 1;
+};
+static struct discovery *last_discovery;
+
+static void free_discovery(struct discovery *d)
+{
+ if (d) {
+ if (d == last_discovery)
+ last_discovery = NULL;
+ free(d->buf_alloc);
+ free(d);
+ }
+}
+
+static struct discovery* discover_refs(const char *service)
{
struct strbuf buffer = STRBUF_INIT;
- char *data, *start, *mid;
- char *ref_name;
+ struct discovery *last = last_discovery;
char *refs_url;
- int i = 0;
- int http_ret;
+ int http_ret, is_http = 0;
- struct ref *refs = NULL;
- struct ref *ref = NULL;
- struct ref *last_ref = NULL;
+ if (last && !strcmp(service, last->service))
+ return last;
+ free_discovery(last);
- refs_url = xmalloc(strlen(url) + 11);
- sprintf(refs_url, "%s/info/refs", url);
+ strbuf_addf(&buffer, "%s/info/refs", url);
+ if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) {
+ is_http = 1;
+ if (!strchr(url, '?'))
+ strbuf_addch(&buffer, '?');
+ else
+ strbuf_addch(&buffer, '&');
+ strbuf_addf(&buffer, "service=%s", service);
+ }
+ refs_url = strbuf_detach(&buffer, NULL);
+ init_walker();
http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
switch (http_ret) {
case HTTP_OK:
@@ -33,10 +132,86 @@ static struct ref *get_refs(struct walker *walker, const char *url)
die("HTTP request failed");
}
- data = buffer.buf;
+ last= xcalloc(1, sizeof(*last_discovery));
+ last->service = service;
+ last->buf_alloc = strbuf_detach(&buffer, &last->len);
+ last->buf = last->buf_alloc;
+
+ if (is_http && 5 <= last->len && last->buf[4] == '#') {
+ /* smart HTTP response; validate that the service
+ * pkt-line matches our request.
+ */
+ struct strbuf exp = STRBUF_INIT;
+
+ if (packet_get_line(&buffer, &last->buf, &last->len) <= 0)
+ die("%s has invalid packet header", refs_url);
+ if (buffer.len && buffer.buf[buffer.len - 1] == '\n')
+ strbuf_setlen(&buffer, buffer.len - 1);
+
+ strbuf_addf(&exp, "# service=%s", service);
+ if (strbuf_cmp(&exp, &buffer))
+ die("invalid server response; got '%s'", buffer.buf);
+ strbuf_release(&exp);
+
+ /* The header can include additional metadata lines, up
+ * until a packet flush marker. Ignore these now, but
+ * in the future we might start to scan them.
+ */
+ strbuf_reset(&buffer);
+ while (packet_get_line(&buffer, &last->buf, &last->len) > 0)
+ strbuf_reset(&buffer);
+
+ last->proto_git = 1;
+ }
+
+ free(refs_url);
+ strbuf_release(&buffer);
+ last_discovery = last;
+ return last;
+}
+
+static int write_discovery(int fd, void *data)
+{
+ struct discovery *heads = data;
+ int err = 0;
+ if (write_in_full(fd, heads->buf, heads->len) != heads->len)
+ err = 1;
+ close(fd);
+ return err;
+}
+
+static struct ref *parse_git_refs(struct discovery *heads)
+{
+ struct ref *list = NULL;
+ struct async async;
+
+ memset(&async, 0, sizeof(async));
+ async.proc = write_discovery;
+ async.data = heads;
+
+ if (start_async(&async))
+ die("cannot start thread to parse advertised refs");
+ get_remote_heads(async.out, &list, 0, NULL, 0, NULL);
+ close(async.out);
+ if (finish_async(&async))
+ die("ref parsing thread failed");
+ return list;
+}
+
+static struct ref *parse_info_refs(struct discovery *heads)
+{
+ char *data, *start, *mid;
+ char *ref_name;
+ int i = 0;
+
+ struct ref *refs = NULL;
+ struct ref *ref = NULL;
+ struct ref *last_ref = NULL;
+
+ data = heads->buf;
start = NULL;
mid = data;
- while (i < buffer.len) {
+ while (i < heads->len) {
if (!start) {
start = &data[i];
}
@@ -60,8 +235,7 @@ static struct ref *get_refs(struct walker *walker, const char *url)
i++;
}
- strbuf_release(&buffer);
-
+ init_walker();
ref = alloc_ref("HEAD");
if (!walker->fetch_ref(walker, ref) &&
!resolve_remote_symref(ref, refs)) {
@@ -71,25 +245,519 @@ static struct ref *get_refs(struct walker *walker, const char *url)
free(ref);
}
- strbuf_release(&buffer);
- free(refs_url);
return refs;
}
+static struct ref *get_refs(int for_push)
+{
+ struct discovery *heads;
+
+ if (for_push)
+ heads = discover_refs("git-receive-pack");
+ else
+ heads = discover_refs("git-upload-pack");
+
+ if (heads->proto_git)
+ return parse_git_refs(heads);
+ return parse_info_refs(heads);
+}
+
+static void output_refs(struct ref *refs)
+{
+ struct ref *posn;
+ for (posn = refs; posn; posn = posn->next) {
+ if (posn->symref)
+ printf("@%s %s\n", posn->symref, posn->name);
+ else
+ printf("%s %s\n", sha1_to_hex(posn->old_sha1), posn->name);
+ }
+ printf("\n");
+ fflush(stdout);
+ free_refs(refs);
+}
+
+struct rpc_state {
+ const char *service_name;
+ const char **argv;
+ char *service_url;
+ char *hdr_content_type;
+ char *hdr_accept;
+ char *buf;
+ size_t alloc;
+ size_t len;
+ size_t pos;
+ int in;
+ int out;
+ struct strbuf result;
+ unsigned gzip_request : 1;
+};
+
+static size_t rpc_out(void *ptr, size_t eltsize,
+ size_t nmemb, void *buffer_)
+{
+ size_t max = eltsize * nmemb;
+ struct rpc_state *rpc = buffer_;
+ size_t avail = rpc->len - rpc->pos;
+
+ if (!avail) {
+ avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
+ if (!avail)
+ return 0;
+ rpc->pos = 0;
+ rpc->len = avail;
+ }
+
+ if (max < avail)
+ avail = max;
+ memcpy(ptr, rpc->buf + rpc->pos, avail);
+ rpc->pos += avail;
+ return avail;
+}
+
+static size_t rpc_in(const void *ptr, size_t eltsize,
+ size_t nmemb, void *buffer_)
+{
+ size_t size = eltsize * nmemb;
+ struct rpc_state *rpc = buffer_;
+ write_or_die(rpc->in, ptr, size);
+ return size;
+}
+
+static int post_rpc(struct rpc_state *rpc)
+{
+ struct active_request_slot *slot;
+ struct slot_results results;
+ struct curl_slist *headers = NULL;
+ int use_gzip = rpc->gzip_request;
+ char *gzip_body = NULL;
+ int err = 0, large_request = 0;
+
+ /* Try to load the entire request, if we can fit it into the
+ * allocated buffer space we can use HTTP/1.0 and avoid the
+ * chunked encoding mess.
+ */
+ while (1) {
+ size_t left = rpc->alloc - rpc->len;
+ char *buf = rpc->buf + rpc->len;
+ int n;
+
+ if (left < LARGE_PACKET_MAX) {
+ large_request = 1;
+ use_gzip = 0;
+ break;
+ }
+
+ n = packet_read_line(rpc->out, buf, left);
+ if (!n)
+ break;
+ rpc->len += n;
+ }
+
+ slot = get_active_slot();
+ slot->results = &results;
+
+ curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+ curl_easy_setopt(slot->curl, CURLOPT_POST, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);
+ curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "");
+
+ headers = curl_slist_append(headers, rpc->hdr_content_type);
+ headers = curl_slist_append(headers, rpc->hdr_accept);
+
+ if (large_request) {
+ /* The request body is large and the size cannot be predicted.
+ * We must use chunked encoding to send it.
+ */
+ headers = curl_slist_append(headers, "Expect: 100-continue");
+ headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
+ curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc);
+ if (options.verbosity > 1) {
+ fprintf(stderr, "POST %s (chunked)\n", rpc->service_name);
+ fflush(stderr);
+ }
+
+ } else if (use_gzip && 1024 < rpc->len) {
+ /* The client backend isn't giving us compressed data so
+ * we can try to deflate it ourselves, this may save on.
+ * the transfer time.
+ */
+ size_t size;
+ z_stream stream;
+ int ret;
+
+ memset(&stream, 0, sizeof(stream));
+ ret = deflateInit2(&stream, Z_BEST_COMPRESSION,
+ Z_DEFLATED, (15 + 16),
+ 8, Z_DEFAULT_STRATEGY);
+ if (ret != Z_OK)
+ die("cannot deflate request; zlib init error %d", ret);
+ size = deflateBound(&stream, rpc->len);
+ gzip_body = xmalloc(size);
+
+ stream.next_in = (unsigned char *)rpc->buf;
+ stream.avail_in = rpc->len;
+ stream.next_out = (unsigned char *)gzip_body;
+ stream.avail_out = size;
+
+ ret = deflate(&stream, Z_FINISH);
+ if (ret != Z_STREAM_END)
+ die("cannot deflate request; zlib deflate error %d", ret);
+
+ ret = deflateEnd(&stream);
+ if (ret != Z_OK)
+ die("cannot deflate request; zlib end error %d", ret);
+
+ size = stream.total_out;
+
+ headers = curl_slist_append(headers, "Content-Encoding: gzip");
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body);
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, size);
+
+ if (options.verbosity > 1) {
+ fprintf(stderr, "POST %s (gzip %lu to %lu bytes)\n",
+ rpc->service_name,
+ (unsigned long)rpc->len, (unsigned long)size);
+ fflush(stderr);
+ }
+ } else {
+ /* We know the complete request size in advance, use the
+ * more normal Content-Length approach.
+ */
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, rpc->buf);
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, rpc->len);
+ if (options.verbosity > 1) {
+ fprintf(stderr, "POST %s (%lu bytes)\n",
+ rpc->service_name, (unsigned long)rpc->len);
+ fflush(stderr);
+ }
+ }
+
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);
+
+ slot->curl_result = curl_easy_perform(slot->curl);
+ finish_active_slot(slot);
+
+ if (results.curl_result != CURLE_OK) {
+ err |= error("RPC failed; result=%d, HTTP code = %ld",
+ results.curl_result, results.http_code);
+ }
+
+ curl_slist_free_all(headers);
+ free(gzip_body);
+ return err;
+}
+
+static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
+{
+ const char *svc = rpc->service_name;
+ struct strbuf buf = STRBUF_INIT;
+ struct child_process client;
+ int err = 0;
+
+ init_walker();
+ memset(&client, 0, sizeof(client));
+ client.in = -1;
+ client.out = -1;
+ client.git_cmd = 1;
+ client.argv = rpc->argv;
+ if (start_command(&client))
+ exit(1);
+ if (heads)
+ write_or_die(client.in, heads->buf, heads->len);
+
+ rpc->alloc = http_post_buffer;
+ rpc->buf = xmalloc(rpc->alloc);
+ rpc->in = client.in;
+ rpc->out = client.out;
+ strbuf_init(&rpc->result, 0);
+
+ strbuf_addf(&buf, "%s/%s", url, svc);
+ rpc->service_url = strbuf_detach(&buf, NULL);
+
+ 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);
+ rpc->hdr_accept = strbuf_detach(&buf, NULL);
+
+ while (!err) {
+ int n = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
+ if (!n)
+ break;
+ rpc->pos = 0;
+ rpc->len = n;
+ err |= post_rpc(rpc);
+ }
+ strbuf_read(&rpc->result, client.out, 0);
+
+ close(client.in);
+ close(client.out);
+ client.in = -1;
+ client.out = -1;
+
+ err |= finish_command(&client);
+ free(rpc->service_url);
+ free(rpc->hdr_content_type);
+ free(rpc->hdr_accept);
+ free(rpc->buf);
+ strbuf_release(&buf);
+ return err;
+}
+
+static int fetch_dumb(int nr_heads, struct ref **to_fetch)
+{
+ char **targets = xmalloc(nr_heads * sizeof(char*));
+ int ret, i;
+
+ if (options.depth)
+ die("dumb http transport does not support --depth");
+ for (i = 0; i < nr_heads; i++)
+ targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1));
+
+ init_walker();
+ walker->get_all = 1;
+ walker->get_tree = 1;
+ walker->get_history = 1;
+ walker->get_verbosely = options.verbosity >= 3;
+ walker->get_recover = 0;
+ ret = walker_fetch(walker, nr_heads, targets, NULL, NULL);
+
+ for (i = 0; i < nr_heads; i++)
+ free(targets[i]);
+ free(targets);
+
+ return ret ? error("Fetch failed.") : 0;
+}
+
+static int fetch_git(struct discovery *heads,
+ int nr_heads, struct ref **to_fetch)
+{
+ struct rpc_state rpc;
+ char *depth_arg = NULL;
+ const char **argv;
+ int argc = 0, i, err;
+
+ argv = xmalloc((15 + nr_heads) * sizeof(char*));
+ argv[argc++] = "fetch-pack";
+ argv[argc++] = "--stateless-rpc";
+ argv[argc++] = "--lock-pack";
+ if (options.followtags)
+ argv[argc++] = "--include-tag";
+ if (options.thin)
+ argv[argc++] = "--thin";
+ if (options.verbosity >= 3) {
+ argv[argc++] = "-v";
+ argv[argc++] = "-v";
+ }
+ if (!options.progress)
+ argv[argc++] = "--no-progress";
+ if (options.depth) {
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addf(&buf, "--depth=%lu", options.depth);
+ depth_arg = strbuf_detach(&buf, NULL);
+ argv[argc++] = depth_arg;
+ }
+ argv[argc++] = url;
+ for (i = 0; i < nr_heads; i++) {
+ struct ref *ref = to_fetch[i];
+ if (!ref->name || !*ref->name)
+ die("cannot fetch by sha1 over smart http");
+ argv[argc++] = ref->name;
+ }
+ argv[argc++] = NULL;
+
+ memset(&rpc, 0, sizeof(rpc));
+ rpc.service_name = "git-upload-pack",
+ rpc.argv = argv;
+ rpc.gzip_request = 1;
+
+ err = rpc_service(&rpc, heads);
+ if (rpc.result.len)
+ safe_write(1, rpc.result.buf, rpc.result.len);
+ strbuf_release(&rpc.result);
+ free(argv);
+ free(depth_arg);
+ return err;
+}
+
+static int fetch(int nr_heads, struct ref **to_fetch)
+{
+ struct discovery *d = discover_refs("git-upload-pack");
+ if (d->proto_git)
+ return fetch_git(d, nr_heads, to_fetch);
+ else
+ return fetch_dumb(nr_heads, to_fetch);
+}
+
+static void parse_fetch(struct strbuf *buf)
+{
+ struct ref **to_fetch = NULL;
+ struct ref *list_head = NULL;
+ struct ref **list = &list_head;
+ int alloc_heads = 0, nr_heads = 0;
+
+ do {
+ if (!prefixcmp(buf->buf, "fetch ")) {
+ char *p = buf->buf + strlen("fetch ");
+ char *name;
+ struct ref *ref;
+ unsigned char old_sha1[20];
+
+ if (strlen(p) < 40 || get_sha1_hex(p, old_sha1))
+ die("protocol error: expected sha/ref, got %s'", p);
+ if (p[40] == ' ')
+ name = p + 41;
+ else if (!p[40])
+ name = "";
+ else
+ die("protocol error: expected sha/ref, got %s'", p);
+
+ ref = alloc_ref(name);
+ hashcpy(ref->old_sha1, old_sha1);
+
+ *list = ref;
+ list = &ref->next;
+
+ ALLOC_GROW(to_fetch, nr_heads + 1, alloc_heads);
+ to_fetch[nr_heads++] = ref;
+ }
+ else
+ die("http transport does not support %s", buf->buf);
+
+ strbuf_reset(buf);
+ if (strbuf_getline(buf, stdin, '\n') == EOF)
+ return;
+ if (!*buf->buf)
+ break;
+ } while (1);
+
+ if (fetch(nr_heads, to_fetch))
+ exit(128); /* error already reported */
+ free_refs(list_head);
+ free(to_fetch);
+
+ printf("\n");
+ fflush(stdout);
+ strbuf_reset(buf);
+}
+
+static int push_dav(int nr_spec, char **specs)
+{
+ const char **argv = xmalloc((10 + nr_spec) * sizeof(char*));
+ int argc = 0, i;
+
+ argv[argc++] = "http-push";
+ argv[argc++] = "--helper-status";
+ if (options.dry_run)
+ argv[argc++] = "--dry-run";
+ if (options.verbosity > 1)
+ argv[argc++] = "--verbose";
+ argv[argc++] = url;
+ for (i = 0; i < nr_spec; i++)
+ argv[argc++] = specs[i];
+ argv[argc++] = NULL;
+
+ if (run_command_v_opt(argv, RUN_GIT_CMD))
+ die("git-%s failed", argv[0]);
+ free(argv);
+ return 0;
+}
+
+static int push_git(struct discovery *heads, int nr_spec, char **specs)
+{
+ struct rpc_state rpc;
+ const char **argv;
+ int argc = 0, i, err;
+
+ argv = xmalloc((10 + nr_spec) * sizeof(char*));
+ argv[argc++] = "send-pack";
+ argv[argc++] = "--stateless-rpc";
+ argv[argc++] = "--helper-status";
+ if (options.thin)
+ argv[argc++] = "--thin";
+ if (options.dry_run)
+ argv[argc++] = "--dry-run";
+ if (options.verbosity > 1)
+ argv[argc++] = "--verbose";
+ argv[argc++] = url;
+ for (i = 0; i < nr_spec; i++)
+ argv[argc++] = specs[i];
+ argv[argc++] = NULL;
+
+ memset(&rpc, 0, sizeof(rpc));
+ rpc.service_name = "git-receive-pack",
+ rpc.argv = argv;
+
+ err = rpc_service(&rpc, heads);
+ if (rpc.result.len)
+ safe_write(1, rpc.result.buf, rpc.result.len);
+ strbuf_release(&rpc.result);
+ free(argv);
+ return err;
+}
+
+static int push(int nr_spec, char **specs)
+{
+ struct discovery *heads = discover_refs("git-receive-pack");
+ int ret;
+
+ if (heads->proto_git)
+ ret = push_git(heads, nr_spec, specs);
+ else
+ ret = push_dav(nr_spec, specs);
+ free_discovery(heads);
+ return ret;
+}
+
+static void parse_push(struct strbuf *buf)
+{
+ char **specs = NULL;
+ int alloc_spec = 0, nr_spec = 0, i;
+
+ do {
+ if (!prefixcmp(buf->buf, "push ")) {
+ ALLOC_GROW(specs, nr_spec + 1, alloc_spec);
+ specs[nr_spec++] = xstrdup(buf->buf + 5);
+ }
+ else
+ die("http transport does not support %s", buf->buf);
+
+ strbuf_reset(buf);
+ if (strbuf_getline(buf, stdin, '\n') == EOF)
+ return;
+ if (!*buf->buf)
+ break;
+ } while (1);
+
+ if (push(nr_spec, specs))
+ exit(128); /* error already reported */
+ for (i = 0; i < nr_spec; i++)
+ free(specs[i]);
+ free(specs);
+
+ printf("\n");
+ fflush(stdout);
+}
+
int main(int argc, const char **argv)
{
- struct remote *remote;
struct strbuf buf = STRBUF_INIT;
- const char *url;
- struct walker *walker = NULL;
+ int nongit;
git_extract_argv0_path(argv[0]);
- setup_git_directory();
+ setup_git_directory_gently(&nongit);
if (argc < 2) {
fprintf(stderr, "Remote needed\n");
return 1;
}
+ options.verbosity = 1;
+ options.progress = !!isatty(2);
+ options.thin = 1;
+
remote = remote_get(argv[1]);
if (argc > 2) {
@@ -102,34 +770,40 @@ int main(int argc, const char **argv)
if (strbuf_getline(&buf, stdin, '\n') == EOF)
break;
if (!prefixcmp(buf.buf, "fetch ")) {
- char *obj = buf.buf + strlen("fetch ");
- if (!walker)
- walker = get_http_walker(url, remote);
- walker->get_all = 1;
- walker->get_tree = 1;
- walker->get_history = 1;
- walker->get_verbosely = 0;
- walker->get_recover = 0;
- if (walker_fetch(walker, 1, &obj, NULL, NULL))
- die("Fetch failed.");
- printf("\n");
- fflush(stdout);
- } else if (!strcmp(buf.buf, "list")) {
- struct ref *refs;
- struct ref *posn;
- if (!walker)
- walker = get_http_walker(url, remote);
- refs = get_refs(walker, url);
- for (posn = refs; posn; posn = posn->next) {
- if (posn->symref)
- printf("@%s %s\n", posn->symref, posn->name);
- else
- printf("%s %s\n", sha1_to_hex(posn->old_sha1), posn->name);
- }
- printf("\n");
+ if (nongit)
+ die("Fetch attempted without a local repo");
+ parse_fetch(&buf);
+
+ } else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) {
+ int for_push = !!strstr(buf.buf + 4, "for-push");
+ output_refs(get_refs(for_push));
+
+ } else if (!prefixcmp(buf.buf, "push ")) {
+ parse_push(&buf);
+
+ } else if (!prefixcmp(buf.buf, "option ")) {
+ char *name = buf.buf + strlen("option ");
+ char *value = strchr(name, ' ');
+ int result;
+
+ if (value)
+ *value++ = '\0';
+ else
+ value = "true";
+
+ result = set_option(name, value);
+ if (!result)
+ printf("ok\n");
+ else if (result < 0)
+ printf("error invalid value\n");
+ else
+ printf("unsupported\n");
fflush(stdout);
+
} else if (!strcmp(buf.buf, "capabilities")) {
printf("fetch\n");
+ printf("option\n");
+ printf("push\n");
printf("\n");
fflush(stdout);
} else {
diff --git a/remote.c b/remote.c
index 73d33f2584..b979a9642b 100644
--- a/remote.c
+++ b/remote.c
@@ -6,6 +6,7 @@
#include "revision.h"
#include "dir.h"
#include "tag.h"
+#include "string-list.h"
static struct refspec s_tag_refspec = {
0,
@@ -396,7 +397,8 @@ static int handle_config(const char *key, const char *value, void *cb)
remote->mirror = git_config_bool(key, value);
else if (!strcmp(subkey, ".skipdefaultupdate"))
remote->skip_default_update = git_config_bool(key, value);
-
+ else if (!strcmp(subkey, ".skipfetchall"))
+ remote->skip_default_update = git_config_bool(key, value);
else if (!strcmp(subkey, ".url")) {
const char *v;
if (git_config_string(&v, key, value))
@@ -734,29 +736,33 @@ int for_each_remote(each_remote_fn fn, void *priv)
void ref_remove_duplicates(struct ref *ref_map)
{
- struct ref **posn;
- struct ref *next;
- for (; ref_map; ref_map = ref_map->next) {
+ struct string_list refs = { NULL, 0, 0, 0 };
+ struct string_list_item *item = NULL;
+ struct ref *prev = NULL, *next = NULL;
+ for (; ref_map; prev = ref_map, ref_map = next) {
+ next = ref_map->next;
if (!ref_map->peer_ref)
continue;
- posn = &ref_map->next;
- while (*posn) {
- if ((*posn)->peer_ref &&
- !strcmp((*posn)->peer_ref->name,
- ref_map->peer_ref->name)) {
- if (strcmp((*posn)->name, ref_map->name))
- die("%s tracks both %s and %s",
- ref_map->peer_ref->name,
- (*posn)->name, ref_map->name);
- next = (*posn)->next;
- free((*posn)->peer_ref);
- free(*posn);
- *posn = next;
- } else {
- posn = &(*posn)->next;
- }
+
+ item = string_list_lookup(ref_map->peer_ref->name, &refs);
+ if (item) {
+ if (strcmp(((struct ref *)item->util)->name,
+ ref_map->name))
+ die("%s tracks both %s and %s",
+ ref_map->peer_ref->name,
+ ((struct ref *)item->util)->name,
+ ref_map->name);
+ prev->next = ref_map->next;
+ free(ref_map->peer_ref);
+ free(ref_map);
+ ref_map = prev; /* skip this; we freed it */
+ continue;
}
+
+ item = string_list_insert(ref_map->peer_ref->name, &refs);
+ item->util = ref_map;
}
+ string_list_clear(&refs, 0);
}
int remote_has_url(struct remote *remote, const char *url)
@@ -1586,3 +1592,42 @@ struct ref *guess_remote_head(const struct ref *head,
return list;
}
+
+struct stale_heads_info {
+ struct remote *remote;
+ struct string_list *ref_names;
+ struct ref **stale_refs_tail;
+};
+
+static int get_stale_heads_cb(const char *refname,
+ const unsigned char *sha1, int flags, void *cb_data)
+{
+ struct stale_heads_info *info = cb_data;
+ struct refspec refspec;
+ memset(&refspec, 0, sizeof(refspec));
+ refspec.dst = (char *)refname;
+ if (!remote_find_tracking(info->remote, &refspec)) {
+ if (!((flags & REF_ISSYMREF) ||
+ string_list_has_string(info->ref_names, refspec.src))) {
+ struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
+ hashcpy(ref->new_sha1, sha1);
+ }
+ }
+ return 0;
+}
+
+struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map)
+{
+ struct ref *ref, *stale_refs = NULL;
+ struct string_list ref_names = { NULL, 0, 0, 0 };
+ struct stale_heads_info info;
+ info.remote = remote;
+ info.ref_names = &ref_names;
+ info.stale_refs_tail = &stale_refs;
+ for (ref = fetch_map; ref; ref = ref->next)
+ string_list_append(ref->name, &ref_names);
+ sort_string_list(&ref_names);
+ for_each_ref(get_stale_heads_cb, &info);
+ string_list_clear(&ref_names, 0);
+ return stale_refs;
+}
diff --git a/remote.h b/remote.h
index 5db842087d..d0aba81ead 100644
--- a/remote.h
+++ b/remote.h
@@ -154,4 +154,7 @@ struct ref *guess_remote_head(const struct ref *head,
const struct ref *refs,
int all);
+/* Return refs which no longer exist on remote */
+struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map);
+
#endif
diff --git a/revision.c b/revision.c
index 8750c20e07..a8a3c3a4bd 100644
--- a/revision.c
+++ b/revision.c
@@ -1032,7 +1032,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
if (!strcmp(arg, "--all") || !strcmp(arg, "--branches") ||
!strcmp(arg, "--tags") || !strcmp(arg, "--remotes") ||
!strcmp(arg, "--reflog") || !strcmp(arg, "--not") ||
- !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk"))
+ !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") ||
+ !strcmp(arg, "--bisect"))
{
unkv[(*unkc)++] = arg;
return 1;
@@ -1256,6 +1257,16 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
ctx->argc -= n;
}
+static int for_each_bad_bisect_ref(each_ref_fn fn, void *cb_data)
+{
+ return for_each_ref_in("refs/bisect/bad", fn, cb_data);
+}
+
+static int for_each_good_bisect_ref(each_ref_fn fn, void *cb_data)
+{
+ return for_each_ref_in("refs/bisect/good", fn, cb_data);
+}
+
static void append_prune_data(const char ***prune_data, const char **av)
{
const char **prune = *prune_data;
@@ -1327,6 +1338,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
handle_refs(revs, flags, for_each_branch_ref);
continue;
}
+ if (!strcmp(arg, "--bisect")) {
+ handle_refs(revs, flags, for_each_bad_bisect_ref);
+ handle_refs(revs, flags ^ UNINTERESTING, for_each_good_bisect_ref);
+ revs->bisect = 1;
+ continue;
+ }
if (!strcmp(arg, "--tags")) {
handle_refs(revs, flags, for_each_tag_ref);
continue;
diff --git a/revision.h b/revision.h
index f64637bcbd..d368003159 100644
--- a/revision.h
+++ b/revision.h
@@ -63,6 +63,7 @@ struct rev_info {
reverse:1,
reverse_output_stage:1,
cherry_pick:1,
+ bisect:1,
first_parent_only:1;
/* Diff flags */
diff --git a/send-pack.h b/send-pack.h
index 8b3cf028ed..28141ac913 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -8,7 +8,8 @@ struct send_pack_args {
force_update:1,
use_thin_pack:1,
use_ofs_delta:1,
- dry_run:1;
+ dry_run:1,
+ stateless_rpc:1;
};
int send_pack(struct send_pack_args *args,
diff --git a/show-index.c b/show-index.c
index 45bb535773..63f9da5323 100644
--- a/show-index.c
+++ b/show-index.c
@@ -1,6 +1,9 @@
#include "cache.h"
#include "pack.h"
+static const char show_index_usage[] =
+"git show-index < <packed archive index>";
+
int main(int argc, char **argv)
{
int i;
@@ -8,6 +11,8 @@ int main(int argc, char **argv)
unsigned int version;
static unsigned int top_index[256];
+ if (argc != 1)
+ usage(show_index_usage);
if (fread(top_index, 2 * 4, 1, stdin) != 1)
die("unable to read header");
if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) {
diff --git a/sideband.c b/sideband.c
index 899b1ff366..d5ffa1c891 100644
--- a/sideband.c
+++ b/sideband.c
@@ -135,9 +135,14 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet
n = sz;
if (packet_max - 5 < n)
n = packet_max - 5;
- sprintf(hdr, "%04x", n + 5);
- hdr[4] = band;
- safe_write(fd, hdr, 5);
+ if (0 <= band) {
+ sprintf(hdr, "%04x", n + 5);
+ hdr[4] = band;
+ safe_write(fd, hdr, 5);
+ } else {
+ sprintf(hdr, "%04x", n + 4);
+ safe_write(fd, hdr, 4);
+ }
safe_write(fd, p, n);
p += n;
sz -= n;
diff --git a/strbuf.h b/strbuf.h
index d05e056dd3..fa07ecf094 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -117,7 +117,7 @@ struct strbuf_expand_dict_entry {
};
extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context);
-__attribute__((format(printf,2,3)))
+__attribute__((format (printf,2,3)))
extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
diff --git a/submodule.c b/submodule.c
index 461faf0e00..86aad653b7 100644
--- a/submodule.c
+++ b/submodule.c
@@ -38,7 +38,7 @@ void show_submodule_summary(FILE *f, const char *path,
const char *del, const char *add, const char *reset)
{
struct rev_info rev;
- struct commit *commit, *left = left, *right;
+ struct commit *commit, *left = left, *right = right;
struct commit_list *merge_bases, *list;
const char *message = NULL;
struct strbuf sb = STRBUF_INIT;
diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh
index 845253274b..76d8b7b803 100644
--- a/t/gitweb-lib.sh
+++ b/t/gitweb-lib.sh
@@ -52,10 +52,24 @@ gitweb_run () {
rm -f gitweb.log &&
perl -- "$SCRIPT_NAME" \
>gitweb.output 2>gitweb.log &&
+ perl -w -e '
+ open O, ">gitweb.headers";
+ while (<>) {
+ print O;
+ last if (/^\r$/ || /^$/);
+ }
+ open O, ">gitweb.body";
+ while (<>) {
+ print O;
+ }
+ close O;
+ ' gitweb.output &&
if grep '^[[]' gitweb.log >/dev/null 2>&1; then false; else true; fi
# gitweb.log is left for debugging
- # gitweb.output is used to parse http output
+ # gitweb.output is used to parse HTTP output
+ # gitweb.headers contains only HTTP headers
+ # gitweb.body contains body of message, without headers
}
. ./test-lib.sh
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf
index 21aa42f1c6..0fe3fd0d01 100644
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
@@ -8,6 +8,28 @@ ErrorLog error.log
<IfModule !mod_log_config.c>
LoadModule log_config_module modules/mod_log_config.so
</IfModule>
+<IfModule !mod_alias.c>
+ LoadModule alias_module modules/mod_alias.so
+</IfModule>
+<IfModule !mod_cgi.c>
+ LoadModule cgi_module modules/mod_cgi.so
+</IfModule>
+<IfModule !mod_env.c>
+ LoadModule env_module modules/mod_env.so
+</IfModule>
+
+Alias /dumb/ www/
+
+<Location /smart/>
+ SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
+</Location>
+ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/
+<Directory ${GIT_EXEC_PATH}>
+ Options None
+</Directory>
+<Files ${GIT_EXEC_PATH}/git-http-backend>
+ Options ExecCGI
+</Files>
<IfDefine SSL>
LoadModule ssl_module modules/mod_ssl.so
@@ -26,7 +48,7 @@ SSLEngine On
LoadModule dav_fs_module modules/mod_dav_fs.so
DAVLockDB DAVLock
- <Location />
+ <Location /dumb/>
Dav on
</Location>
</IfDefine>
diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh
index 271bc4e17f..c2d408b461 100755
--- a/t/t1001-read-tree-m-2way.sh
+++ b/t/t1001-read-tree-m-2way.sh
@@ -5,7 +5,7 @@
test_description='Two way merge with read-tree -m $H $M
-This test tries two-way merge (aka fast forward with carry forward).
+This test tries two-way merge (aka fast-forward with carry forward).
There is the head (called H) and another commit (called M), which is
simply ahead of H. The index and the work tree contains a state that
@@ -51,7 +51,7 @@ check_cache_at () {
}
cat >bozbar-old <<\EOF
-This is a sample file used in two-way fast forward merge
+This is a sample file used in two-way fast-forward merge
tests. Its second line ends with a magic word bozbar
which will be modified by the merged head to gnusto.
It has some extra lines so that external tools can
@@ -300,7 +300,7 @@ test_expect_success \
echo gnusto gnusto >bozbar &&
if read_tree_twoway $treeH $treeM; then false; else :; fi'
-# This fails with straight two-way fast forward.
+# This fails with straight two-way fast-forward.
test_expect_success \
'22 - local change cache updated.' \
'rm -f .git/index &&
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
index 67e637b781..6bf84755f3 100755
--- a/t/t1200-tutorial.sh
+++ b/t/t1200-tutorial.sh
@@ -7,14 +7,18 @@ test_description='A simple turial in the form of a test case'
. ./test-lib.sh
-echo "Hello World" > hello
-echo "Silly example" > example
+test_expect_success 'blob' '
+ echo "Hello World" > hello &&
+ echo "Silly example" > example &&
-git update-index --add hello example
+ git update-index --add hello example &&
-test_expect_success 'blob' "test blob = \"$(git cat-file -t 557db03)\""
+ test blob = "$(git cat-file -t 557db03)"
+'
-test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git cat-file blob 557db03)\""
+test_expect_success 'blob 557db03' '
+ test "Hello World" = "$(git cat-file blob 557db03)"
+'
echo "It's a new day for git" >>hello
cat > diff.expect << EOF
@@ -26,25 +30,34 @@ index 557db03..263414f 100644
Hello World
+It's a new day for git
EOF
-git diff-files -p > diff.output
-test_expect_success 'git diff-files -p' 'cmp diff.expect diff.output'
-git diff > diff.output
-test_expect_success 'git diff' 'cmp diff.expect diff.output'
-
-tree=$(git write-tree 2>/dev/null)
-test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tree"
+test_expect_success 'git diff-files -p' '
+ git diff-files -p > diff.output &&
+ test_cmp diff.expect diff.output
+'
-output="$(echo "Initial commit" | git commit-tree $(git write-tree) 2>&1 > .git/refs/heads/master)"
+test_expect_success 'git diff' '
+ git diff > diff.output &&
+ test_cmp diff.expect diff.output
+'
-git diff-index -p HEAD > diff.output
-test_expect_success 'git diff-index -p HEAD' 'cmp diff.expect diff.output'
+test_expect_success 'tree' '
+ tree=$(git write-tree 2>/dev/null)
+ test 8988da15d077d4829fc51d8544c097def6644dbb = $tree
+'
-git diff HEAD > diff.output
-test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output'
+test_expect_success 'git diff-index -p HEAD' '
+ tree=$(git write-tree)
+ commit=$(echo "Initial commit" | git commit-tree $tree) &&
+ git update-ref HEAD $commit &&
+ git diff-index -p HEAD > diff.output &&
+ test_cmp diff.expect diff.output
+'
-#rm hello
-#test_expect_success 'git read-tree --reset HEAD' "git read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git update-index --refresh)\""
+test_expect_success 'git diff HEAD' '
+ git diff HEAD > diff.output &&
+ test_cmp diff.expect diff.output
+'
cat > whatchanged.expect << EOF
commit VARIABLE
@@ -69,39 +82,45 @@ index 0000000..557db03
+Hello World
EOF
-git whatchanged -p --root | \
- sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \
+test_expect_success 'git whatchanged -p --root' '
+ git whatchanged -p --root |
+ sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \
-e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \
-> whatchanged.output
-test_expect_success 'git whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
-
-git tag my-first-tag
-test_expect_success 'git tag my-first-tag' 'cmp .git/refs/heads/master .git/refs/tags/my-first-tag'
+ > whatchanged.output &&
+ test_cmp whatchanged.expect whatchanged.output
+'
-# TODO: test git clone
+test_expect_success 'git tag my-first-tag' '
+ git tag my-first-tag &&
+ test_cmp .git/refs/heads/master .git/refs/tags/my-first-tag
+'
-git checkout -b mybranch
-test_expect_success 'git checkout -b mybranch' 'cmp .git/refs/heads/master .git/refs/heads/mybranch'
+test_expect_success 'git checkout -b mybranch' '
+ git checkout -b mybranch &&
+ test_cmp .git/refs/heads/master .git/refs/heads/mybranch
+'
cat > branch.expect <<EOF
master
* mybranch
EOF
-git branch > branch.output
-test_expect_success 'git branch' 'cmp branch.expect branch.output'
+test_expect_success 'git branch' '
+ git branch > branch.output &&
+ test_cmp branch.expect branch.output
+'
-git checkout mybranch
-echo "Work, work, work" >>hello
-git commit -m 'Some work.' -i hello
+test_expect_success 'git resolve now fails' '
+ git checkout mybranch &&
+ echo "Work, work, work" >>hello &&
+ git commit -m "Some work." -i hello &&
-git checkout master
+ git checkout master &&
-echo "Play, play, play" >>hello
-echo "Lots of fun" >>example
-git commit -m 'Some fun.' -i hello example
+ echo "Play, play, play" >>hello &&
+ echo "Lots of fun" >>example &&
+ git commit -m "Some fun." -i hello example &&
-test_expect_success 'git resolve now fails' '
test_must_fail git merge -m "Merge work in mybranch" mybranch
'
@@ -112,52 +131,131 @@ Play, play, play
Work, work, work
EOF
-git commit -m 'Merged "mybranch" changes.' -i hello
-
-test_done
-
cat > show-branch.expect << EOF
-* [master] Merged "mybranch" changes.
+* [master] Merge work in mybranch
! [mybranch] Some work.
--
-- [master] Merged "mybranch" changes.
+- [master] Merge work in mybranch
*+ [mybranch] Some work.
+* [master^] Some fun.
EOF
-git show-branch --topo-order master mybranch > show-branch.output
-test_expect_success 'git show-branch' 'cmp show-branch.expect show-branch.output'
-
-git checkout mybranch
+test_expect_success 'git show-branch' '
+ git commit -m "Merge work in mybranch" -i hello &&
+ git show-branch --topo-order --more=1 master mybranch \
+ > show-branch.output &&
+ test_cmp show-branch.expect show-branch.output
+'
cat > resolve.expect << EOF
-Updating from VARIABLE to VARIABLE
+Updating VARIABLE..VARIABLE
+FASTFORWARD (no commit created; -m option ignored)
example | 1 +
hello | 1 +
2 files changed, 2 insertions(+), 0 deletions(-)
EOF
-git merge -s "Merge upstream changes." master | \
- sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" >resolve.output
-test_expect_success 'git resolve' 'cmp resolve.expect resolve.output'
+test_expect_success 'git resolve' '
+ git checkout mybranch &&
+ git merge -m "Merge upstream changes." master |
+ sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" \
+ -e "s/^Fast[- ]forward /FASTFORWARD /" >resolve.output &&
+ test_cmp resolve.expect resolve.output
+'
cat > show-branch2.expect << EOF
-! [master] Merged "mybranch" changes.
- * [mybranch] Merged "mybranch" changes.
+! [master] Merge work in mybranch
+ * [mybranch] Merge work in mybranch
+--
+-- [master] Merge work in mybranch
+EOF
+
+test_expect_success 'git show-branch (part 2)' '
+ git show-branch --topo-order master mybranch > show-branch2.output &&
+ test_cmp show-branch2.expect show-branch2.output
+'
+
+cat > show-branch3.expect << EOF
+! [master] Merge work in mybranch
+ * [mybranch] Merge work in mybranch
+--
+-- [master] Merge work in mybranch
++* [master^2] Some work.
++* [master^] Some fun.
+EOF
+
+test_expect_success 'git show-branch (part 3)' '
+ git show-branch --topo-order --more=2 master mybranch \
+ > show-branch3.output &&
+ test_cmp show-branch3.expect show-branch3.output
+'
+
+test_expect_success 'rewind to "Some fun." and "Some work."' '
+ git checkout mybranch &&
+ git reset --hard master^2 &&
+ git checkout master &&
+ git reset --hard master^
+'
+
+cat > show-branch4.expect << EOF
+* [master] Some fun.
+ ! [mybranch] Some work.
--
--- [master] Merged "mybranch" changes.
+ + [mybranch] Some work.
+* [master] Some fun.
+*+ [mybranch^] Initial commit
+EOF
+
+test_expect_success 'git show-branch (part 4)' '
+ git show-branch --topo-order > show-branch4.output &&
+ test_cmp show-branch4.expect show-branch4.output
+'
+
+test_expect_success 'manual merge' '
+ mb=$(git merge-base HEAD mybranch) &&
+ git name-rev --name-only --tags $mb > name-rev.output &&
+ test "my-first-tag" = $(cat name-rev.output) &&
+
+ git read-tree -m -u $mb HEAD mybranch
+'
+
+cat > ls-files.expect << EOF
+100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example
+100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello
+100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello
+100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
+EOF
+
+test_expect_success 'git ls-files --stage' '
+ git ls-files --stage > ls-files.output &&
+ test_cmp ls-files.expect ls-files.output
+'
+
+cat > ls-files-unmerged.expect << EOF
+100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello
+100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello
+100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
EOF
-git show-branch --topo-order master mybranch > show-branch2.output
-test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.output'
+test_expect_success 'git ls-files --unmerged' '
+ git ls-files --unmerged > ls-files-unmerged.output &&
+ test_cmp ls-files-unmerged.expect ls-files-unmerged.output
+'
-# TODO: test git fetch
+test_expect_success 'git-merge-index' '
+ test_must_fail git merge-index git-merge-one-file hello
+'
-# TODO: test git push
+test_expect_success 'git ls-files --stage (part 2)' '
+ git ls-files --stage > ls-files.output2 &&
+ test_cmp ls-files.expect ls-files.output2
+'
test_expect_success 'git repack' 'git repack'
test_expect_success 'git prune-packed' 'git prune-packed'
test_expect_success '-> only packed objects' '
- ! find -type f .git/objects/[0-9a-f][0-9a-f]
+ git prune && # Remove conflict marked blobs
+ ! find .git/objects/[0-9a-f][0-9a-f] -type f
'
test_done
diff --git a/t/t3003-ls-files-exclude.sh b/t/t3003-ls-files-exclude.sh
index fc1e379321..d5ec333131 100755
--- a/t/t3003-ls-files-exclude.sh
+++ b/t/t3003-ls-files-exclude.sh
@@ -29,4 +29,12 @@ test_expect_success 'add file to gitignore' '
'
check_all_output
+test_expect_success 'ls-files -i lists only tracked-but-ignored files' '
+ echo content >other-file &&
+ git add other-file &&
+ echo file >expect &&
+ git ls-files -i --exclude-standard >output &&
+ test_cmp expect output
+'
+
test_done
diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh
index 51cb4a30f5..8be9fb4112 100755
--- a/t/t3101-ls-tree-dirname.sh
+++ b/t/t3101-ls-tree-dirname.sh
@@ -39,8 +39,8 @@ test_expect_success \
tree=`git write-tree` &&
echo $tree'
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+_x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
test_output () {
sed -e "s/ $_x40 / X /" <current >check
test_cmp expected check
@@ -141,4 +141,89 @@ test_expect_success 'ls-tree filter is leading path match' '
test_output
'
+test_expect_success 'ls-tree --full-name' '
+ (
+ cd path0 &&
+ git ls-tree --full-name $tree a
+ ) >current &&
+ cat >expected <<\EOF &&
+040000 tree X path0/a
+EOF
+ test_output
+'
+
+test_expect_success 'ls-tree --full-tree' '
+ (
+ cd path1/b/c &&
+ git ls-tree --full-tree $tree
+ ) >current &&
+ cat >expected <<\EOF &&
+100644 blob X 1.txt
+100644 blob X 2.txt
+040000 tree X path0
+040000 tree X path1
+040000 tree X path2
+040000 tree X path3
+EOF
+ test_output
+'
+
+test_expect_success 'ls-tree --full-tree -r' '
+ (
+ cd path3/ &&
+ git ls-tree --full-tree -r $tree
+ ) >current &&
+ cat >expected <<\EOF &&
+100644 blob X 1.txt
+100644 blob X 2.txt
+100644 blob X path0/a/b/c/1.txt
+100644 blob X path1/b/c/1.txt
+100644 blob X path2/1.txt
+100644 blob X path3/1.txt
+100644 blob X path3/2.txt
+EOF
+ test_output
+'
+
+test_expect_success 'ls-tree --abbrev=5' '
+ git ls-tree --abbrev=5 $tree >current &&
+ sed -e "s/ $_x05[0-9a-f]* / X /" <current >check &&
+ cat >expected <<\EOF &&
+100644 blob X 1.txt
+100644 blob X 2.txt
+040000 tree X path0
+040000 tree X path1
+040000 tree X path2
+040000 tree X path3
+EOF
+ test_cmp expected check
+'
+
+test_expect_success 'ls-tree --name-only' '
+ git ls-tree --name-only $tree >current
+ cat >expected <<\EOF &&
+1.txt
+2.txt
+path0
+path1
+path2
+path3
+EOF
+ test_output
+'
+
+test_expect_success 'ls-tree --name-only -r' '
+ git ls-tree --name-only -r $tree >current
+ cat >expected <<\EOF &&
+1.txt
+2.txt
+path0/a/b/c/1.txt
+path1/b/c/1.txt
+path2/1.txt
+path3/1.txt
+path3/2.txt
+EOF
+ test_output
+'
+
test_done
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
new file mode 100755
index 0000000000..1e34f4836f
--- /dev/null
+++ b/t/t3301-notes.sh
@@ -0,0 +1,150 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='Test commit notes'
+
+. ./test-lib.sh
+
+cat > fake_editor.sh << \EOF
+echo "$MSG" > "$1"
+echo "$MSG" >& 2
+EOF
+chmod a+x fake_editor.sh
+VISUAL=./fake_editor.sh
+export VISUAL
+
+test_expect_success 'cannot annotate non-existing HEAD' '
+ (MSG=3 && export MSG && test_must_fail git notes edit)
+'
+
+test_expect_success setup '
+ : > a1 &&
+ git add a1 &&
+ test_tick &&
+ git commit -m 1st &&
+ : > a2 &&
+ git add a2 &&
+ test_tick &&
+ git commit -m 2nd
+'
+
+test_expect_success 'need valid notes ref' '
+ (MSG=1 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF &&
+ test_must_fail git notes edit) &&
+ (MSG=2 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF &&
+ test_must_fail git notes show)
+'
+
+test_expect_success 'refusing to edit in refs/heads/' '
+ (MSG=1 GIT_NOTES_REF=refs/heads/bogus &&
+ export MSG GIT_NOTES_REF &&
+ test_must_fail git notes edit)
+'
+
+test_expect_success 'refusing to edit in refs/remotes/' '
+ (MSG=1 GIT_NOTES_REF=refs/remotes/bogus &&
+ export MSG GIT_NOTES_REF &&
+ test_must_fail git notes edit)
+'
+
+# 1 indicates caught gracefully by die, 128 means git-show barked
+test_expect_success 'handle empty notes gracefully' '
+ git notes show ; test 1 = $?
+'
+
+test_expect_success 'create notes' '
+ git config core.notesRef refs/notes/commits &&
+ MSG=b1 git notes edit &&
+ test ! -f .git/new-notes &&
+ test 1 = $(git ls-tree refs/notes/commits | wc -l) &&
+ test b1 = $(git notes show) &&
+ git show HEAD^ &&
+ test_must_fail git notes show HEAD^
+'
+
+cat > expect << EOF
+commit 268048bfb8a1fb38e703baceb8ab235421bf80c5
+Author: A U Thor <author@example.com>
+Date: Thu Apr 7 15:14:13 2005 -0700
+
+ 2nd
+
+Notes:
+ b1
+EOF
+
+test_expect_success 'show notes' '
+ ! (git cat-file commit HEAD | grep b1) &&
+ git log -1 > output &&
+ test_cmp expect output
+'
+test_expect_success 'create multi-line notes (setup)' '
+ : > a3 &&
+ git add a3 &&
+ test_tick &&
+ git commit -m 3rd &&
+ MSG="b3
+c3c3c3c3
+d3d3d3" git notes edit
+'
+
+cat > expect-multiline << EOF
+commit 1584215f1d29c65e99c6c6848626553fdd07fd75
+Author: A U Thor <author@example.com>
+Date: Thu Apr 7 15:15:13 2005 -0700
+
+ 3rd
+
+Notes:
+ b3
+ c3c3c3c3
+ d3d3d3
+EOF
+
+printf "\n" >> expect-multiline
+cat expect >> expect-multiline
+
+test_expect_success 'show multi-line notes' '
+ git log -2 > output &&
+ test_cmp expect-multiline output
+'
+test_expect_success 'create -m and -F notes (setup)' '
+ : > a4 &&
+ git add a4 &&
+ test_tick &&
+ git commit -m 4th &&
+ echo "xyzzy" > note5 &&
+ git notes edit -m spam -F note5 -m "foo
+bar
+baz"
+'
+
+whitespace=" "
+cat > expect-m-and-F << EOF
+commit 15023535574ded8b1a89052b32673f84cf9582b8
+Author: A U Thor <author@example.com>
+Date: Thu Apr 7 15:16:13 2005 -0700
+
+ 4th
+
+Notes:
+ spam
+$whitespace
+ xyzzy
+$whitespace
+ foo
+ bar
+ baz
+EOF
+
+printf "\n" >> expect-m-and-F
+cat expect-multiline >> expect-m-and-F
+
+test_expect_success 'show -m and -F notes' '
+ git log -3 > output &&
+ test_cmp expect-m-and-F output
+'
+
+test_done
diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh
new file mode 100755
index 0000000000..ee84fc4884
--- /dev/null
+++ b/t/t3302-notes-index-expensive.sh
@@ -0,0 +1,118 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='Test commit notes index (expensive!)'
+
+. ./test-lib.sh
+
+test -z "$GIT_NOTES_TIMING_TESTS" && {
+ say Skipping timing tests
+ test_done
+ exit
+}
+
+create_repo () {
+ number_of_commits=$1
+ nr=0
+ test -d .git || {
+ git init &&
+ (
+ while [ $nr -lt $number_of_commits ]; do
+ nr=$(($nr+1))
+ mark=$(($nr+$nr))
+ notemark=$(($mark+1))
+ test_tick &&
+ cat <<INPUT_END &&
+commit refs/heads/master
+mark :$mark
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit #$nr
+COMMIT
+
+M 644 inline file
+data <<EOF
+file in commit #$nr
+EOF
+
+blob
+mark :$notemark
+data <<EOF
+note for commit #$nr
+EOF
+
+INPUT_END
+
+ echo "N :$notemark :$mark" >> note_commit
+ done &&
+ test_tick &&
+ cat <<INPUT_END &&
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes
+COMMIT
+
+INPUT_END
+
+ cat note_commit
+ ) |
+ git fast-import --quiet &&
+ git config core.notesRef refs/notes/commits
+ }
+}
+
+test_notes () {
+ count=$1 &&
+ git config core.notesRef refs/notes/commits &&
+ git log | grep "^ " > output &&
+ i=$count &&
+ while [ $i -gt 0 ]; do
+ echo " commit #$i" &&
+ echo " note for commit #$i" &&
+ i=$(($i-1));
+ done > expect &&
+ test_cmp expect output
+}
+
+cat > time_notes << \EOF
+ mode=$1
+ i=1
+ while [ $i -lt $2 ]; do
+ case $1 in
+ no-notes)
+ GIT_NOTES_REF=non-existing; export GIT_NOTES_REF
+ ;;
+ notes)
+ unset GIT_NOTES_REF
+ ;;
+ esac
+ git log >/dev/null
+ i=$(($i+1))
+ done
+EOF
+
+time_notes () {
+ for mode in no-notes notes
+ do
+ echo $mode
+ /usr/bin/time sh ../time_notes $mode $1
+ done
+}
+
+for count in 10 100 1000 10000; do
+
+ mkdir $count
+ (cd $count;
+
+ test_expect_success "setup $count" "create_repo $count"
+
+ test_expect_success 'notes work' "test_notes $count"
+
+ test_expect_success 'notes timing' "time_notes 100"
+ )
+done
+
+test_done
diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh
new file mode 100755
index 0000000000..edc4bc8841
--- /dev/null
+++ b/t/t3303-notes-subtrees.sh
@@ -0,0 +1,188 @@
+#!/bin/sh
+
+test_description='Test commit notes organized in subtrees'
+
+. ./test-lib.sh
+
+number_of_commits=100
+
+start_note_commit () {
+ test_tick &&
+ cat <<INPUT_END
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes
+COMMIT
+
+from refs/notes/commits^0
+deleteall
+INPUT_END
+
+}
+
+verify_notes () {
+ git log | grep "^ " > output &&
+ i=$number_of_commits &&
+ while [ $i -gt 0 ]; do
+ echo " commit #$i" &&
+ echo " note for commit #$i" &&
+ i=$(($i-1));
+ done > expect &&
+ test_cmp expect output
+}
+
+test_expect_success "setup: create $number_of_commits commits" '
+
+ (
+ nr=0 &&
+ while [ $nr -lt $number_of_commits ]; do
+ nr=$(($nr+1)) &&
+ test_tick &&
+ cat <<INPUT_END
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit #$nr
+COMMIT
+
+M 644 inline file
+data <<EOF
+file in commit #$nr
+EOF
+
+INPUT_END
+
+ done &&
+ test_tick &&
+ cat <<INPUT_END
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+no notes
+COMMIT
+
+deleteall
+
+INPUT_END
+
+ ) |
+ git fast-import --quiet &&
+ git config core.notesRef refs/notes/commits
+'
+
+test_sha1_based () {
+ (
+ start_note_commit &&
+ nr=$number_of_commits &&
+ git rev-list refs/heads/master |
+ while read sha1; do
+ note_path=$(echo "$sha1" | sed "$1")
+ cat <<INPUT_END &&
+M 100644 inline $note_path
+data <<EOF
+note for commit #$nr
+EOF
+
+INPUT_END
+
+ nr=$(($nr-1))
+ done
+ ) |
+ git fast-import --quiet
+}
+
+test_expect_success 'test notes in 2/38-fanout' 'test_sha1_based "s|^..|&/|"'
+test_expect_success 'verify notes in 2/38-fanout' 'verify_notes'
+
+test_expect_success 'test notes in 4/36-fanout' 'test_sha1_based "s|^....|&/|"'
+test_expect_success 'verify notes in 4/36-fanout' 'verify_notes'
+
+test_expect_success 'test notes in 2/2/36-fanout' 'test_sha1_based "s|^\(..\)\(..\)|\1/\2/|"'
+test_expect_success 'verify notes in 2/2/36-fanout' 'verify_notes'
+
+test_same_notes () {
+ (
+ start_note_commit &&
+ nr=$number_of_commits &&
+ git rev-list refs/heads/master |
+ while read sha1; do
+ first_note_path=$(echo "$sha1" | sed "$1")
+ second_note_path=$(echo "$sha1" | sed "$2")
+ cat <<INPUT_END &&
+M 100644 inline $second_note_path
+data <<EOF
+note for commit #$nr
+EOF
+
+M 100644 inline $first_note_path
+data <<EOF
+note for commit #$nr
+EOF
+
+INPUT_END
+
+ nr=$(($nr-1))
+ done
+ ) |
+ git fast-import --quiet
+}
+
+test_expect_success 'test same notes in 4/36-fanout and 2/38-fanout' 'test_same_notes "s|^..|&/|" "s|^....|&/|"'
+test_expect_success 'verify same notes in 4/36-fanout and 2/38-fanout' 'verify_notes'
+
+test_expect_success 'test same notes in 2/38-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^..|&/|"'
+test_expect_success 'verify same notes in 2/38-fanout and 2/2/36-fanout' 'verify_notes'
+
+test_expect_success 'test same notes in 4/36-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^....|&/|"'
+test_expect_success 'verify same notes in 4/36-fanout and 2/2/36-fanout' 'verify_notes'
+
+test_concatenated_notes () {
+ (
+ start_note_commit &&
+ nr=$number_of_commits &&
+ git rev-list refs/heads/master |
+ while read sha1; do
+ first_note_path=$(echo "$sha1" | sed "$1")
+ second_note_path=$(echo "$sha1" | sed "$2")
+ cat <<INPUT_END &&
+M 100644 inline $second_note_path
+data <<EOF
+second note for commit #$nr
+EOF
+
+M 100644 inline $first_note_path
+data <<EOF
+first note for commit #$nr
+EOF
+
+INPUT_END
+
+ nr=$(($nr-1))
+ done
+ ) |
+ git fast-import --quiet
+}
+
+verify_concatenated_notes () {
+ git log | grep "^ " > output &&
+ i=$number_of_commits &&
+ while [ $i -gt 0 ]; do
+ echo " commit #$i" &&
+ echo " first note for commit #$i" &&
+ echo " second note for commit #$i" &&
+ i=$(($i-1));
+ done > expect &&
+ test_cmp expect output
+}
+
+test_expect_success 'test notes in 4/36-fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" "s|^....|&/|"'
+test_expect_success 'verify notes in 4/36-fanout concatenated with 2/38-fanout' 'verify_concatenated_notes'
+
+test_expect_success 'test notes in 2/38-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^..|&/|"'
+test_expect_success 'verify notes in 2/38-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes'
+
+test_expect_success 'test notes in 4/36-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^....|&/|"'
+test_expect_success 'verify notes in 4/36-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes'
+
+test_done
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 687bd7ab53..d86bc81abf 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -228,4 +228,21 @@ test_expect_success 'add first line works' '
test_cmp expected diff
'
+cat >expected <<EOF
+diff --git a/empty b/empty
+deleted file mode 100644
+index e69de29..0000000
+EOF
+
+test_expect_success 'deleting an empty file' '
+ git reset --hard &&
+ > empty &&
+ git add empty &&
+ git commit -m empty &&
+ rm empty &&
+ echo y | git add -p empty &&
+ git diff --cached >diff &&
+ test_cmp expected diff
+'
+
test_done
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 531f5b795c..7f267f9ed1 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -455,6 +455,27 @@ test_expect_success 'format-patch respects -U' '
'
+cat > expect << EOF
+
+diff --git a/file b/file
+index 40f36c6..2dc5c23 100644
+--- a/file
++++ b/file
+@@ -14,3 +14,19 @@ C
+ D
+ E
+ F
++5
+EOF
+
+test_expect_success 'format-patch -p suppresses stat' '
+
+ git format-patch -p -2 &&
+ sed -e "1,/^$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+ test_cmp expect output
+
+'
+
test_expect_success 'format-patch from a subdirectory (1)' '
filename=$(
rm -rf sub &&
@@ -515,4 +536,20 @@ test_expect_success 'format-patch --signoff' '
grep "^Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
'
+echo "fatal: --name-only does not make sense" > expect.name-only
+echo "fatal: --name-status does not make sense" > expect.name-status
+echo "fatal: --check does not make sense" > expect.check
+
+test_expect_success 'options no longer allowed for format-patch' '
+ test_must_fail git format-patch --name-only 2> output &&
+ test_cmp expect.name-only output &&
+ test_must_fail git format-patch --name-status 2> output &&
+ test_cmp expect.name-status output &&
+ test_must_fail git format-patch --check 2> output &&
+ test_cmp expect.check output'
+
+test_expect_success 'format-patch --numstat should produce a patch' '
+ git format-patch --numstat --stdout master..side > output &&
+ test 6 = $(grep "^diff --git a/" output | wc -l)'
+
test_done
diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh
index 4508effcaa..21db6e95c4 100755
--- a/t/t4034-diff-words.sh
+++ b/t/t4034-diff-words.sh
@@ -68,6 +68,26 @@ cat > expect <<\EOF
<WHITE>index 330b04f..5ed8eff 100644<RESET>
<WHITE>--- a/pre<RESET>
<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1 +1 @@<RESET>
+<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
+<BROWN>@@ -3,0 +4,4 @@ a = b + c<RESET>
+
+<GREEN>aa = a<RESET>
+
+<GREEN>aeff = aeff * ( aaa )<RESET>
+EOF
+
+test_expect_success 'word diff without context' '
+
+ word_diff --color-words --unified=0
+
+'
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 330b04f..5ed8eff 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
<BROWN>@@ -1,3 +1,7 @@<RESET>
h(4),<GREEN>hh<RESET>[44]
<RESET>
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 1e952ca55b..779a5adf55 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -64,6 +64,27 @@ test_expect_success 'format' '
'
cat > expect << EOF
+ This is
+ the sixth
+ commit.
+ This is
+ the fifth
+ commit.
+EOF
+
+test_expect_success 'format %w(12,1,2)' '
+
+ git log -2 --format="%w(12,1,2)This is the %s commit." > actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'format %w(,1,2)' '
+
+ git log -2 --format="%w(,1,2)This is%nthe %s%ncommit." > actual &&
+ test_cmp expect actual
+'
+
+cat > expect << EOF
804a787 sixth
394ef78 fifth
5d31159 fourth
diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh
index 0279d07c83..ebc36c1758 100755
--- a/t/t5100-mailinfo.sh
+++ b/t/t5100-mailinfo.sh
@@ -11,7 +11,7 @@ test_expect_success 'split sample box' \
'git mailsplit -o. "$TEST_DIRECTORY"/t5100/sample.mbox >last &&
last=`cat last` &&
echo total is $last &&
- test `cat last` = 14'
+ test `cat last` = 16'
check_mailinfo () {
mail=$1 opt=$2
@@ -30,6 +30,10 @@ do
if test -f "$TEST_DIRECTORY"/t5100/msg$mail--scissors
then
check_mailinfo $mail --scissors
+ fi &&
+ if test -f "$TEST_DIRECTORY"/t5100/msg$mail--no-inbody-headers
+ then
+ check_mailinfo $mail --no-inbody-headers
fi
'
done
diff --git a/t/t5100/info0015 b/t/t5100/info0015
new file mode 100644
index 0000000000..0114f106c5
--- /dev/null
+++ b/t/t5100/info0015
@@ -0,0 +1,5 @@
+Author:
+Email:
+Subject: check bogus body header (from)
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
diff --git a/t/t5100/info0015--no-inbody-headers b/t/t5100/info0015--no-inbody-headers
new file mode 100644
index 0000000000..c4d8d7720e
--- /dev/null
+++ b/t/t5100/info0015--no-inbody-headers
@@ -0,0 +1,5 @@
+Author: A U Thor
+Email: a.u.thor@example.com
+Subject: check bogus body header (from)
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
diff --git a/t/t5100/info0016 b/t/t5100/info0016
new file mode 100644
index 0000000000..38ccd0dcf2
--- /dev/null
+++ b/t/t5100/info0016
@@ -0,0 +1,5 @@
+Author: A U Thor
+Email: a.u.thor@example.com
+Subject: check bogus body header (date)
+Date: bogus
+
diff --git a/t/t5100/info0016--no-inbody-headers b/t/t5100/info0016--no-inbody-headers
new file mode 100644
index 0000000000..f4857d45df
--- /dev/null
+++ b/t/t5100/info0016--no-inbody-headers
@@ -0,0 +1,5 @@
+Author: A U Thor
+Email: a.u.thor@example.com
+Subject: check bogus body header (date)
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
diff --git a/t/t5100/msg0015 b/t/t5100/msg0015
new file mode 100644
index 0000000000..9577238685
--- /dev/null
+++ b/t/t5100/msg0015
@@ -0,0 +1,2 @@
+- a list
+ - of stuff
diff --git a/t/t5100/msg0015--no-inbody-headers b/t/t5100/msg0015--no-inbody-headers
new file mode 100644
index 0000000000..be5115b1c1
--- /dev/null
+++ b/t/t5100/msg0015--no-inbody-headers
@@ -0,0 +1,3 @@
+From: bogosity
+ - a list
+ - of stuff
diff --git a/t/t5100/msg0016 b/t/t5100/msg0016
new file mode 100644
index 0000000000..0d9adada96
--- /dev/null
+++ b/t/t5100/msg0016
@@ -0,0 +1,2 @@
+and some content
+
diff --git a/t/t5100/msg0016--no-inbody-headers b/t/t5100/msg0016--no-inbody-headers
new file mode 100644
index 0000000000..1063f51178
--- /dev/null
+++ b/t/t5100/msg0016--no-inbody-headers
@@ -0,0 +1,4 @@
+Date: bogus
+
+and some content
+
diff --git a/t/t5100/patch0015 b/t/t5100/patch0015
new file mode 100644
index 0000000000..ad64848873
--- /dev/null
+++ b/t/t5100/patch0015
@@ -0,0 +1,8 @@
+---
+diff --git a/foo b/foo
+index e69de29..d95f3ad 100644
+--- a/foo
++++ b/foo
+@@ -0,0 +1 @@
++content
+
diff --git a/t/t5100/patch0015--no-inbody-headers b/t/t5100/patch0015--no-inbody-headers
new file mode 100644
index 0000000000..ad64848873
--- /dev/null
+++ b/t/t5100/patch0015--no-inbody-headers
@@ -0,0 +1,8 @@
+---
+diff --git a/foo b/foo
+index e69de29..d95f3ad 100644
+--- a/foo
++++ b/foo
+@@ -0,0 +1 @@
++content
+
diff --git a/t/t5100/patch0016 b/t/t5100/patch0016
new file mode 100644
index 0000000000..ad64848873
--- /dev/null
+++ b/t/t5100/patch0016
@@ -0,0 +1,8 @@
+---
+diff --git a/foo b/foo
+index e69de29..d95f3ad 100644
+--- a/foo
++++ b/foo
+@@ -0,0 +1 @@
++content
+
diff --git a/t/t5100/patch0016--no-inbody-headers b/t/t5100/patch0016--no-inbody-headers
new file mode 100644
index 0000000000..ad64848873
--- /dev/null
+++ b/t/t5100/patch0016--no-inbody-headers
@@ -0,0 +1,8 @@
+---
+diff --git a/foo b/foo
+index e69de29..d95f3ad 100644
+--- a/foo
++++ b/foo
+@@ -0,0 +1 @@
++content
+
diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox
index 13fa4ae03b..de1031241d 100644
--- a/t/t5100/sample.mbox
+++ b/t/t5100/sample.mbox
@@ -650,3 +650,36 @@ index b0b5d8f..461c47e 100644
convert_to_utf8(line, charset.buf);
--
1.6.4.1
+From nobody Mon Sep 17 00:00:00 2001
+From: A U Thor <a.u.thor@example.com>
+Subject: check bogus body header (from)
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
+From: bogosity
+ - a list
+ - of stuff
+---
+diff --git a/foo b/foo
+index e69de29..d95f3ad 100644
+--- a/foo
++++ b/foo
+@@ -0,0 +1 @@
++content
+
+From nobody Mon Sep 17 00:00:00 2001
+From: A U Thor <a.u.thor@example.com>
+Subject: check bogus body header (date)
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
+Date: bogus
+
+and some content
+
+---
+diff --git a/foo b/foo
+index e69de29..d95f3ad 100644
+--- a/foo
++++ b/foo
+@@ -0,0 +1 @@
++content
+
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 852ccb5d7d..fd166d9de3 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -158,7 +158,7 @@ cat > test/expect << EOF
another
master
Local refs configured for 'git push':
- ahead forces to master (fast forwardable)
+ ahead forces to master (fast-forwardable)
master pushes to another (up to date)
EOF
@@ -365,6 +365,17 @@ test_expect_success 'update with arguments' '
'
+test_expect_success 'update --prune' '
+
+ (cd one &&
+ git branch -m side2 side3) &&
+ (cd test &&
+ git remote update --prune &&
+ (cd ../one && git branch -m side3 side2)
+ git rev-parse refs/remotes/origin/side3 &&
+ test_must_fail git rev-parse refs/remotes/origin/side2)
+'
+
cat > one/expect << EOF
apis/master
apis/side
diff --git a/t/t5506-remote-groups.sh b/t/t5506-remote-groups.sh
index 2a1806b0b4..b7b7ddaa40 100755
--- a/t/t5506-remote-groups.sh
+++ b/t/t5506-remote-groups.sh
@@ -51,7 +51,7 @@ test_expect_success 'nonexistant group produces error' '
! repo_fetched two
'
-test_expect_success 'updating group updates all members' '
+test_expect_success 'updating group updates all members (remote update)' '
mark group-all &&
update_repos &&
git config --add remotes.all one &&
@@ -61,7 +61,15 @@ test_expect_success 'updating group updates all members' '
repo_fetched two
'
-test_expect_success 'updating group does not update non-members' '
+test_expect_success 'updating group updates all members (fetch)' '
+ mark fetch-group-all &&
+ update_repos &&
+ git fetch all &&
+ repo_fetched one &&
+ repo_fetched two
+'
+
+test_expect_success 'updating group does not update non-members (remote update)' '
mark group-some &&
update_repos &&
git config --add remotes.some one &&
@@ -70,6 +78,15 @@ test_expect_success 'updating group does not update non-members' '
! repo_fetched two
'
+test_expect_success 'updating group does not update non-members (fetch)' '
+ mark fetch-group-some &&
+ update_repos &&
+ git config --add remotes.some one &&
+ git remote update some &&
+ repo_fetched one &&
+ ! repo_fetched two
+'
+
test_expect_success 'updating remote name updates that remote' '
mark remote-name &&
update_repos &&
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index d13c806624..169af1edde 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -341,4 +341,15 @@ test_expect_success 'fetch into the current branch with --update-head-ok' '
'
+test_expect_success "should be able to fetch with duplicate refspecs" '
+ mkdir dups &&
+ cd dups &&
+ git init &&
+ git config branch.master.remote three &&
+ git config remote.three.url ../three/.git &&
+ git config remote.three.fetch +refs/heads/*:refs/remotes/origin/* &&
+ git config --add remote.three.fetch +refs/heads/*:refs/remotes/origin/* &&
+ git fetch three
+'
+
test_done
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
new file mode 100755
index 0000000000..b73733219d
--- /dev/null
+++ b/t/t5514-fetch-multiple.sh
@@ -0,0 +1,154 @@
+#!/bin/sh
+
+test_description='fetch --all works correctly'
+
+. ./test-lib.sh
+
+setup_repository () {
+ mkdir "$1" && (
+ cd "$1" &&
+ git init &&
+ >file &&
+ git add file &&
+ test_tick &&
+ git commit -m "Initial" &&
+ git checkout -b side &&
+ >elif &&
+ git add elif &&
+ test_tick &&
+ git commit -m "Second" &&
+ git checkout master
+ )
+}
+
+test_expect_success setup '
+ setup_repository one &&
+ setup_repository two &&
+ (
+ cd two && git branch another
+ ) &&
+ git clone --mirror two three
+ git clone one test
+'
+
+cat > test/expect << EOF
+ one/master
+ one/side
+ origin/HEAD -> origin/master
+ origin/master
+ origin/side
+ three/another
+ three/master
+ three/side
+ two/another
+ two/master
+ two/side
+EOF
+
+test_expect_success 'git fetch --all' '
+ (cd test &&
+ git remote add one ../one &&
+ git remote add two ../two &&
+ git remote add three ../three &&
+ git fetch --all &&
+ git branch -r > output &&
+ test_cmp expect output)
+'
+
+test_expect_success 'git fetch --all should continue if a remote has errors' '
+ (git clone one test2 &&
+ cd test2 &&
+ git remote add bad ../non-existing &&
+ git remote add one ../one &&
+ git remote add two ../two &&
+ git remote add three ../three &&
+ test_must_fail git fetch --all &&
+ git branch -r > output &&
+ test_cmp ../test/expect output)
+'
+
+test_expect_success 'git fetch --all does not allow non-option arguments' '
+ (cd test &&
+ test_must_fail git fetch --all origin &&
+ test_must_fail git fetch --all origin master)
+'
+
+cat > expect << EOF
+ origin/HEAD -> origin/master
+ origin/master
+ origin/side
+ three/another
+ three/master
+ three/side
+EOF
+
+test_expect_success 'git fetch --multiple (but only one remote)' '
+ (git clone one test3 &&
+ cd test3 &&
+ git remote add three ../three &&
+ git fetch --multiple three &&
+ git branch -r > output &&
+ test_cmp ../expect output)
+'
+
+cat > expect << EOF
+ one/master
+ one/side
+ two/another
+ two/master
+ two/side
+EOF
+
+test_expect_success 'git fetch --multiple (two remotes)' '
+ (git clone one test4 &&
+ cd test4 &&
+ git remote rm origin &&
+ git remote add one ../one &&
+ git remote add two ../two &&
+ git fetch --multiple one two &&
+ git branch -r > output &&
+ test_cmp ../expect output)
+'
+
+test_expect_success 'git fetch --multiple (bad remote names)' '
+ (cd test4 &&
+ test_must_fail git fetch --multiple four)
+'
+
+
+test_expect_success 'git fetch --all (skipFetchAll)' '
+ (cd test4 &&
+ for b in $(git branch -r)
+ do
+ git branch -r -d $b || break
+ done &&
+ git remote add three ../three &&
+ git config remote.three.skipFetchAll true &&
+ git fetch --all &&
+ git branch -r > output &&
+ test_cmp ../expect output)
+'
+
+cat > expect << EOF
+ one/master
+ one/side
+ three/another
+ three/master
+ three/side
+ two/another
+ two/master
+ two/side
+EOF
+
+test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '
+ (cd test4 &&
+ for b in $(git branch -r)
+ do
+ git branch -r -d $b || break
+ done &&
+ git fetch --multiple one two three &&
+ git branch -r > output &&
+ test_cmp ../expect output)
+'
+
+test_done
diff --git a/t/t5518-fetch-exit-status.sh b/t/t5518-fetch-exit-status.sh
index c6bc65faa0..c2060bb870 100755
--- a/t/t5518-fetch-exit-status.sh
+++ b/t/t5518-fetch-exit-status.sh
@@ -22,7 +22,7 @@ test_expect_success setup '
git commit -a -m next
'
-test_expect_success 'non fast forward fetch' '
+test_expect_success 'non-fast-forward fetch' '
test_must_fail git fetch . master:side
diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh
index f4a2cf6c17..bb18f8bfc4 100755
--- a/t/t5540-http-push.sh
+++ b/t/t5540-http-push.sh
@@ -3,23 +3,22 @@
# Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>
#
-test_description='test http-push
+test_description='test WebDAV http-push
This test runs various sanity checks on http-push.'
. ./test-lib.sh
-ROOT_PATH="$PWD"
-LIB_HTTPD_DAV=t
-LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'}
-
if git http-push > /dev/null 2>&1 || [ $? -eq 128 ]
then
say "skipping test, USE_CURL_MULTI is not defined"
test_done
fi
+LIB_HTTPD_DAV=t
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'}
. "$TEST_DIRECTORY"/lib-httpd.sh
+ROOT_PATH="$PWD"
start_httpd
test_expect_success 'setup remote repository' '
@@ -36,16 +35,17 @@ test_expect_success 'setup remote repository' '
cd test_repo.git &&
git --bare update-server-info &&
mv hooks/post-update.sample hooks/post-update &&
+ ORIG_HEAD=$(git rev-parse --verify HEAD) &&
cd - &&
mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
'
test_expect_success 'clone remote repository' '
cd "$ROOT_PATH" &&
- git clone $HTTPD_URL/test_repo.git test_repo_clone
+ git clone $HTTPD_URL/dumb/test_repo.git test_repo_clone
'
-test_expect_failure 'push to remote repository with packed refs' '
+test_expect_success 'push to remote repository with packed refs' '
cd "$ROOT_PATH"/test_repo_clone &&
: >path2 &&
git add path2 &&
@@ -57,11 +57,15 @@ test_expect_failure 'push to remote repository with packed refs' '
test $HEAD = $(git rev-parse --verify HEAD))
'
-test_expect_success ' push to remote repository with unpacked refs' '
+test_expect_success 'push already up-to-date' '
+ git push
+'
+
+test_expect_success 'push to remote repository with unpacked refs' '
(cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
rm packed-refs &&
- git update-ref refs/heads/master \
- 0c973ae9bd51902a28466f3850b543fa66a6aaf4) &&
+ git update-ref refs/heads/master $ORIG_HEAD &&
+ git --bare update-server-info) &&
git push &&
(cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
test $HEAD = $(git rev-parse --verify HEAD))
@@ -71,7 +75,7 @@ test_expect_success 'http-push fetches unpacked objects' '
cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \
"$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_unpacked.git &&
- git clone $HTTPD_URL/test_repo_unpacked.git \
+ git clone $HTTPD_URL/dumb/test_repo_unpacked.git \
"$ROOT_PATH"/fetch_unpacked &&
# By reset, we force git to retrieve the object
@@ -80,14 +84,14 @@ test_expect_success 'http-push fetches unpacked objects' '
git remote rm origin &&
git reflog expire --expire=0 --all &&
git prune &&
- git push -f -v $HTTPD_URL/test_repo_unpacked.git master)
+ git push -f -v $HTTPD_URL/dumb/test_repo_unpacked.git master)
'
test_expect_success 'http-push fetches packed objects' '
cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \
"$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git &&
- git clone $HTTPD_URL/test_repo_packed.git \
+ git clone $HTTPD_URL/dumb/test_repo_packed.git \
"$ROOT_PATH"/test_repo_clone_packed &&
(cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git &&
@@ -100,7 +104,7 @@ test_expect_success 'http-push fetches packed objects' '
git remote rm origin &&
git reflog expire --expire=0 --all &&
git prune &&
- git push -f -v $HTTPD_URL/test_repo_packed.git master)
+ git push -f -v $HTTPD_URL/dumb/test_repo_packed.git master)
'
test_expect_success 'create and delete remote branch' '
@@ -111,10 +115,7 @@ test_expect_success 'create and delete remote branch' '
test_tick &&
git commit -m dev &&
git push origin dev &&
- git fetch &&
git push origin :dev &&
- git branch -d -r origin/dev &&
- git fetch &&
test_must_fail git show-ref --verify refs/remotes/origin/dev
'
diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh
new file mode 100755
index 0000000000..2a58d0cc9c
--- /dev/null
+++ b/t/t5541-http-push.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>
+#
+
+test_description='test smart pushing over http via http-backend'
+. ./test-lib.sh
+
+if test -n "$NO_CURL"; then
+ say 'skipping test, git built without http support'
+ test_done
+fi
+
+ROOT_PATH="$PWD"
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5541'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'setup remote repository' '
+ cd "$ROOT_PATH" &&
+ mkdir test_repo &&
+ cd test_repo &&
+ git init &&
+ : >path1 &&
+ git add path1 &&
+ test_tick &&
+ git commit -m initial &&
+ cd - &&
+ git clone --bare test_repo test_repo.git &&
+ cd test_repo.git &&
+ git config http.receivepack true &&
+ ORIG_HEAD=$(git rev-parse --verify HEAD) &&
+ cd - &&
+ mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
+'
+
+test_expect_success 'clone remote repository' '
+ cd "$ROOT_PATH" &&
+ git clone $HTTPD_URL/smart/test_repo.git test_repo_clone
+'
+
+test_expect_success 'push to remote repository' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ : >path2 &&
+ git add path2 &&
+ test_tick &&
+ git commit -m path2 &&
+ HEAD=$(git rev-parse --verify HEAD) &&
+ git push &&
+ (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
+ test $HEAD = $(git rev-parse --verify HEAD))
+'
+
+test_expect_success 'push already up-to-date' '
+ git push
+'
+
+test_expect_success 'create and delete remote branch' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ git checkout -b dev &&
+ : >path3 &&
+ git add path3 &&
+ test_tick &&
+ git commit -m dev &&
+ git push origin dev &&
+ git push origin :dev &&
+ test_must_fail git show-ref --verify refs/remotes/origin/dev
+'
+
+cat >exp <<EOF
+GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200
+GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
+GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
+GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200
+EOF
+test_expect_success 'used receive-pack service' '
+ 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/t5550-http-fetch.sh b/t/t5550-http-fetch.sh
index 0e69324652..8cfce969bc 100755
--- a/t/t5550-http-fetch.sh
+++ b/t/t5550-http-fetch.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-test_description='test fetching over http'
+test_description='test dumb fetching over http via static file'
. ./test-lib.sh
if test -n "$NO_CURL"; then
@@ -30,7 +30,7 @@ test_expect_success 'create http-accessible bare repository' '
'
test_expect_success 'clone http repository' '
- git clone $HTTPD_URL/repo.git clone &&
+ git clone $HTTPD_URL/dumb/repo.git clone &&
test_cmp file clone/file
'
@@ -58,7 +58,13 @@ test_expect_success 'fetch packed objects' '
cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git &&
git --bare repack &&
git --bare prune-packed &&
- git clone $HTTPD_URL/repo_pack.git
+ git clone $HTTPD_URL/dumb/repo_pack.git
+'
+
+test_expect_success 'did not use upload-pack service' '
+ grep '/git-upload-pack' <"$HTTPD_ROOT_PATH"/access.log >act
+ : >exp
+ test_cmp exp act
'
stop_httpd
diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh
new file mode 100755
index 0000000000..c0505ecd7b
--- /dev/null
+++ b/t/t5551-http-fetch.sh
@@ -0,0 +1,105 @@
+#!/bin/sh
+
+test_description='test smart fetching over http via 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-'5551'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'setup repository' '
+ echo content >file &&
+ git add file &&
+ git commit -m one
+'
+
+test_expect_success 'create http-accessible bare repository' '
+ mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git --bare init
+ ) &&
+ git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+ git push public master:master
+'
+
+cat >exp <<EOF
+> GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1
+> Accept: */*
+> Pragma: no-cache
+< HTTP/1.1 200 OK
+< Pragma: no-cache
+< Cache-Control: no-cache, max-age=0, must-revalidate
+< Content-Type: application/x-git-upload-pack-advertisement
+> 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
+> Content-Length: xxx
+< HTTP/1.1 200 OK
+< Pragma: no-cache
+< Cache-Control: no-cache, max-age=0, must-revalidate
+< Content-Type: application/x-git-upload-pack-result
+EOF
+test_expect_success 'clone http repository' '
+ GIT_CURL_VERBOSE=1 git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err &&
+ test_cmp file clone/file &&
+ tr '\''\015'\'' Q <err |
+ sed -e "
+ s/Q\$//
+ /^[*] /d
+ /^$/d
+ /^< $/d
+
+ /^[^><]/{
+ s/^/> /
+ }
+
+ /^> User-Agent: /d
+ /^> Host: /d
+ /^> POST /,$ {
+ /^> Accept: [*]\\/[*]/d
+ }
+ s/^> Content-Length: .*/> Content-Length: xxx/
+ /^> 00..want /d
+ /^> 00.*done/d
+
+ /^< Server: /d
+ /^< Expires: /d
+ /^< Date: /d
+ /^< Content-Length: /d
+ /^< Transfer-Encoding: /d
+ " >act &&
+ test_cmp exp act
+'
+
+test_expect_success 'fetch changes via http' '
+ echo content >>file &&
+ git commit -a -m two &&
+ git push public
+ (cd clone && git pull) &&
+ test_cmp file clone/file
+'
+
+cat >exp <<EOF
+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
+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
+EOF
+test_expect_success 'used upload-pack service' '
+ 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/t5560-http-backend.sh b/t/t5560-http-backend.sh
new file mode 100755
index 0000000000..ed034bc980
--- /dev/null
+++ b/t/t5560-http-backend.sh
@@ -0,0 +1,260 @@
+#!/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/t6028-merge-up-to-date.sh b/t/t6028-merge-up-to-date.sh
index f8f3e3ff2c..a91644e3b2 100755
--- a/t/t6028-merge-up-to-date.sh
+++ b/t/t6028-merge-up-to-date.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-test_description='merge fast forward and up to date'
+test_description='merge fast-forward and up to date'
. ./test-lib.sh
diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh
index d4818b430a..203ffdb17a 100755
--- a/t/t6050-replace.sh
+++ b/t/t6050-replace.sh
@@ -77,6 +77,11 @@ test_expect_success 'test --no-replace-objects option' '
git --no-replace-objects show $HASH2 | grep "A U Thor"
'
+test_expect_success 'test GIT_NO_REPLACE_OBJECTS env variable' '
+ GIT_NO_REPLACE_OBJECTS=1 git cat-file commit $HASH2 | grep "author A U Thor" &&
+ GIT_NO_REPLACE_OBJECTS=1 git show $HASH2 | grep "A U Thor"
+'
+
cat >tag.sig <<EOF
object $HASH2
type commit
@@ -202,6 +207,18 @@ test_expect_success 'fetch branch with replacement' '
cd ..
'
+test_expect_success 'bisect and replacements' '
+ git bisect start $HASH7 $HASH1 &&
+ test "$S" = "$(git rev-parse --verify HEAD)" &&
+ git bisect reset &&
+ GIT_NO_REPLACE_OBJECTS=1 git bisect start $HASH7 $HASH1 &&
+ test "$HASH4" = "$(git rev-parse --verify HEAD)" &&
+ git bisect reset &&
+ git --no-replace-objects bisect start $HASH7 $HASH1 &&
+ test "$HASH4" = "$(git rev-parse --verify HEAD)" &&
+ git bisect reset
+'
+
#
#
test_done
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index f5a1b615f6..065deadc29 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -92,12 +92,18 @@ check_describe A-* HEAD^
check_describe D-* HEAD^^
check_describe A-* HEAD^^2
check_describe B HEAD^^2^
+check_describe D-* HEAD^^^
check_describe c-* --tags HEAD
check_describe c-* --tags HEAD^
check_describe e-* --tags HEAD^^
check_describe c-* --tags HEAD^^2
check_describe B --tags HEAD^^2^
+check_describe e --tags HEAD^^^
+
+check_describe heads/master --all HEAD
+check_describe tags/c-* --all HEAD^
+check_describe tags/e --all HEAD^^^
check_describe B-0-* --long HEAD^^2^
check_describe A-3-* --long HEAD^^2
@@ -125,6 +131,20 @@ test_expect_success 'rename tag Q back to A' '
test_expect_success 'pack tag refs' 'git pack-refs'
check_describe A-* HEAD
+check_describe "A-*[0-9a-f]" --dirty
+
+test_expect_success 'set-up dirty work tree' '
+ echo >>file
+'
+
+check_describe "A-*[0-9a-f]-dirty" --dirty
+
+check_describe "A-*[0-9a-f].mod" --dirty=.mod
+
+test_expect_success 'describe --dirty HEAD' '
+ test_must_fail git describe --dirty HEAD
+'
+
test_expect_success 'set-up matching pattern tests' '
git tag -a -m test-annotated test-annotated &&
echo >>file &&
diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh
index ae5290ab43..abd14bf819 100755
--- a/t/t7002-grep.sh
+++ b/t/t7002-grep.sh
@@ -14,6 +14,7 @@ int main(int argc, const char **argv)
{
printf("Hello world.\n");
return 0;
+ /* char ?? */
}
EOF
@@ -213,6 +214,11 @@ test_expect_success 'grep -e A --and --not -e B' '
test_cmp expected actual
'
+test_expect_success 'grep should ignore GREP_OPTIONS' '
+ GREP_OPTIONS=-v git grep " mmap bar\$" >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'grep -f, non-existent file' '
test_must_fail git grep -f patterns
'
@@ -411,4 +417,13 @@ test_expect_success 'grep from a subdirectory to search wider area (2)' '
)
'
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+EOF
+
+test_expect_success 'grep -Fi' '
+ git grep -Fi "CHAR *" >actual &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index 329c851685..9503875e97 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -288,4 +288,22 @@ test_expect_success 'Prune empty commits' '
test_cmp expect actual
'
+test_expect_success '--remap-to-ancestor with filename filters' '
+ git checkout master &&
+ git reset --hard A &&
+ test_commit add-foo foo 1 &&
+ git branch moved-foo &&
+ test_commit add-bar bar a &&
+ git branch invariant &&
+ orig_invariant=$(git rev-parse invariant) &&
+ git branch moved-bar &&
+ test_commit change-foo foo 2 &&
+ git filter-branch -f --remap-to-ancestor \
+ moved-foo moved-bar A..master \
+ -- -- foo &&
+ test $(git rev-parse moved-foo) = $(git rev-parse moved-bar) &&
+ test $(git rev-parse moved-foo) = $(git rev-parse master^) &&
+ test $orig_invariant = $(git rev-parse invariant)
+'
+
test_done
diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh
index b647957d75..5257f4d261 100755
--- a/t/t7005-editor.sh
+++ b/t/t7005-editor.sh
@@ -4,7 +4,21 @@ test_description='GIT_EDITOR, core.editor, and stuff'
. ./test-lib.sh
-for i in GIT_EDITOR core_editor EDITOR VISUAL vi
+unset EDITOR VISUAL GIT_EDITOR
+
+test_expect_success 'determine default editor' '
+
+ vi=$(TERM=vt100 git var GIT_EDITOR) &&
+ test -n "$vi"
+
+'
+
+if ! expr "$vi" : '^[a-z]*$' >/dev/null
+then
+ vi=
+fi
+
+for i in GIT_EDITOR core_editor EDITOR VISUAL $vi
do
cat >e-$i.sh <<-EOF
#!$SHELL_PATH
@@ -12,19 +26,18 @@ do
EOF
chmod +x e-$i.sh
done
-unset vi
-mv e-vi.sh vi
-unset EDITOR VISUAL GIT_EDITOR
+
+if ! test -z "$vi"
+then
+ mv e-$vi.sh $vi
+fi
test_expect_success setup '
- msg="Hand edited" &&
+ msg="Hand-edited" &&
+ test_commit "$msg" &&
echo "$msg" >expect &&
- git add vi &&
- test_tick &&
- git commit -m "$msg" &&
- git show -s --pretty=oneline |
- sed -e "s/^[0-9a-f]* //" >actual &&
+ git show -s --format=%s > actual &&
diff actual expect
'
@@ -42,9 +55,19 @@ test_expect_success 'dumb should error out when falling back on vi' '
fi
'
+test_expect_success 'dumb should prefer EDITOR to VISUAL' '
+
+ EDITOR=./e-EDITOR.sh &&
+ VISUAL=./e-VISUAL.sh &&
+ export EDITOR VISUAL &&
+ git commit --amend &&
+ test "$(git show -s --format=%s)" = "Edited by EDITOR"
+
+'
+
TERM=vt100
export TERM
-for i in vi EDITOR VISUAL core_editor GIT_EDITOR
+for i in $vi EDITOR VISUAL core_editor GIT_EDITOR
do
echo "Edited by $i" >expect
unset EDITOR VISUAL GIT_EDITOR
@@ -68,7 +91,7 @@ done
unset EDITOR VISUAL GIT_EDITOR
git config --unset-all core.editor
-for i in vi EDITOR VISUAL core_editor GIT_EDITOR
+for i in $vi EDITOR VISUAL core_editor GIT_EDITOR
do
echo "Edited by $i" >expect
case "$i" in
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index 0f2ccc6cf0..a0cc99ab9f 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -306,4 +306,20 @@ test_expect_success 'submodule <invalid-path> warns' '
'
+test_expect_success 'add submodules without specifying an explicit path' '
+ mkdir repo &&
+ cd repo &&
+ git init &&
+ echo r >r &&
+ git add r &&
+ git commit -m "repo commit 1" &&
+ cd .. &&
+ git clone --bare repo/ bare.git &&
+ cd addtest &&
+ git submodule add "$submodurl/repo" &&
+ git config -f .gitmodules submodule.repo.path repo &&
+ git submodule add "$submodurl/bare.git" &&
+ git config -f .gitmodules submodule.bare.path bare
+'
+
test_done
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index d2de57679f..a603f6d21a 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -86,7 +86,7 @@ chmod 755 editor
test_expect_success \
"amend commit" \
- "VISUAL=./editor git commit --amend"
+ "EDITOR=./editor git commit --amend"
test_expect_success \
"passing -m and -F" \
@@ -107,7 +107,7 @@ chmod 755 editor
test_expect_success \
"editing message from other commit" \
"echo 'hula hula' >file && \
- VISUAL=./editor git commit -c HEAD^ -a"
+ EDITOR=./editor git commit -c HEAD^ -a"
test_expect_success \
"message from stdin" \
@@ -141,10 +141,10 @@ EOF
test_expect_success \
'editor not invoked if -F is given' '
echo "moo" >file &&
- VISUAL=./editor git commit -a -F msg &&
+ EDITOR=./editor git commit -a -F msg &&
git show -s --pretty=format:"%s" | grep -q good &&
echo "quack" >file &&
- echo "Another good message." | VISUAL=./editor git commit -a -F - &&
+ echo "Another good message." | EDITOR=./editor git commit -a -F - &&
git show -s --pretty=format:"%s" | grep -q good
'
# We could just check the head sha1, but checking each commit makes it
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
index 56cd866019..fe94552296 100755
--- a/t/t7502-commit.sh
+++ b/t/t7502-commit.sh
@@ -258,4 +258,13 @@ test_expect_success 'Hand committing of a redundant merge removes dups' '
'
+test_expect_success 'A single-liner subject with a token plus colon is not a footer' '
+
+ git reset --hard &&
+ git commit -s -m "hello: kitty" --allow-empty &&
+ git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
+ test $(wc -l <actual) = 3
+
+'
+
test_done
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index e5b210bc96..57f6d2bae7 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -243,6 +243,16 @@ test_expect_success 'merge c0 with c1' '
test_debug 'gitk --all'
+test_expect_success 'merge c0 with c1 with --ff-only' '
+ git reset --hard c0 &&
+ git merge --ff-only c1 &&
+ git merge --ff-only HEAD c0 c1 &&
+ verify_merge file result.1 &&
+ verify_head "$c1"
+'
+
+test_debug 'gitk --all'
+
test_expect_success 'merge c1 with c2' '
git reset --hard c1 &&
test_tick &&
@@ -263,6 +273,14 @@ test_expect_success 'merge c1 with c2 and c3' '
test_debug 'gitk --all'
+test_expect_success 'failing merges with --ff-only' '
+ git reset --hard c1 &&
+ test_tick &&
+ test_must_fail git merge --ff-only c2 &&
+ test_must_fail git merge --ff-only c3 &&
+ test_must_fail git merge --ff-only c2 c3
+'
+
test_expect_success 'merge c0 with c1 (no-commit)' '
git reset --hard c0 &&
git merge --no-commit c1 &&
@@ -303,6 +321,17 @@ test_expect_success 'merge c0 with c1 (squash)' '
test_debug 'gitk --all'
+test_expect_success 'merge c0 with c1 (squash, ff-only)' '
+ git reset --hard c0 &&
+ git merge --squash --ff-only c1 &&
+ verify_merge file result.1 &&
+ verify_head $c0 &&
+ verify_no_mergehead &&
+ verify_diff squash.1 .git/SQUASH_MSG "[OOPS] bad squash message"
+'
+
+test_debug 'gitk --all'
+
test_expect_success 'merge c1 with c2 (squash)' '
git reset --hard c1 &&
git merge --squash c2 &&
@@ -314,6 +343,13 @@ test_expect_success 'merge c1 with c2 (squash)' '
test_debug 'gitk --all'
+test_expect_success 'unsuccesful merge of c1 with c2 (squash, ff-only)' '
+ git reset --hard c1 &&
+ test_must_fail git merge --squash --ff-only c2
+'
+
+test_debug 'gitk --all'
+
test_expect_success 'merge c1 with c2 and c3 (squash)' '
git reset --hard c1 &&
git merge --squash c2 c3 &&
@@ -432,6 +468,11 @@ test_expect_success 'combining --squash and --no-ff is refused' '
test_must_fail git merge --no-ff --squash c1
'
+test_expect_success 'combining --ff-only and --no-ff is refused' '
+ test_must_fail git merge --ff-only --no-ff c1 &&
+ test_must_fail git merge --no-ff --ff-only c1
+'
+
test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '
git reset --hard c0 &&
git config branch.master.mergeoptions "--no-ff" &&
diff --git a/t/t9115-git-svn-dcommit-funky-renames.sh b/t/t9115-git-svn-dcommit-funky-renames.sh
index 9be7aefaee..767799e7a7 100755
--- a/t/t9115-git-svn-dcommit-funky-renames.sh
+++ b/t/t9115-git-svn-dcommit-funky-renames.sh
@@ -19,7 +19,7 @@ test_expect_success 'init and fetch repository' '
'
test_expect_success 'create file in existing ugly and empty dir' '
- mkdir "#{bad_directory_name}" &&
+ mkdir -p "#{bad_directory_name}" &&
echo hi > "#{bad_directory_name}/ foo" &&
git update-index --add "#{bad_directory_name}/ foo" &&
git commit -m "new file in ugly parent" &&
@@ -37,7 +37,7 @@ test_expect_success 'rename pretty file' '
git update-index --add pretty &&
git commit -m "pretty :x" &&
git svn dcommit &&
- mkdir regular_dir_name &&
+ mkdir -p regular_dir_name &&
git mv pretty regular_dir_name/pretty &&
git commit -m "moved pretty file" &&
git svn dcommit
diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh
index f5abdb3c7f..134411e0a5 100755
--- a/t/t9130-git-svn-authors-file.sh
+++ b/t/t9130-git-svn-authors-file.sh
@@ -91,4 +91,27 @@ test_expect_success 'fetch continues after authors-file is fixed' '
)
'
+test_expect_success 'fresh clone with svn.authors-file in config' '
+ (
+ rm -r "$GIT_DIR" &&
+ test x = x"$(git config svn.authorsfile)" &&
+ HOME="`pwd`" &&
+ export HOME &&
+ test_config="$HOME"/.gitconfig &&
+ unset GIT_CONFIG_NOGLOBAL &&
+ unset GIT_DIR &&
+ unset GIT_CONFIG &&
+ git config --global \
+ svn.authorsfile "$HOME"/svn-authors &&
+ test x"$HOME"/svn-authors = x"$(git config svn.authorsfile)" &&
+ git svn clone "$svnrepo" gitconfig.clone &&
+ cd gitconfig.clone &&
+ nr_ex=$(git log | grep "^Author:.*example.com" | wc -l) &&
+ nr_rev=$(git rev-list HEAD | wc -l) &&
+ test $nr_rev -eq $nr_ex
+ )
+'
+
+test_debug 'GIT_DIR=gitconfig.clone/.git git log'
+
test_done
diff --git a/t/t9146-git-svn-empty-dirs.sh b/t/t9146-git-svn-empty-dirs.sh
new file mode 100755
index 0000000000..70c52c1f97
--- /dev/null
+++ b/t/t9146-git-svn-empty-dirs.sh
@@ -0,0 +1,108 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Eric Wong
+
+test_description='git svn creates empty directories'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize repo' '
+ for i in a b c d d/e d/e/f "weird file name"
+ do
+ svn_cmd mkdir -m "mkdir $i" "$svnrepo"/"$i"
+ done
+'
+
+test_expect_success 'clone' 'git svn clone "$svnrepo" cloned'
+
+test_expect_success 'empty directories exist' '
+ (
+ cd cloned &&
+ for i in a b c d d/e d/e/f "weird file name"
+ do
+ if ! test -d "$i"
+ then
+ echo >&2 "$i does not exist"
+ exit 1
+ fi
+ done
+ )
+'
+
+test_expect_success 'more emptiness' '
+ svn_cmd mkdir -m "bang bang" "$svnrepo"/"! !"
+'
+
+test_expect_success 'git svn rebase creates empty directory' '
+ ( cd cloned && git svn rebase )
+ test -d cloned/"! !"
+'
+
+test_expect_success 'git svn mkdirs recreates empty directories' '
+ (
+ cd cloned &&
+ rm -r * &&
+ git svn mkdirs &&
+ for i in a b c d d/e d/e/f "weird file name" "! !"
+ do
+ if ! test -d "$i"
+ then
+ echo >&2 "$i does not exist"
+ exit 1
+ fi
+ done
+ )
+'
+
+test_expect_success 'git svn mkdirs -r works' '
+ (
+ cd cloned &&
+ rm -r * &&
+ git svn mkdirs -r7 &&
+ for i in a b c d d/e d/e/f "weird file name"
+ do
+ if ! test -d "$i"
+ then
+ echo >&2 "$i does not exist"
+ exit 1
+ fi
+ done
+
+ if test -d "! !"
+ then
+ echo >&2 "$i should not exist"
+ exit 1
+ fi
+
+ git svn mkdirs -r8 &&
+ if ! test -d "! !"
+ then
+ echo >&2 "$i not exist"
+ exit 1
+ fi
+ )
+'
+
+test_expect_success 'initialize trunk' '
+ for i in trunk trunk/a trunk/"weird file name"
+ do
+ svn_cmd mkdir -m "mkdir $i" "$svnrepo"/"$i"
+ done
+'
+
+test_expect_success 'clone trunk' 'git svn clone -s "$svnrepo" trunk'
+
+test_expect_success 'empty directories in trunk exist' '
+ (
+ cd trunk &&
+ for i in a "weird file name"
+ do
+ if ! test -d "$i"
+ then
+ echo >&2 "$i does not exist"
+ exit 1
+ fi
+ done
+ )
+'
+
+test_done
diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh
index 9bee516358..f57daf401a 100755
--- a/t/t9151-svn-mergeinfo.sh
+++ b/t/t9151-svn-mergeinfo.sh
@@ -15,7 +15,11 @@ test_expect_success 'load svn dump' "
git svn fetch --all
"
-test_expect_success 'svn merges were represented coming in' "
+test_expect_success 'represent svn merges without intervening commits' "
+ [ `git cat-file commit HEAD^1 | grep parent | wc -l` -eq 2 ]
+ "
+
+test_expect_success 'represent svn merges with intervening commits' "
[ `git cat-file commit HEAD | grep parent | wc -l` -eq 2 ]
"
diff --git a/t/t9151/make-svnmerge-dump b/t/t9151/make-svnmerge-dump
index e35d64d585..7e3da75f86 100644
--- a/t/t9151/make-svnmerge-dump
+++ b/t/t9151/make-svnmerge-dump
@@ -28,6 +28,10 @@ svn cp trunk branches/left
echo "Committing BRANCH POINT"
svn commit -m "make left branch"
+svn cp trunk branches/right
+
+echo "Committing other BRANCH POINT"
+svn commit -m "make right branch"
cd branches/left/
#$sm init
@@ -64,7 +68,33 @@ git cat-file blob b51ad431:Makefile > Makefile
svn resolved Makefile
-svn commit -m "Merge trunk"
+svn commit -m "Merge trunk 1"
+
+# create commits on both branches
+
+cd ../branches/left
+git cat-file blob ff5ebe39:Makefile > Makefile
+echo "Committing BRANCH UPDATE 4"
+svn commit -m "left update 4"
+
+cd ../right
+git cat-file blob b5039db6:Makefile > Makefile
+echo "Committing other BRANCH UPDATE 1"
+svn commit -m "right update 1"
+
+# merge to trun again
+
+cd ../..
+svn update
+cd trunk
+
+svn merge ../branches/left --accept postpone
+
+git cat-file blob b51ad431:Makefile > Makefile
+
+svn resolved Makefile
+
+svn commit -m "Merge trunk 2"
cd ../..
diff --git a/t/t9151/svn-mergeinfo.dump b/t/t9151/svn-mergeinfo.dump
index 2153187c9b..11a883fda9 100644
--- a/t/t9151/svn-mergeinfo.dump
+++ b/t/t9151/svn-mergeinfo.dump
@@ -1,6 +1,6 @@
SVN-fs-dump-format-version: 2
-UUID: 1ce241d1-ba54-4eb9-bded-03057fe48a33
+UUID: 1530d5a2-a1dc-4438-8ad5-d95e96db8945
Revision-number: 0
Prop-content-length: 56
@@ -9,12 +9,12 @@ Content-length: 56
K 8
svn:date
V 27
-2009-10-20T01:33:37.692723Z
+2009-11-12T20:29:38.812226Z
PROPS-END
Revision-number: 1
-Prop-content-length: 123
-Content-length: 123
+Prop-content-length: 127
+Content-length: 127
K 7
svn:log
@@ -22,12 +22,12 @@ V 24
Setup trunk and branches
K 10
svn:author
-V 4
-samv
+V 8
+tallsopp
K 8
svn:date
V 27
-2009-10-20T01:33:38.159933Z
+2009-11-12T20:29:39.045856Z
PROPS-END
Node-path: branches
@@ -49,8 +49,8 @@ PROPS-END
Revision-number: 2
-Prop-content-length: 106
-Content-length: 106
+Prop-content-length: 110
+Content-length: 110
K 7
svn:log
@@ -58,12 +58,12 @@ V 8
ancestor
K 10
svn:author
-V 4
-samv
+V 8
+tallsopp
K 8
svn:date
V 27
-2009-10-20T01:33:39.160059Z
+2009-11-12T20:29:40.079587Z
PROPS-END
Node-path: trunk/Makefile
@@ -72,6 +72,7 @@ Node-action: add
Prop-content-length: 10
Text-content-length: 2401
Text-content-md5: bfd8ff778d1492dc6758567373176a89
+Text-content-sha1: 103205ce331f7d64086dba497574734f78439590
Content-length: 2411
PROPS-END
@@ -155,8 +156,8 @@ backup: clean
Revision-number: 3
-Prop-content-length: 115
-Content-length: 115
+Prop-content-length: 119
+Content-length: 119
K 7
svn:log
@@ -164,12 +165,12 @@ V 16
make left branch
K 10
svn:author
-V 4
-samv
+V 8
+tallsopp
K 8
svn:date
V 27
-2009-10-20T01:33:41.148192Z
+2009-11-12T20:29:42.084439Z
PROPS-END
Node-path: branches/left
@@ -177,27 +178,54 @@ Node-kind: dir
Node-action: add
Node-copyfrom-rev: 1
Node-copyfrom-path: trunk
-Prop-content-length: 34
-Content-length: 34
-K 13
-svn:mergeinfo
-V 0
+Node-path: branches/left/Makefile
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/Makefile
+Text-copy-source-md5: bfd8ff778d1492dc6758567373176a89
+Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590
+
+
+Revision-number: 4
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 17
+make right branch
+K 10
+svn:author
+V 8
+tallsopp
+K 8
+svn:date
+V 27
+2009-11-12T20:29:44.065452Z
PROPS-END
+Node-path: branches/right
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
-Node-path: branches/left/Makefile
+Node-path: branches/right/Makefile
Node-kind: file
Node-action: add
Node-copyfrom-rev: 2
Node-copyfrom-path: trunk/Makefile
Text-copy-source-md5: bfd8ff778d1492dc6758567373176a89
+Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590
-Revision-number: 4
-Prop-content-length: 112
-Content-length: 112
+Revision-number: 5
+Prop-content-length: 116
+Content-length: 116
K 7
svn:log
@@ -205,12 +233,12 @@ V 13
left update 1
K 10
svn:author
-V 4
-samv
+V 8
+tallsopp
K 8
svn:date
V 27
-2009-10-20T01:33:42.148773Z
+2009-11-12T20:29:45.066262Z
PROPS-END
Node-path: branches/left/Makefile
@@ -218,6 +246,7 @@ Node-kind: file
Node-action: change
Text-content-length: 2465
Text-content-md5: 16e38d9753b061731650561ce01b1195
+Text-content-sha1: 36da4b84ea9b64218ab48171dfc5c48ae025f38b
Content-length: 2465
# -DCOLLISION_CHECK if you believe that SHA1's
@@ -299,9 +328,9 @@ backup: clean
cd .. ; tar czvf dircache.tar.gz dir-cache
-Revision-number: 5
-Prop-content-length: 111
-Content-length: 111
+Revision-number: 6
+Prop-content-length: 115
+Content-length: 115
K 7
svn:log
@@ -309,12 +338,12 @@ V 12
trunk update
K 10
svn:author
-V 4
-samv
+V 8
+tallsopp
K 8
svn:date
V 27
-2009-10-20T01:33:43.159959Z
+2009-11-12T20:29:46.278498Z
PROPS-END
Node-path: trunk/Makefile
@@ -322,6 +351,7 @@ Node-kind: file
Node-action: change
Text-content-length: 2521
Text-content-md5: 0668418a621333f4aa8b6632cd63e2a0
+Text-content-sha1: 4f29afd038e52f45acb5ef8c41acfc70062a741a
Content-length: 2521
# -DCOLLISION_CHECK if you believe that SHA1's
@@ -406,9 +436,9 @@ backup: clean
cd .. ; tar czvf dircache.tar.gz dir-cache
-Revision-number: 6
-Prop-content-length: 112
-Content-length: 112
+Revision-number: 7
+Prop-content-length: 116
+Content-length: 116
K 7
svn:log
@@ -416,12 +446,12 @@ V 13
left update 2
K 10
svn:author
-V 4
-samv
+V 8
+tallsopp
K 8
svn:date
V 27
-2009-10-20T01:33:44.164175Z
+2009-11-12T20:29:47.069090Z
PROPS-END
Node-path: branches/left/Makefile
@@ -429,6 +459,7 @@ Node-kind: file
Node-action: change
Text-content-length: 2529
Text-content-md5: f6b197cc3f2e89a83e545d4bb003de73
+Text-content-sha1: 2f656677cfec0bceec85e53036ffb63e25126f8e
Content-length: 2529
# -DCOLLISION_CHECK if you believe that SHA1's
@@ -510,9 +541,9 @@ backup: clean
cd .. ; tar czvf dircache.tar.gz dir-cache
-Revision-number: 7
-Prop-content-length: 112
-Content-length: 112
+Revision-number: 8
+Prop-content-length: 116
+Content-length: 116
K 7
svn:log
@@ -520,12 +551,12 @@ V 13
left update 3
K 10
svn:author
-V 4
-samv
+V 8
+tallsopp
K 8
svn:date
V 27
-2009-10-20T01:33:45.144214Z
+2009-11-12T20:29:48.053835Z
PROPS-END
Node-path: branches/left/Makefile
@@ -533,6 +564,7 @@ Node-kind: file
Node-action: change
Text-content-length: 2593
Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba
+Text-content-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10
Content-length: 2593
# -DCOLLISION_CHECK if you believe that SHA1's
@@ -614,22 +646,22 @@ backup: clean
cd .. ; tar czvf dircache.tar.gz dir-cache
-Revision-number: 8
-Prop-content-length: 110
-Content-length: 110
+Revision-number: 9
+Prop-content-length: 116
+Content-length: 116
K 7
svn:log
-V 11
-Merge trunk
+V 13
+Merge trunk 1
K 10
svn:author
-V 4
-samv
+V 8
+tallsopp
K 8
svn:date
V 27
-2009-10-20T01:33:48.176135Z
+2009-11-12T20:29:51.098306Z
PROPS-END
Node-path: trunk
@@ -641,7 +673,7 @@ Content-length: 53
K 13
svn:mergeinfo
V 18
-/branches/left:2-7
+/branches/left:2-8
PROPS-END
@@ -650,6 +682,7 @@ Node-kind: file
Node-action: change
Text-content-length: 2713
Text-content-md5: 0afbe34f244cd662b1f97d708c687f90
+Text-content-sha1: 46d9377d783e67a9b581da110352e799517c8a14
Content-length: 2713
# -DCOLLISION_CHECK if you believe that SHA1's
@@ -734,3 +767,244 @@ backup: clean
cd .. ; tar czvf dircache.tar.gz dir-cache
+Revision-number: 10
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 13
+left update 4
+K 10
+svn:author
+V 8
+tallsopp
+K 8
+svn:date
+V 27
+2009-11-12T20:29:52.081644Z
+PROPS-END
+
+Node-path: branches/left/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2529
+Text-content-md5: f6b197cc3f2e89a83e545d4bb003de73
+Text-content-sha1: 2f656677cfec0bceec85e53036ffb63e25126f8e
+Content-length: 2529
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new glibc (at least >2.2.4) for this, and it will
+# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly
+# break unless your underlying filesystem supports those sub-second times
+# (my ext3 doesn't).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG= update-cache show-diff init-db write-tree read-tree commit-tree \
+ cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+ check-files ls-tree merge-base
+
+all: $(PROG)
+
+install: $(PROG)
+ install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+ $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+ $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+ $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+ $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+ $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+ $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o
+ $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+ $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+ $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+ $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+ $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+ $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+ $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o
+ $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+ rm -f *.o $(PROG)
+
+backup: clean
+ cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 11
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 14
+right update 1
+K 10
+svn:author
+V 8
+tallsopp
+K 8
+svn:date
+V 27
+2009-11-12T20:29:53.059636Z
+PROPS-END
+
+Node-path: branches/right/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2593
+Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba
+Text-content-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10
+Content-length: 2593
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new glibc (at least >2.2.4) for this, and it will
+# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly
+# break unless your underlying filesystem supports those sub-second times
+# (my ext3 doesn't).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG= update-cache show-diff init-db write-tree read-tree commit-tree \
+ cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+ check-files ls-tree merge-base
+
+all: $(PROG)
+
+install: $(PROG)
+ install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+ $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+ $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+ $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+ $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+ $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+ $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o
+ $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+ $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+ $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+ $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+ $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+ $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+ $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o
+ $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+ rm -f *.o $(PROG)
+
+backup: clean
+ cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 12
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 13
+Merge trunk 2
+K 10
+svn:author
+V 8
+tallsopp
+K 8
+svn:date
+V 27
+2009-11-12T20:29:56.083003Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 54
+Content-length: 54
+
+K 13
+svn:mergeinfo
+V 19
+/branches/left:2-11
+PROPS-END
+
+
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
index 821be7ce8d..b49815d108 100755
--- a/t/t9300-fast-import.sh
+++ b/t/t9300-fast-import.sh
@@ -1088,4 +1088,170 @@ INPUT_END
test_expect_success 'P: fail on blob mark in gitlink' '
test_must_fail git fast-import <input'
+###
+### series Q (notes)
+###
+
+note1_data="Note for the first commit"
+note2_data="Note for the second commit"
+note3_data="Note for the third commit"
+
+test_tick
+cat >input <<INPUT_END
+blob
+mark :2
+data <<EOF
+$file2_data
+EOF
+
+commit refs/heads/notes-test
+mark :3
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+first (:3)
+COMMIT
+
+M 644 :2 file2
+
+blob
+mark :4
+data $file4_len
+$file4_data
+commit refs/heads/notes-test
+mark :5
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+second (:5)
+COMMIT
+
+M 644 :4 file4
+
+commit refs/heads/notes-test
+mark :6
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+third (:6)
+COMMIT
+
+M 644 inline file5
+data <<EOF
+$file5_data
+EOF
+
+M 755 inline file6
+data <<EOF
+$file6_data
+EOF
+
+blob
+mark :7
+data <<EOF
+$note1_data
+EOF
+
+blob
+mark :8
+data <<EOF
+$note2_data
+EOF
+
+commit refs/notes/foobar
+mark :9
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes (:9)
+COMMIT
+
+N :7 :3
+N :8 :5
+N inline :6
+data <<EOF
+$note3_data
+EOF
+
+INPUT_END
+test_expect_success \
+ 'Q: commit notes' \
+ 'git fast-import <input &&
+ git whatchanged notes-test'
+test_expect_success \
+ 'Q: verify pack' \
+ 'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+commit1=$(git rev-parse notes-test~2)
+commit2=$(git rev-parse notes-test^)
+commit3=$(git rev-parse notes-test)
+
+cat >expect <<EOF
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+first (:3)
+EOF
+test_expect_success \
+ 'Q: verify first commit' \
+ 'git cat-file commit notes-test~2 | sed 1d >actual &&
+ test_cmp expect actual'
+
+cat >expect <<EOF
+parent $commit1
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+second (:5)
+EOF
+test_expect_success \
+ 'Q: verify second commit' \
+ 'git cat-file commit notes-test^ | sed 1d >actual &&
+ test_cmp expect actual'
+
+cat >expect <<EOF
+parent $commit2
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+third (:6)
+EOF
+test_expect_success \
+ 'Q: verify third commit' \
+ 'git cat-file commit notes-test | sed 1d >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 (:9)
+EOF
+test_expect_success \
+ 'Q: verify 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 notes tree' \
+ 'git cat-file -p refs/notes/foobar^{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'
+
+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'
+
+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'
+
test_done
diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh
index 64f947d75b..c2ec3cb4bd 100755
--- a/t/t9400-git-cvsserver-server.sh
+++ b/t/t9400-git-cvsserver-server.sh
@@ -20,7 +20,7 @@ then
say 'skipping git-cvsserver tests, cvs not found'
test_done
fi
-perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
+"$PERL_PATH" -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
say 'skipping git-cvsserver tests, Perl SQLite interface unavailable'
test_done
}
diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh
index aca40c1b1f..40637d6782 100755
--- a/t/t9401-git-cvsserver-crlf.sh
+++ b/t/t9401-git-cvsserver-crlf.sh
@@ -57,7 +57,7 @@ then
say 'skipping git-cvsserver tests, perl not available'
test_done
fi
-perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
+"$PERL_PATH" -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
say 'skipping git-cvsserver tests, Perl SQLite interface unavailable'
test_done
}
diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh
index d0ff21d426..0688a57e1d 100755
--- a/t/t9501-gitweb-standalone-http-status.sh
+++ b/t/t9501-gitweb-standalone-http-status.sh
@@ -75,4 +75,43 @@ test_expect_success \
test_debug 'cat gitweb.output'
+# ----------------------------------------------------------------------
+# snapshot hash ids
+
+test_expect_success 'snapshots: good tree-ish id' '
+ gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" &&
+ grep "Status: 200 OK" gitweb.output
+'
+test_debug 'cat gitweb.output'
+
+test_expect_success 'snapshots: bad tree-ish id' '
+ gitweb_run "p=.git;a=snapshot;h=frizzumFrazzum;sf=tgz" &&
+ grep "404 - Object does not exist" gitweb.output
+'
+test_debug 'cat gitweb.output'
+
+test_expect_success 'snapshots: bad tree-ish id (tagged object)' '
+ echo object > tag-object &&
+ git add tag-object &&
+ git commit -m "Object to be tagged" &&
+ git tag tagged-object `git hash-object tag-object` &&
+ gitweb_run "p=.git;a=snapshot;h=tagged-object;sf=tgz" &&
+ grep "400 - Object is not a tree-ish" gitweb.output
+'
+test_debug 'cat gitweb.output'
+
+test_expect_success 'snapshots: good object id' '
+ ID=`git rev-parse --verify HEAD` &&
+ gitweb_run "p=.git;a=snapshot;h=$ID;sf=tgz" &&
+ grep "Status: 200 OK" gitweb.output
+'
+test_debug 'cat gitweb.output'
+
+test_expect_success 'snapshots: bad object id' '
+ gitweb_run "p=.git;a=snapshot;h=abcdef01234;sf=tgz" &&
+ grep "404 - Object does not exist" gitweb.output
+'
+test_debug 'cat gitweb.output'
+
+
test_done
diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh
new file mode 100755
index 0000000000..dd83890001
--- /dev/null
+++ b/t/t9502-gitweb-standalone-parse-output.sh
@@ -0,0 +1,115 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Mark Rada
+#
+
+test_description='gitweb as standalone script (parsing script output).
+
+This test runs gitweb (git web interface) as a CGI script from the
+commandline, and checks that it produces the correct output, either
+in the HTTP header or the actual script output.'
+
+
+. ./gitweb-lib.sh
+
+# ----------------------------------------------------------------------
+# snapshot file name and prefix
+
+cat >>gitweb_config.perl <<\EOF
+
+$known_snapshot_formats{'tar'} = {
+ 'display' => 'tar',
+ 'type' => 'application/x-tar',
+ 'suffix' => '.tar',
+ 'format' => 'tar',
+};
+
+$feature{'snapshot'}{'default'} = ['tar'];
+EOF
+
+# Call check_snapshot with the arguments "<basename> [<prefix>]"
+#
+# This will check that gitweb HTTP header contains proposed filename
+# as <basename> with '.tar' suffix added, and that generated tarfile
+# (gitweb message body) has <prefix> as prefix for al files in tarfile
+#
+# <prefix> default to <basename>
+check_snapshot () {
+ basename=$1
+ prefix=${2:-"$1"}
+ echo "basename=$basename"
+ grep "filename=.*$basename.tar" gitweb.headers >/dev/null 2>&1 &&
+ "$TAR" tf gitweb.body >file_list &&
+ ! grep -v "^$prefix/" file_list
+}
+
+test_expect_success setup '
+ test_commit first foo &&
+ git branch xx/test &&
+ FULL_ID=$(git rev-parse --verify HEAD) &&
+ SHORT_ID=$(git rev-parse --verify --short=7 HEAD)
+'
+test_debug '
+ echo "FULL_ID = $FULL_ID"
+ echo "SHORT_ID = $SHORT_ID"
+'
+
+test_expect_success 'snapshot: full sha1' '
+ gitweb_run "p=.git;a=snapshot;h=$FULL_ID;sf=tar" &&
+ check_snapshot ".git-$SHORT_ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: shortened sha1' '
+ gitweb_run "p=.git;a=snapshot;h=$SHORT_ID;sf=tar" &&
+ check_snapshot ".git-$SHORT_ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: almost full sha1' '
+ ID=$(git rev-parse --short=30 HEAD) &&
+ gitweb_run "p=.git;a=snapshot;h=$ID;sf=tar" &&
+ check_snapshot ".git-$SHORT_ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: HEAD' '
+ gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tar" &&
+ check_snapshot ".git-HEAD-$SHORT_ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: short branch name (master)' '
+ gitweb_run "p=.git;a=snapshot;h=master;sf=tar" &&
+ ID=$(git rev-parse --verify --short=7 master) &&
+ check_snapshot ".git-master-$ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: short tag name (first)' '
+ gitweb_run "p=.git;a=snapshot;h=first;sf=tar" &&
+ ID=$(git rev-parse --verify --short=7 first) &&
+ check_snapshot ".git-first-$ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: full branch name (refs/heads/master)' '
+ gitweb_run "p=.git;a=snapshot;h=refs/heads/master;sf=tar" &&
+ ID=$(git rev-parse --verify --short=7 master) &&
+ check_snapshot ".git-master-$ID"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: full tag name (refs/tags/first)' '
+ gitweb_run "p=.git;a=snapshot;h=refs/tags/first;sf=tar" &&
+ check_snapshot ".git-first"
+'
+test_debug 'cat gitweb.headers && cat file_list'
+
+test_expect_success 'snapshot: hierarchical branch name (xx/test)' '
+ gitweb_run "p=.git;a=snapshot;h=xx/test;sf=tar" &&
+ ! grep "filename=.*/" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_done
diff --git a/t/t9700-perl-git.sh b/t/t9700-perl-git.sh
index 4eb7d3f7f0..8686086dde 100755
--- a/t/t9700-perl-git.sh
+++ b/t/t9700-perl-git.sh
@@ -11,7 +11,7 @@ if ! test_have_prereq PERL; then
test_done
fi
-perl -MTest::More -e 0 2>/dev/null || {
+"$PERL_PATH" -MTest::More -e 0 2>/dev/null || {
say "Perl Test::More unavailable, skipping test"
test_done
}
@@ -48,6 +48,6 @@ test_expect_success \
test_external_without_stderr \
'Perl API' \
- perl "$TEST_DIRECTORY"/t9700/test.pl
+ "$PERL_PATH" "$TEST_DIRECTORY"/t9700/test.pl
test_done
diff --git a/t/t9700/test.pl b/t/t9700/test.pl
index 6c70aec020..666722d9bf 100755
--- a/t/t9700/test.pl
+++ b/t/t9700/test.pl
@@ -13,7 +13,7 @@ use File::Basename;
BEGIN { use_ok('Git') }
# set up
-our $abs_repo_dir = Cwd->cwd;
+our $abs_repo_dir = cwd();
ok(our $r = Git->repository(Directory => "."), "open repository");
# config
diff --git a/t/test-lib.sh b/t/test-lib.sh
index f2ca536472..ec3336aba5 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -30,7 +30,7 @@ TZ=UTC
TERM=dumb
export LANG LC_ALL PAGER TERM TZ
EDITOR=:
-VISUAL=:
+unset VISUAL
unset GIT_EDITOR
unset AUTHOR_DATE
unset AUTHOR_EMAIL
@@ -58,7 +58,7 @@ GIT_MERGE_VERBOSITY=5
export GIT_MERGE_VERBOSITY
export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
-export EDITOR VISUAL
+export EDITOR
GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u}
# Protect ourselves from common misconfiguration to export
@@ -207,8 +207,8 @@ trap 'die' EXIT
test_set_editor () {
FAKE_EDITOR="$1"
export FAKE_EDITOR
- VISUAL='"$FAKE_EDITOR"'
- export VISUAL
+ EDITOR='"$FAKE_EDITOR"'
+ export EDITOR
}
test_tick () {
diff --git a/templates/hooks--pre-commit.sample b/templates/hooks--pre-commit.sample
index 043970a751..439eefda51 100755
--- a/templates/hooks--pre-commit.sample
+++ b/templates/hooks--pre-commit.sample
@@ -7,6 +7,14 @@
#
# To enable this hook, rename this file to "pre-commit".
+if git-rev-parse --verify HEAD >/dev/null 2>&1
+then
+ against=HEAD
+else
+ # Initial commit: diff against an empty tree object
+ against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+fi
+
# If you want to allow non-ascii filenames set this variable to true.
allownonascii=$(git config hooks.allownonascii)
@@ -17,7 +25,7 @@ if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
- test "$(git diff --cached --name-only --diff-filter=A -z |
+ test "$(git diff --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0')"
then
echo "Error: Attempt to add a non-ascii file name."
@@ -35,12 +43,4 @@ then
exit 1
fi
-if git-rev-parse --verify HEAD >/dev/null 2>&1
-then
- against=HEAD
-else
- # Initial commit: diff against an empty tree object
- against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
-fi
-
exec git diff-index --check --cached $against --
diff --git a/transport-helper.c b/transport-helper.c
index f57e84c676..5078c7100f 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -1,16 +1,20 @@
#include "cache.h"
#include "transport.h"
-
+#include "quote.h"
#include "run-command.h"
#include "commit.h"
#include "diff.h"
#include "revision.h"
+#include "quote.h"
struct helper_data
{
const char *name;
struct child_process *helper;
- unsigned fetch : 1;
+ FILE *out;
+ unsigned fetch : 1,
+ option : 1,
+ push : 1;
};
static struct child_process *get_helper(struct transport *transport)
@@ -18,7 +22,6 @@ static struct child_process *get_helper(struct transport *transport)
struct helper_data *data = transport->data;
struct strbuf buf = STRBUF_INIT;
struct child_process *helper;
- FILE *file;
if (data->helper)
return data->helper;
@@ -39,15 +42,19 @@ static struct child_process *get_helper(struct transport *transport)
write_str_in_full(helper->in, "capabilities\n");
- file = xfdopen(helper->out, "r");
+ data->out = xfdopen(helper->out, "r");
while (1) {
- if (strbuf_getline(&buf, file, '\n') == EOF)
+ if (strbuf_getline(&buf, data->out, '\n') == EOF)
exit(128); /* child died, message supplied already */
if (!*buf.buf)
break;
if (!strcmp(buf.buf, "fetch"))
data->fetch = 1;
+ if (!strcmp(buf.buf, "option"))
+ data->option = 1;
+ if (!strcmp(buf.buf, "push"))
+ data->push = 1;
}
return data->helper;
}
@@ -58,23 +65,104 @@ static int disconnect_helper(struct transport *transport)
if (data->helper) {
write_str_in_full(data->helper->in, "\n");
close(data->helper->in);
+ fclose(data->out);
finish_command(data->helper);
free((char *)data->helper->argv[0]);
free(data->helper->argv);
free(data->helper);
data->helper = NULL;
}
+ free(data);
return 0;
}
+static const char *unsupported_options[] = {
+ TRANS_OPT_UPLOADPACK,
+ TRANS_OPT_RECEIVEPACK,
+ TRANS_OPT_THIN,
+ TRANS_OPT_KEEP
+ };
+static const char *boolean_options[] = {
+ TRANS_OPT_THIN,
+ TRANS_OPT_KEEP,
+ TRANS_OPT_FOLLOWTAGS
+ };
+
+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;
+
+ if (!data->option)
+ return 1;
+
+ for (i = 0; i < ARRAY_SIZE(unsupported_options); i++) {
+ if (!strcmp(name, unsupported_options[i]))
+ return 1;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(boolean_options); i++) {
+ if (!strcmp(name, boolean_options[i])) {
+ is_bool = 1;
+ break;
+ }
+ }
+
+ strbuf_addf(&buf, "option %s ", name);
+ if (is_bool)
+ strbuf_addstr(&buf, value ? "true" : "false");
+ else
+ 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 */
+
+ if (!strcmp(buf.buf, "ok"))
+ ret = 0;
+ else if (!prefixcmp(buf.buf, "error")) {
+ ret = -1;
+ } else if (!strcmp(buf.buf, "unsupported"))
+ ret = 1;
+ else {
+ warning("%s unexpectedly said: '%s'", data->name, buf.buf);
+ ret = 1;
+ }
+ strbuf_release(&buf);
+ return ret;
+}
+
+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));
+
+ set_helper_option(t, "progress", !no_progress ? "true" : "false");
+
+ n = snprintf(buf, sizeof(buf), "%d", v + 1);
+ if (n >= sizeof(buf))
+ die("impossibly large verbosity value");
+ set_helper_option(t, "verbosity", buf);
+}
+
static int fetch_with_fetch(struct transport *transport,
int nr_heads, const struct ref **to_fetch)
{
- struct child_process *helper = get_helper(transport);
- FILE *file = xfdopen(helper->out, "r");
+ struct helper_data *data = transport->data;
int i;
struct strbuf buf = STRBUF_INIT;
+ standard_options(transport);
+
for (i = 0; i < nr_heads; i++) {
const struct ref *posn = to_fetch[i];
if (posn->status & REF_STATUS_UPTODATE)
@@ -82,12 +170,30 @@ static int fetch_with_fetch(struct transport *transport,
strbuf_addf(&buf, "fetch %s %s\n",
sha1_to_hex(posn->old_sha1), posn->name);
- write_in_full(helper->in, buf.buf, buf.len);
- strbuf_reset(&buf);
+ }
+
+ 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);
- if (strbuf_getline(&buf, file, '\n') == EOF)
+ while (1) {
+ strbuf_reset(&buf);
+ if (strbuf_getline(&buf, data->out, '\n') == EOF)
exit(128); /* child died, message supplied already */
+
+ if (!prefixcmp(buf.buf, "lock ")) {
+ const char *name = buf.buf + 5;
+ if (transport->pack_lockfile)
+ warning("%s also locked %s", data->name, name);
+ else
+ transport->pack_lockfile = xstrdup(name);
+ }
+ else if (!buf.len)
+ break;
+ else
+ warning("%s unexpectedly said: '%s'", data->name, buf.buf);
}
+ strbuf_release(&buf);
return 0;
}
@@ -111,23 +217,151 @@ static int fetch(struct transport *transport,
return -1;
}
+static int push_refs(struct transport *transport,
+ struct ref *remote_refs, int flags)
+{
+ int force_all = flags & TRANSPORT_PUSH_FORCE;
+ int mirror = flags & TRANSPORT_PUSH_MIRROR;
+ struct helper_data *data = transport->data;
+ struct strbuf buf = STRBUF_INIT;
+ struct child_process *helper;
+ struct ref *ref;
+
+ if (!remote_refs)
+ 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)
+ 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;
+ }
+
+ if (force_all)
+ ref->force = 1;
+
+ strbuf_addstr(&buf, "push ");
+ if (!ref->deletion) {
+ if (ref->force)
+ strbuf_addch(&buf, '+');
+ if (ref->peer_ref)
+ strbuf_addstr(&buf, ref->peer_ref->name);
+ else
+ strbuf_addstr(&buf, sha1_to_hex(ref->new_sha1));
+ }
+ strbuf_addch(&buf, ':');
+ strbuf_addstr(&buf, ref->name);
+ strbuf_addch(&buf, '\n');
+ }
+ if (buf.len == 0)
+ return 0;
+
+ transport->verbose = flags & TRANSPORT_PUSH_VERBOSE ? 1 : 0;
+ standard_options(transport);
+
+ if (flags & TRANSPORT_PUSH_DRY_RUN) {
+ if (set_helper_option(transport, "dry-run", "true") != 0)
+ die("helper %s does not support dry-run", data->name);
+ }
+
+ strbuf_addch(&buf, '\n');
+ if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
+ exit(128);
+
+ 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 */
+ if (!buf.len)
+ break;
+
+ if (!prefixcmp(buf.buf, "ok ")) {
+ status = REF_STATUS_OK;
+ refname = buf.buf + 3;
+ } else if (!prefixcmp(buf.buf, "error ")) {
+ status = REF_STATUS_REMOTE_REJECT;
+ refname = buf.buf + 6;
+ } else
+ die("expected ok/error, helper said '%s'\n", buf.buf);
+
+ msg = strchr(refname, ' ');
+ if (msg) {
+ struct strbuf msg_buf = STRBUF_INIT;
+ const char *end;
+
+ *msg++ = '\0';
+ if (!unquote_c_style(&msg_buf, msg, &end))
+ msg = strbuf_detach(&msg_buf, NULL);
+ else
+ msg = xstrdup(msg);
+ strbuf_release(&msg_buf);
+
+ if (!strcmp(msg, "no match")) {
+ status = REF_STATUS_NONE;
+ free(msg);
+ msg = NULL;
+ }
+ else if (!strcmp(msg, "up to date")) {
+ status = REF_STATUS_UPTODATE;
+ free(msg);
+ msg = NULL;
+ }
+ else if (!strcmp(msg, "non-fast forward")) {
+ status = REF_STATUS_REJECT_NONFASTFORWARD;
+ free(msg);
+ msg = NULL;
+ }
+ }
+
+ if (ref)
+ ref = find_ref_by_name(ref, refname);
+ if (!ref)
+ ref = find_ref_by_name(remote_refs, refname);
+ if (!ref) {
+ warning("helper reported unexpected status of %s", refname);
+ continue;
+ }
+
+ ref->status = status;
+ ref->remote_status = msg;
+ }
+ strbuf_release(&buf);
+ return 0;
+}
+
static struct ref *get_refs_list(struct transport *transport, int for_push)
{
+ struct helper_data *data = transport->data;
struct child_process *helper;
struct ref *ret = NULL;
struct ref **tail = &ret;
struct ref *posn;
struct strbuf buf = STRBUF_INIT;
- FILE *file;
helper = get_helper(transport);
- write_str_in_full(helper->in, "list\n");
+ if (data->push && for_push)
+ write_str_in_full(helper->in, "list for-push\n");
+ else
+ write_str_in_full(helper->in, "list\n");
- file = xfdopen(helper->out, "r");
while (1) {
char *eov, *eon;
- if (strbuf_getline(&buf, file, '\n') == EOF)
+ if (strbuf_getline(&buf, data->out, '\n') == EOF)
exit(128); /* child died, message supplied already */
if (!*buf.buf)
@@ -161,8 +395,10 @@ int transport_helper_init(struct transport *transport, const char *name)
data->name = name;
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 = disconnect_helper;
return 0;
}
diff --git a/transport.c b/transport.c
index 644a30a0b2..7362ec09b2 100644
--- a/transport.c
+++ b/transport.c
@@ -349,35 +349,6 @@ static int rsync_transport_push(struct transport *transport,
return result;
}
-#ifndef NO_CURL
-static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
-{
- const char **argv;
- int argc;
-
- if (flags & TRANSPORT_PUSH_MIRROR)
- return error("http transport does not support mirror mode");
-
- argv = xmalloc((refspec_nr + 12) * sizeof(char *));
- argv[0] = "http-push";
- argc = 1;
- if (flags & TRANSPORT_PUSH_ALL)
- argv[argc++] = "--all";
- if (flags & TRANSPORT_PUSH_FORCE)
- argv[argc++] = "--force";
- if (flags & TRANSPORT_PUSH_DRY_RUN)
- argv[argc++] = "--dry-run";
- if (flags & TRANSPORT_PUSH_VERBOSE)
- argv[argc++] = "--verbose";
- argv[argc++] = transport->url;
- while (refspec_nr--)
- argv[argc++] = *refspec++;
- argv[argc] = NULL;
- return !!run_command_v_opt(argv, RUN_GIT_CMD);
-}
-
-#endif
-
struct bundle_transport_data {
int fd;
struct bundle_header header;
@@ -668,7 +639,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
break;
case REF_STATUS_REJECT_NONFASTFORWARD:
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
- "non-fast forward", porcelain);
+ "non-fast-forward", porcelain);
break;
case REF_STATUS_REMOTE_REJECT:
print_ref_status('!', "[remote rejected]", ref,
@@ -760,6 +731,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
NULL);
}
+ 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;
@@ -812,6 +784,9 @@ struct transport *transport_get(struct remote *remote, const char *url)
{
struct transport *ret = xcalloc(1, sizeof(*ret));
+ if (!remote)
+ die("No remote provided to transport_get()");
+
ret->remote = remote;
ret->url = url;
@@ -826,8 +801,6 @@ struct transport *transport_get(struct remote *remote, const char *url)
transport_helper_init(ret, "curl");
#ifdef NO_CURL
error("git was compiled without libcurl support.");
-#else
- ret->push = curl_transport_push;
#endif
} else if (is_local(url) && is_file(url)) {
@@ -849,10 +822,10 @@ struct transport *transport_get(struct remote *remote, const char *url)
data->thin = 1;
data->conn = NULL;
data->uploadpack = "git-upload-pack";
- if (remote && remote->uploadpack)
+ if (remote->uploadpack)
data->uploadpack = remote->uploadpack;
data->receivepack = "git-receive-pack";
- if (remote && remote->receivepack)
+ if (remote->receivepack)
data->receivepack = remote->receivepack;
}
diff --git a/transport.h b/transport.h
index c14da6f1e5..e4e6177e26 100644
--- a/transport.h
+++ b/transport.h
@@ -25,7 +25,7 @@ struct transport {
int (*disconnect)(struct transport *connection);
char *pack_lockfile;
- signed verbose : 2;
+ signed verbose : 3;
/* Force progress even if the output is not a tty */
unsigned progress : 1;
};
diff --git a/unpack-file.c b/unpack-file.c
index ac9cbf7cd8..e9d8934691 100644
--- a/unpack-file.c
+++ b/unpack-file.c
@@ -28,7 +28,7 @@ int main(int argc, char **argv)
git_extract_argv0_path(argv[0]);
- if (argc != 2)
+ 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]);
diff --git a/unpack-trees.c b/unpack-trees.c
index 720f7a1616..dd5999c356 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -277,6 +277,17 @@ static int unpack_nondirectories(int n, unsigned long mask,
return 0;
}
+static int unpack_failed(struct unpack_trees_options *o, const char *message)
+{
+ discard_index(&o->result);
+ if (!o->gently) {
+ if (message)
+ return error("%s", message);
+ return -1;
+ }
+ return -1;
+}
+
static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info)
{
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
@@ -294,7 +305,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
int cmp = compare_entry(ce, info, p);
if (cmp < 0) {
if (unpack_index_entry(ce, o) < 0)
- return -1;
+ return unpack_failed(o, NULL);
continue;
}
if (!cmp) {
@@ -352,17 +363,6 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
return mask;
}
-static int unpack_failed(struct unpack_trees_options *o, const char *message)
-{
- discard_index(&o->result);
- if (!o->gently) {
- if (message)
- return error("%s", message);
- return -1;
- }
- return -1;
-}
-
/*
* N-way merge "len" trees. Returns 0 on success, -1 on failure to manipulate the
* resulting index, -2 on failure to reflect the changes to the work tree.
@@ -617,7 +617,7 @@ static int verify_absent(struct cache_entry *ce, const char *action,
* found "foo/." in the working tree.
* This is tricky -- if we have modified
* files that are in "foo/" we would lose
- * it.
+ * them.
*/
ret = verify_clean_subdirectory(ce, action, o);
if (ret < 0)
@@ -895,7 +895,7 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
* Two-way merge.
*
* The rule is to "carry forward" what is in the index without losing
- * information across a "fast forward", favoring a successful merge
+ * information across a "fast-forward", favoring a successful merge
* over a merge failure when it makes sense. For details of the
* "carry forward" rule, please see <Documentation/git-read-tree.txt>.
*
diff --git a/upload-pack.c b/upload-pack.c
index 38ddac2e86..6bfb500eb4 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -39,6 +39,8 @@ static unsigned int timeout;
*/
static int use_sideband;
static int debug_fd;
+static int advertise_refs;
+static int stateless_rpc;
static void reset_timeout(void)
{
@@ -308,6 +310,23 @@ static void create_pack_file(void)
}
continue;
}
+ if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) {
+ /* Status ready; we ship that in the side-band
+ * or dump to the standard error.
+ */
+ sz = xread(pack_objects.err, progress,
+ sizeof(progress));
+ if (0 < sz)
+ send_client_data(2, progress, sz);
+ else if (sz == 0) {
+ close(pack_objects.err);
+ pack_objects.err = -1;
+ }
+ else
+ goto fail;
+ /* give priority to status messages */
+ continue;
+ }
if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) {
/* Data ready; we keep the last byte to ourselves
* in case we detect broken rev-list, so that we
@@ -345,21 +364,6 @@ static void create_pack_file(void)
if (sz < 0)
goto fail;
}
- if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) {
- /* Status ready; we ship that in the side-band
- * or dump to the standard error.
- */
- sz = xread(pack_objects.err, progress,
- sizeof(progress));
- if (0 < sz)
- send_client_data(2, progress, sz);
- else if (sz == 0) {
- close(pack_objects.err);
- pack_objects.err = -1;
- }
- else
- goto fail;
- }
}
if (finish_command(&pack_objects)) {
@@ -498,7 +502,7 @@ static int get_common_commits(void)
{
static char line[1000];
unsigned char sha1[20];
- char hex[41], last_hex[41];
+ char last_hex[41];
save_commit_buffer = 0;
@@ -509,25 +513,30 @@ static int get_common_commits(void)
if (!len) {
if (have_obj.nr == 0 || multi_ack)
packet_write(1, "NAK\n");
+ if (stateless_rpc)
+ exit(0);
continue;
}
strip(line, len);
if (!prefixcmp(line, "have ")) {
switch (got_sha1(line+5, sha1)) {
case -1: /* they have what we do not */
- if (multi_ack && ok_to_give_up())
- packet_write(1, "ACK %s continue\n",
- sha1_to_hex(sha1));
+ if (multi_ack && ok_to_give_up()) {
+ const char *hex = sha1_to_hex(sha1);
+ if (multi_ack == 2)
+ packet_write(1, "ACK %s ready\n", hex);
+ else
+ packet_write(1, "ACK %s continue\n", hex);
+ }
break;
default:
- memcpy(hex, sha1_to_hex(sha1), 41);
- if (multi_ack) {
- const char *msg = "ACK %s continue\n";
- packet_write(1, msg, hex);
- memcpy(last_hex, hex, 41);
- }
+ memcpy(last_hex, sha1_to_hex(sha1), 41);
+ if (multi_ack == 2)
+ packet_write(1, "ACK %s common\n", last_hex);
+ else if (multi_ack)
+ packet_write(1, "ACK %s continue\n", last_hex);
else if (have_obj.nr == 1)
- packet_write(1, "ACK %s\n", hex);
+ packet_write(1, "ACK %s\n", last_hex);
break;
}
continue;
@@ -587,7 +596,9 @@ static void receive_needs(void)
get_sha1_hex(line+5, sha1_buf))
die("git upload-pack: protocol error, "
"expected to get sha, not '%s'", line);
- if (strstr(line+45, "multi_ack"))
+ if (strstr(line+45, "multi_ack_detailed"))
+ multi_ack = 2;
+ else if (strstr(line+45, "multi_ack"))
multi_ack = 1;
if (strstr(line+45, "thin-pack"))
use_thin_pack = 1;
@@ -681,7 +692,7 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
{
static const char *capabilities = "multi_ack thin-pack side-band"
" side-band-64k ofs-delta shallow no-progress"
- " include-tag";
+ " include-tag multi_ack_detailed";
struct object *o = parse_object(sha1);
if (!o)
@@ -705,12 +716,32 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
return 0;
}
+static int mark_our_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ struct object *o = parse_object(sha1);
+ if (!o)
+ die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1));
+ if (!(o->flags & OUR_REF)) {
+ o->flags |= OUR_REF;
+ nr_our_refs++;
+ }
+ return 0;
+}
+
static void upload_pack(void)
{
- reset_timeout();
- head_ref(send_ref, NULL);
- for_each_ref(send_ref, NULL);
- packet_flush(1);
+ if (advertise_refs || !stateless_rpc) {
+ reset_timeout();
+ head_ref(send_ref, NULL);
+ for_each_ref(send_ref, NULL);
+ packet_flush(1);
+ } else {
+ head_ref(mark_our_ref, NULL);
+ for_each_ref(mark_our_ref, NULL);
+ }
+ if (advertise_refs)
+ return;
+
receive_needs();
if (want_obj.nr) {
get_common_commits();
@@ -732,6 +763,14 @@ int main(int argc, char **argv)
if (arg[0] != '-')
break;
+ if (!strcmp(arg, "--advertise-refs")) {
+ advertise_refs = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--stateless-rpc")) {
+ stateless_rpc = 1;
+ continue;
+ }
if (!strcmp(arg, "--strict")) {
strict = 1;
continue;
diff --git a/usage.c b/usage.c
index c488f3afcd..79856a2b9f 100644
--- a/usage.c
+++ b/usage.c
@@ -7,14 +7,14 @@
static void report(const char *prefix, const char *err, va_list params)
{
- char msg[1024];
+ char msg[4096];
vsnprintf(msg, sizeof(msg), err, params);
fprintf(stderr, "%s%s\n", prefix, msg);
}
-static NORETURN void usage_builtin(const char *err)
+static NORETURN void usage_builtin(const char *err, va_list params)
{
- fprintf(stderr, "usage: %s\n", err);
+ report("usage: ", err, params);
exit(129);
}
@@ -36,7 +36,7 @@ static void warn_builtin(const char *warn, va_list params)
/* If we are in a dlopen()ed .so write to a global variable would segfault
* (ugh), so keep things static. */
-static NORETURN_PTR void (*usage_routine)(const char *err) = usage_builtin;
+static NORETURN_PTR void (*usage_routine)(const char *err, va_list params) = usage_builtin;
static NORETURN_PTR void (*die_routine)(const char *err, va_list params) = die_builtin;
static void (*error_routine)(const char *err, va_list params) = error_builtin;
static void (*warn_routine)(const char *err, va_list params) = warn_builtin;
@@ -46,9 +46,18 @@ void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list param
die_routine = routine;
}
+void usagef(const char *err, ...)
+{
+ va_list params;
+
+ va_start(params, err);
+ usage_routine(err, params);
+ va_end(params);
+}
+
void usage(const char *err)
{
- usage_routine(err);
+ usagef("%s", err);
}
void die(const char *err, ...)
diff --git a/utf8.c b/utf8.c
index db706ac4ed..01d1869fa3 100644
--- a/utf8.c
+++ b/utf8.c
@@ -1,4 +1,5 @@
#include "git-compat-util.h"
+#include "strbuf.h"
#include "utf8.h"
/* This code is originally from http://www.cl.cam.ac.uk/~mgk25/ucs/ */
@@ -279,14 +280,38 @@ int is_utf8(const char *text)
return 1;
}
-static void print_spaces(int count)
+static inline void strbuf_write(struct strbuf *sb, const char *buf, int len)
+{
+ if (sb)
+ strbuf_insert(sb, sb->len, buf, len);
+ else
+ fwrite(buf, len, 1, stdout);
+}
+
+static void print_spaces(struct strbuf *buf, int count)
{
static const char s[] = " ";
while (count >= sizeof(s)) {
- fwrite(s, sizeof(s) - 1, 1, stdout);
+ strbuf_write(buf, s, sizeof(s) - 1);
count -= sizeof(s) - 1;
}
- fwrite(s, count, 1, stdout);
+ strbuf_write(buf, s, count);
+}
+
+static void strbuf_add_indented_text(struct strbuf *buf, const char *text,
+ int indent, int indent2)
+{
+ if (indent < 0)
+ indent = 0;
+ while (*text) {
+ const char *eol = strchrnul(text, '\n');
+ if (*eol == '\n')
+ eol++;
+ print_spaces(buf, indent);
+ strbuf_write(buf, text, eol - text);
+ text = eol;
+ indent = indent2;
+ }
}
/*
@@ -295,11 +320,17 @@ static void print_spaces(int count)
* If indent is negative, assume that already -indent columns have been
* consumed (and no extra indent is necessary for the first line).
*/
-int print_wrapped_text(const char *text, int indent, int indent2, int width)
+int strbuf_add_wrapped_text(struct strbuf *buf,
+ const char *text, int indent, int indent2, int width)
{
int w = indent, assume_utf8 = is_utf8(text);
const char *bol = text, *space = NULL;
+ if (width <= 0) {
+ strbuf_add_indented_text(buf, text, indent, indent2);
+ return 1;
+ }
+
if (indent < 0) {
w = -indent;
space = text;
@@ -310,21 +341,35 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width)
if (!c || isspace(c)) {
if (w < width || !space) {
const char *start = bol;
+ if (!c && text == start)
+ return w;
if (space)
start = space;
else
- print_spaces(indent);
- fwrite(start, text - start, 1, stdout);
+ print_spaces(buf, indent);
+ strbuf_write(buf, start, text - start);
if (!c)
return w;
- else if (c == '\t')
- w |= 0x07;
space = text;
+ if (c == '\t')
+ w |= 0x07;
+ else if (c == '\n') {
+ space++;
+ if (*space == '\n') {
+ strbuf_write(buf, "\n", 1);
+ goto new_line;
+ }
+ else if (!isalnum(*space))
+ goto new_line;
+ else
+ strbuf_write(buf, " ", 1);
+ }
w++;
text++;
}
else {
- putchar('\n');
+new_line:
+ strbuf_write(buf, "\n", 1);
text = bol = space + isspace(*space);
space = NULL;
w = indent = indent2;
@@ -340,6 +385,11 @@ int print_wrapped_text(const char *text, int indent, int indent2, int width)
}
}
+int print_wrapped_text(const char *text, int indent, int indent2, int width)
+{
+ return strbuf_add_wrapped_text(NULL, text, indent, indent2, width);
+}
+
int is_encoding_utf8(const char *name)
{
if (!name)
diff --git a/utf8.h b/utf8.h
index 2f1b14ff49..ae30ae4c6e 100644
--- a/utf8.h
+++ b/utf8.h
@@ -10,6 +10,8 @@ int is_utf8(const char *text);
int is_encoding_utf8(const char *name);
int print_wrapped_text(const char *text, int indent, int indent2, int len);
+int strbuf_add_wrapped_text(struct strbuf *buf,
+ const char *text, int indent, int indent2, int width);
#ifndef NO_ICONV
char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding);
diff --git a/var.c b/var.c
index 125c0d1676..d9892f85ce 100644
--- a/var.c
+++ b/var.c
@@ -8,6 +8,25 @@
static const char var_usage[] = "git var [-l | <variable>]";
+static const char *editor(int flag)
+{
+ const char *pgm = git_editor();
+
+ if (!pgm && flag & IDENT_ERROR_ON_NO_NAME)
+ die("Terminal is dumb, but EDITOR unset");
+
+ return pgm;
+}
+
+static const char *pager(int flag)
+{
+ const char *pgm = git_pager();
+
+ if (!pgm)
+ pgm = "cat";
+ return pgm;
+}
+
struct git_var {
const char *name;
const char *(*read)(int);
@@ -15,14 +34,19 @@ struct git_var {
static struct git_var git_vars[] = {
{ "GIT_COMMITTER_IDENT", git_committer_info },
{ "GIT_AUTHOR_IDENT", git_author_info },
+ { "GIT_EDITOR", editor },
+ { "GIT_PAGER", pager },
{ "", NULL },
};
static void list_vars(void)
{
struct git_var *ptr;
+ const char *val;
+
for (ptr = git_vars; ptr->read; ptr++)
- printf("%s=%s\n", ptr->name, ptr->read(IDENT_WARN_ON_NO_NAME));
+ if ((val = ptr->read(0)))
+ printf("%s=%s\n", ptr->name, val);
}
static const char *read_var(const char *var)