Code

Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Sun, 7 Feb 2010 23:52:12 +0000 (15:52 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 7 Feb 2010 23:52:12 +0000 (15:52 -0800)
* maint:
  archive: simplify archive format guessing

460 files changed:
.gitattributes
.gitignore
COPYING
Documentation/Makefile
Documentation/RelNotes-1.7.0.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/date-formats.txt [new file with mode: 0644]
Documentation/diff-format.txt
Documentation/fetch-options.txt
Documentation/git-add.txt
Documentation/git-am.txt
Documentation/git-apply.txt
Documentation/git-archimport.txt
Documentation/git-archive.txt
Documentation/git-bisect-lk2009.txt
Documentation/git-blame.txt
Documentation/git-branch.txt
Documentation/git-bundle.txt
Documentation/git-check-ref-format.txt
Documentation/git-checkout-index.txt
Documentation/git-cherry-pick.txt
Documentation/git-cherry.txt
Documentation/git-citool.txt
Documentation/git-clean.txt
Documentation/git-clone.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-config.txt
Documentation/git-cvsexportcommit.txt
Documentation/git-cvsimport.txt
Documentation/git-cvsserver.txt
Documentation/git-daemon.txt
Documentation/git-describe.txt
Documentation/git-diff-files.txt
Documentation/git-diff-index.txt
Documentation/git-diff-tree.txt
Documentation/git-diff.txt
Documentation/git-difftool.txt
Documentation/git-fast-export.txt
Documentation/git-fast-import.txt
Documentation/git-fetch-pack.txt
Documentation/git-fetch.txt
Documentation/git-filter-branch.txt
Documentation/git-fmt-merge-msg.txt
Documentation/git-for-each-ref.txt
Documentation/git-format-patch.txt
Documentation/git-fsck.txt
Documentation/git-gc.txt
Documentation/git-get-tar-commit-id.txt
Documentation/git-grep.txt
Documentation/git-gui.txt
Documentation/git-hash-object.txt
Documentation/git-help.txt
Documentation/git-http-backend.txt
Documentation/git-http-fetch.txt
Documentation/git-imap-send.txt
Documentation/git-index-pack.txt
Documentation/git-init.txt
Documentation/git-instaweb.txt
Documentation/git-log.txt
Documentation/git-ls-files.txt
Documentation/git-ls-tree.txt
Documentation/git-mailinfo.txt
Documentation/git-merge-file.txt
Documentation/git-merge-index.txt
Documentation/git-merge-one-file.txt
Documentation/git-merge.txt
Documentation/git-mergetool.txt
Documentation/git-name-rev.txt
Documentation/git-notes.txt
Documentation/git-pack-objects.txt
Documentation/git-pack-redundant.txt
Documentation/git-patch-id.txt
Documentation/git-peek-remote.txt
Documentation/git-prune.txt
Documentation/git-pull.txt
Documentation/git-push.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-receive-pack.txt
Documentation/git-reflog.txt
Documentation/git-remote-helpers.txt
Documentation/git-remote.txt
Documentation/git-repack.txt
Documentation/git-replace.txt
Documentation/git-rerere.txt
Documentation/git-reset.txt
Documentation/git-rev-list.txt
Documentation/git-rev-parse.txt
Documentation/git-revert.txt
Documentation/git-send-email.txt
Documentation/git-send-pack.txt
Documentation/git-sh-setup.txt
Documentation/git-shortlog.txt
Documentation/git-show-index.txt
Documentation/git-show-ref.txt
Documentation/git-show.txt
Documentation/git-stash.txt
Documentation/git-status.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git-symbolic-ref.txt
Documentation/git-tag.txt
Documentation/git-tar-tree.txt
Documentation/git-update-index.txt
Documentation/git-upload-archive.txt
Documentation/git-upload-pack.txt
Documentation/git-var.txt
Documentation/git-verify-pack.txt
Documentation/git-verify-tag.txt
Documentation/git-web--browse.txt
Documentation/git-whatchanged.txt
Documentation/git-write-tree.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitcore-tutorial.txt
Documentation/gitcvs-migration.txt
Documentation/gitdiffcore.txt
Documentation/githooks.txt
Documentation/gitignore.txt
Documentation/gitk.txt
Documentation/gitrepository-layout.txt
Documentation/gittutorial-2.txt
Documentation/gittutorial.txt
Documentation/gitworkflows.txt
Documentation/i18n.txt
Documentation/merge-options.txt
Documentation/merge-strategies.txt
Documentation/pretty-formats.txt
Documentation/pull-fetch-param.txt
Documentation/rev-list-options.txt
Documentation/technical/api-directory-listing.txt
Documentation/technical/api-run-command.txt
Documentation/technical/api-strbuf.txt
Documentation/technical/pack-protocol.txt
Documentation/technical/protocol-capabilities.txt
Documentation/urls-remotes.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
INSTALL
Makefile
RelNotes
advice.c
advice.h
archive.c
attr.c
attr.h
bisect.c
bisect.h
blob.c
blob.h
branch.c
builtin-add.c
builtin-apply.c
builtin-archive.c
builtin-branch.c
builtin-check-attr.c
builtin-checkout.c
builtin-clean.c
builtin-clone.c
builtin-commit-tree.c
builtin-commit.c
builtin-config.c
builtin-fetch.c
builtin-gc.c
builtin-grep.c
builtin-hash-object.c [new file with mode: 0644]
builtin-help.c
builtin-index-pack.c [new file with mode: 0644]
builtin-log.c
builtin-ls-files.c
builtin-ls-remote.c
builtin-merge-file.c
builtin-merge-index.c [new file with mode: 0644]
builtin-merge-recursive.c
builtin-merge-tree.c [new file with mode: 0644]
builtin-merge.c
builtin-mktag.c [new file with mode: 0644]
builtin-mv.c
builtin-pack-objects.c
builtin-pack-redundant.c [new file with mode: 0644]
builtin-patch-id.c [new file with mode: 0644]
builtin-push.c
builtin-read-tree.c
builtin-receive-pack.c
builtin-remote.c
builtin-rerere.c
builtin-reset.c
builtin-rev-list.c
builtin-rev-parse.c
builtin-revert.c
builtin-rm.c
builtin-send-pack.c
builtin-tag.c
builtin-unpack-file.c [new file with mode: 0644]
builtin-update-index.c
builtin-var.c [new file with mode: 0644]
builtin.h
cache.h
commit.h
compat/mingw.c
compat/mingw.h
compat/msvc.h
compat/regex/regex.c
compat/vcbuild/scripts/clink.pl
compat/win32/pthread.c [new file with mode: 0644]
compat/win32/pthread.h [new file with mode: 0644]
config.c
config.mak.in
configure.ac
connect.c
contrib/buildsystems/engine.pl
contrib/completion/git-completion.bash
contrib/examples/git-merge.sh
contrib/fast-import/git-p4
contrib/hg-to-git/hg-to-git.py
convert.c
daemon.c
date.c
diff-lib.c
diff.c
diff.h
diffcore.h
dir.c
dir.h
editor.c
entry.c
environment.c
fast-import.c
git-am.sh
git-compat-util.h
git-cvsimport.perl
git-cvsserver.perl
git-difftool--helper.sh
git-difftool.perl
git-filter-branch.sh
git-instaweb.sh
git-mergetool--lib.sh
git-pull.sh
git-rebase--interactive.sh
git-rebase.sh
git-send-email.perl
git-sh-setup.sh
git-submodule.sh
git-svn.perl
git.c
git.spec.in
git_remote_helpers/.gitignore [new file with mode: 0644]
git_remote_helpers/Makefile [new file with mode: 0644]
git_remote_helpers/__init__.py [new file with mode: 0644]
git_remote_helpers/git/__init__.py [new file with mode: 0644]
git_remote_helpers/git/git.py [new file with mode: 0644]
git_remote_helpers/setup.py [new file with mode: 0644]
git_remote_helpers/util.py [new file with mode: 0644]
gitk-git/gitk
gitk-git/po/de.po
gitk-git/po/fr.po [new file with mode: 0644]
gitk-git/po/hu.po [new file with mode: 0644]
gitk-git/po/it.po
gitk-git/po/sv.po
gitweb/Makefile [new file with mode: 0644]
gitweb/README
gitweb/gitweb.js
gitweb/gitweb.perl
grep.c
grep.h
hash-object.c [deleted file]
hex.c [new file with mode: 0644]
http-backend.c
http.c
http.h
ident.c
imap-send.c
index-pack.c [deleted file]
ll-merge.c
ll-merge.h
lockfile.c
mailmap.c
mailmap.h
match-trees.c
merge-file.c
merge-index.c [deleted file]
merge-recursive.c
merge-recursive.h
merge-tree.c [deleted file]
mktag.c [deleted file]
object.c
object.h
pack-redundant.c [deleted file]
pack-write.c
pack.h
pager.c
parse-options.c
parse-options.h
patch-delta.c
patch-id.c [deleted file]
preload-index.c
pretty.c
quote.c
quote.h
read-cache.c
refs.c
refs.h
remote-curl.c
remote.c
remote.h
rerere.c
rerere.h
resolve-undo.c [new file with mode: 0644]
resolve-undo.h [new file with mode: 0644]
revision.c
run-command.c
run-command.h
setup.c
sha1_file.c
sha1_name.c
show-index.c
strbuf.c
strbuf.h
string-list.h
submodule.c
submodule.h
symlinks.c
t/README
t/diff-lib.sh
t/gitweb-lib.sh
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/lib-rebase.sh
t/t0020-crlf.sh
t/t0021-conversion.sh
t/t0022-crlf-rename.sh
t/t0061-run-command.sh [new file with mode: 0755]
t/t0101-at-syntax.sh [new file with mode: 0755]
t/t1000-read-tree-m-3way.sh
t/t1001-read-tree-m-2way.sh
t/t1002-read-tree-m-u-2way.sh
t/t1011-read-tree-sparse-checkout.sh [new file with mode: 0755]
t/t1012-read-tree-df.sh [new file with mode: 0755]
t/t1300-repo-config.sh
t/t1506-rev-parse-diagnosis.sh [new file with mode: 0755]
t/t1507-rev-parse-upstream.sh [new file with mode: 0755]
t/t1508-at-combinations.sh [new file with mode: 0755]
t/t2012-checkout-last.sh
t/t2030-unresolve-info.sh [new file with mode: 0755]
t/t2104-update-index-skip-worktree.sh [new file with mode: 0755]
t/t2105-update-index-gitfile.sh [new file with mode: 0755]
t/t3001-ls-files-others-exclude.sh
t/t3030-merge-recursive.sh
t/t3100-ls-tree-restrict.sh
t/t3101-ls-tree-dirname.sh
t/t3200-branch.sh
t/t3400-rebase.sh
t/t3404-rebase-interactive.sh
t/t3408-rebase-multi-line.sh
t/t3415-rebase-autosquash.sh [new file with mode: 0755]
t/t3416-rebase-onto-threedots.sh [new file with mode: 0755]
t/t3501-revert-cherry-pick.sh
t/t3902-quoted.sh
t/t4006-diff-mode.sh
t/t4012-diff-binary.sh
t/t4014-format-patch.sh
t/t4015-diff-whitespace.sh
t/t4027-diff-submodule.sh
t/t4030-diff-textconv.sh
t/t4031-diff-rewrite-binary.sh
t/t4034-diff-words.sh
t/t4038-diff-combined.sh
t/t4040-whitespace-status.sh [new file with mode: 0755]
t/t4041-diff-submodule.sh
t/t4120-apply-popt.sh
t/t4125-apply-ws-fuzz.sh
t/t4133-apply-filenames.sh [new file with mode: 0755]
t/t4150-am.sh
t/t4200-rerere.sh
t/t4202-log.sh
t/t5300-pack-object.sh
t/t5400-send-pack.sh
t/t5401-update-hooks.sh
t/t5405-send-pack-rewind.sh
t/t5505-remote.sh
t/t5516-fetch-push.sh
t/t5517-push-mirror.sh
t/t5522-pull-symlink.sh
t/t5523-push-upstream.sh [new file with mode: 0755]
t/t5524-pull-msg.sh [new file with mode: 0755]
t/t5541-http-push.sh
t/t5560-http-backend-noserver.sh [new file with mode: 0755]
t/t5560-http-backend.sh [deleted file]
t/t5561-http-backend.sh [new file with mode: 0755]
t/t556x_common [new file with mode: 0755]
t/t5701-clone-local.sh
t/t5702-clone-options.sh
t/t5705-clone-2gb.sh
t/t6006-rev-list-format.sh
t/t6012-rev-list-simplify.sh
t/t6018-rev-list-glob.sh [new file with mode: 0755]
t/t6023-merge-file.sh
t/t6026-merge-attr.sh
t/t6029-merge-subtree.sh
t/t6030-bisect-porcelain.sh
t/t6033-merge-crlf.sh
t/t6035-merge-dir-to-symlink.sh
t/t6037-merge-ours-theirs.sh [new file with mode: 0755]
t/t6040-tracking-info.sh
t/t7001-mv.sh
t/t7002-grep.sh
t/t7003-filter-branch.sh
t/t7011-skip-worktree-reading.sh [new file with mode: 0755]
t/t7012-skip-worktree-writing.sh [new file with mode: 0755]
t/t7060-wtstatus.sh
t/t7110-reset-merge.sh [new file with mode: 0755]
t/t7111-reset-table.sh [new file with mode: 0755]
t/t7201-co.sh
t/t7300-clean.sh
t/t7400-submodule-basic.sh
t/t7401-submodule-summary.sh
t/t7500-commit.sh
t/t7501-commit.sh
t/t7502-commit.sh
t/t7506-status-submodule.sh
t/t7508-status.sh
t/t7800-difftool.sh
t/t9001-send-email.sh
t/t9151-svn-mergeinfo.sh
t/t9151/make-svnmerge-dump
t/t9151/svn-mergeinfo.dump
t/t9153-git-svn-rewrite-uuid.sh [new file with mode: 0755]
t/t9153/svn.dump [new file with mode: 0644]
t/t9154-git-svn-fancy-glob.sh [new file with mode: 0755]
t/t9154/svn.dump [new file with mode: 0644]
t/t9300-fast-import.sh
t/t9301-fast-export.sh [deleted file]
t/t9301-fast-import-notes.sh [new file with mode: 0755]
t/t9350-fast-export.sh [new file with mode: 0755]
t/t9400-git-cvsserver-server.sh
t/t9401-git-cvsserver-crlf.sh
t/t9501-gitweb-standalone-http-status.sh
t/test-lib.sh
test-index-version.c [new file with mode: 0644]
test-run-command.c [new file with mode: 0644]
transport-helper.c
transport.c
transport.h
tree-diff.c
tree-walk.c
unpack-file.c [deleted file]
unpack-trees.c
unpack-trees.h
userdiff.c
utf8.c
utf8.h
var.c [deleted file]
wrap-for-bin.sh [new file with mode: 0644]
wrapper.c
ws.c
wt-status.c
wt-status.h
xdiff/xdiff.h
xdiff/xmerge.c

index 0636deea9357d2f1e9331119f02fb75fb6b15393..5e98806c6cc246acef5f539ae191710a0c06ad3f 100644 (file)
@@ -1,2 +1,3 @@
 * whitespace=!indent,trail,space
 *.[ch] whitespace=indent,trail,space
+*.sh whitespace=indent,trail,space
index ac02a580daf07150b03194a5a0de522ac1e3415d..8df8f88bea10e02371a41ca20420ee1607814dc8 100644 (file)
@@ -2,6 +2,7 @@
 /GIT-CFLAGS
 /GIT-GUI-VARS
 /GIT-VERSION-FILE
+/bin-wrappers/
 /git
 /git-add
 /git-add--interactive
 /git-relink
 /git-remote
 /git-remote-curl
+/git-remote-http
+/git-remote-https
+/git-remote-ftp
+/git-remote-ftps
 /git-repack
 /git-replace
 /git-repo-config
 /test-delta
 /test-dump-cache-tree
 /test-genrandom
+/test-index-version
 /test-match-trees
 /test-parse-options
 /test-path-utils
+/test-run-command
 /test-sha1
 /test-sigchain
 /common-cmds.h
diff --git a/COPYING b/COPYING
index 6ff87c4664981e4397625791c8ea3bbb5f2279a3..536e55524db72bd2acf175208aef4f3dfc148d42 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -22,8 +22,8 @@
                    GNU GENERAL PUBLIC LICENSE
                       Version 2, June 1991
 
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
                      59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
@@ -36,7 +36,7 @@ software--to make sure the software is free for all its users.  This
 General Public License applies to most of the Free Software
 Foundation's software and to any other program whose authors commit to
 using it.  (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.)  You can apply it to
+the GNU Lesser General Public License instead.)  You can apply it to
 your programs, too.
 
   When we speak of free software, we are referring to freedom, not
@@ -76,7 +76,7 @@ patent must be licensed for everyone's free use or not licensed at all.
 
   The precise terms and conditions for copying, distribution and
 modification follow.
-\f
+
                    GNU GENERAL PUBLIC LICENSE
    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
@@ -131,7 +131,7 @@ above, provided that you also meet all of these conditions:
     License.  (Exception: if the Program itself is interactive but
     does not normally print such an announcement, your work based on
     the Program is not required to print an announcement.)
-\f
+
 These requirements apply to the modified work as a whole.  If
 identifiable sections of that work are not derived from the Program,
 and can be reasonably considered independent and separate works in
@@ -189,7 +189,7 @@ access to copy from a designated place, then offering equivalent
 access to copy the source code from the same place counts as
 distribution of the source code, even though third parties are not
 compelled to copy the source along with the object code.
-\f
+
   4. You may not copy, modify, sublicense, or distribute the Program
 except as expressly provided under this License.  Any attempt
 otherwise to copy, modify, sublicense or distribute the Program is
@@ -246,7 +246,7 @@ impose that choice.
 
 This section is intended to make thoroughly clear what is believed to
 be a consequence of the rest of this License.
-\f
+
   8. If the distribution and/or use of the Program is restricted in
 certain countries either by patents or by copyrighted interfaces, the
 original copyright holder who places the Program under this License
@@ -299,7 +299,7 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGES.
 
                     END OF TERMS AND CONDITIONS
-\f
+
            How to Apply These Terms to Your New Programs
 
   If you develop a new program, and you want it to be of the greatest
@@ -324,10 +324,9 @@ the "copyright" line and a pointer to where the full notice is found.
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
 
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 Also add information on how to contact you by electronic and paper mail.
 
@@ -357,5 +356,5 @@ necessary.  Here is a sample; alter the names:
 This General Public License does not permit incorporating your program into
 proprietary programs.  If your program is a subroutine library, you may
 consider it more useful to permit linking proprietary applications with the
-library.  If this is what you want to do, use the GNU Library General
+library.  If this is what you want to do, use the GNU Lesser General
 Public License instead of this License.
index 4797b2dc3522ccd2050ddefe990268fddb6b8a47..8a8a3954dc45723f7380b59dadbb7e412198d672 100644 (file)
@@ -204,7 +204,7 @@ install-pdf: pdf
 install-html: html
        '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(DESTDIR)$(htmldir)
 
-../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+../GIT-VERSION-FILE: FORCE
        $(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE
 
 -include ../GIT-VERSION-FILE
@@ -337,4 +337,4 @@ quick-install-man:
 quick-install-html:
        '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir)
 
-.PHONY: .FORCE-GIT-VERSION-FILE
+.PHONY: FORCE
diff --git a/Documentation/RelNotes-1.7.0.txt b/Documentation/RelNotes-1.7.0.txt
new file mode 100644 (file)
index 0000000..0897287
--- /dev/null
@@ -0,0 +1,223 @@
+Git v1.7.0 Release Notes
+========================
+
+Notes on behaviour change
+-------------------------
+
+ * "git push" into a branch that is currently checked out (i.e. pointed by
+   HEAD in a repository that is not bare) is refused by default.
+
+   Similarly, "git push $there :$killed" to delete the branch $killed
+   in a remote repository $there, when $killed branch is the current
+   branch pointed at by its HEAD, will be refused by default.
+
+   Setting the configuration variables receive.denyCurrentBranch and
+   receive.denyDeleteCurrent to 'ignore' in the receiving repository
+   can be used to override these safety features.
+
+ * "git send-email" does not make deep threads by default when sending a
+   patch series with more than two messages.  All messages will be sent
+   as a reply to the first message, i.e. cover letter.
+
+   It has been possible to configure send-email to send "shallow thread"
+   by setting sendemail.chainreplyto configuration variable to false.  The
+   only thing this release does is to change the default when you haven't
+   configured that variable.
+
+ * "git status" is not "git commit --dry-run" anymore.  This change does
+   not affect you if you run the command without argument.
+
+ * "git diff" traditionally treated various "ignore whitespace" options
+   only as a way to filter the patch output.  "git diff --exit-code -b"
+   exited with non-zero status even if all changes were about changing the
+   ammount of whitespace and nothing else.  and "git diff -b" showed the
+   "diff --git" header line for such a change without patch text.
+
+   In this release, the "ignore whitespaces" options affect the semantics
+   of the diff operation.  A change that does not affect anything but
+   whitespaces is reported with zero exit status when run with
+   --exit-code, and there is no "diff --git" header for such a change.
+
+ * External diff and textconv helpers are now executed using the shell.
+   This makes them consistent with other programs executed by git, and
+   allows you to pass command-line parameters to the helpers. Any helper
+   paths containing spaces or other metacharacters now need to be
+   shell-quoted.  The affected helpers are GIT_EXTERNAL_DIFF in the
+   environment, and diff.*.command and diff.*.textconv in the config
+   file.
+
+ * The --max-pack-size argument to 'git repack', 'git pack-objects', and
+   'git fast-import' was assuming the provided size to be expressed in MiB,
+   unlike the corresponding config variable and other similar options accepting
+   a size value.  It is now expecting a size expressed in bytes, with a possible
+   unit suffix of 'k', 'm', or 'g'.
+
+Updates since v1.6.6
+--------------------
+
+(subsystems)
+
+ * "git fast-import" updates; adds "option" and "feature" to detect the
+   mismatch between fast-import and the frontends that produce the input
+   stream.
+
+ * "git svn" support of subversion "merge tickets" and miscellaneous fixes.
+
+ * "gitk" updates.
+
+ * "gitweb" updates (code clean-up, load checking etc.)
+
+(portability)
+
+ * Some more MSVC portability patches for msysgit port.
+
+ * Minimum Pthreads emulation for msysgit port.
+
+(performance)
+
+ * More performance improvement patches for msysgit port.
+
+(usability, bells and whistles)
+
+ * More commands learned "--quiet" and "--[no-]progress" options.
+
+ * Various commands given by the end user (e.g. diff.type.textconv,
+   and GIT_EDITOR) can be specified with command line arguments.  E.g. it
+   is now possible to say "[diff "utf8doc"] textconv = nkf -w".
+
+ * "sparse checkout" feature allows only part of the work tree to be
+   checked out.
+
+ * HTTP transfer can use authentication scheme other than basic
+   (i.e./e.g. digest).
+
+ * Switching from a version of superproject that used to have a submodule
+   to another version of superproject that no longer has it did not remove
+   the submodule directory when it should (namely, when you are not
+   interested in the submodule at all and didn't clone/checkout).
+
+ * A new attribute conflict-marker-size can be used to change the size of
+   the conflict markers from the default 7; this is useful when tracked
+   contents (e.g. git-merge documentation) have strings that resemble the
+   conflict markers.
+
+ * A new syntax "<branch>@{upstream}" can be used on the command line to
+   substitute the name of the "upstream" of the branch.  Missing branch
+   defaults to the current branch, so "git fetch && git merge @{upstream}"
+   will be equivalent to "git pull".
+
+ * "git branch --set-upstream" can be used to update the (surprise!) upstream
+   i.e. where the branch is supposed to pull and merge from (or rebase onto).
+
+ * "git checkout A...B" is a way to detach HEAD at the merge base between
+   A and B.
+
+ * "git checkout -m path" to reset the work tree file back into the
+   conflicted state works even when you already ran "git add path" and
+   resolved the conflicts.
+
+ * "git commit --date='<date>'" can be used to override the author date
+   just like "git commit --author='<name> <email>'" can be used to
+   override the author identity.
+
+ * "git commit --no-status" can be used to omit the listing of the index
+   and the work tree status in the editor used to prepare the log message.
+
+ * "git commit" warns a bit more aggressively until you configure user.email,
+   whose default value almost always is not (and fundamentally cannot be)
+   what you want.
+
+ * "git difftool" has been extended to make it easier to integrate it
+   with gitk.
+
+ * "git fetch --all" can now be used in place of "git remote update".
+
+ * "git grep" does not rely on external grep anymore.  It can use more than
+   one threads to accelerate the operation.
+
+ * "git grep" learned "--quiet" option.
+
+ * "git log" and friends learned "--glob=heads/*" syntax that is a more
+   flexible way to complement "--branches/--tags/--remotes".
+
+ * "git merge" learned to pass options specific to strategy-backends.  E.g.
+
+    - "git merge -Xsubtree=path/to/directory" can be used to tell the subtree
+      strategy how much to shift the trees explicitly.
+
+    - "git merge -Xtheirs" can be used to auto-merge as much as possible,
+      while discarding your own changes and taking merged version in
+      conflicted regions.
+
+ * "git push" learned "git push origin --delete branch", a syntactic sugar
+   for "git push origin :branch".
+
+ * "git push" learned "git push --set-upstream origin forker:forkee" that
+   lets you configure your "forker" branch to later pull from "forkee"
+   branch at "origin".
+
+ * "git rebase --onto A...B" means the history is replayed on top of the
+   merge base between A and B.
+
+ * "git rebase -i" learned new action "fixup", that squashes the change
+   but does not affect existing log message.
+
+ * "git rebase -i" also learned --autosquash option, that is useful
+   together with the new "fixup" action.
+
+ * "git remote" learned set-url subcommand, to update (surprise!) url
+   for an existing remote nickname.
+
+ * "git rerere" learned "forget path" subcommand.  Together with "git
+   checkout -m path" it will be useful when you recorded a wrong
+   resolution.
+
+ * Use of "git reset --merge" has become easier when resetting away a
+   conflicted mess left in the work tree.
+
+ * "git rerere" had rerere.autoupdate configuration but there was no way
+   to countermand it from the command line; --no-rerere-autoupdate option
+   given to "merge", "revert", etc. fixes this.
+
+ * "git status" learned "-s(hort)" output format.
+
+(developers)
+
+ * The infrastructure to build foreign SCM interface has been updated.
+
+ * Many more commands are now built-in.
+
+ * THREADED_DELTA_SEARCH is no more.  If you build with threads, delta
+   compression will always take advantage of it.
+
+Fixes since v1.6.6
+------------------
+
+All of the fixes in v1.6.6.X maintenance series are included in this
+release, unless otherwise noted.
+
+ * "git branch -d branch" used to refuse deleting the branch even when
+   the branch is fully merged to its upstream branch if it is not merged
+   to the current branch.  It now deletes it in such a case.
+
+ * "git config -f <relative path>" run from a subdirectory misbehaved.
+   65807ee (builtin-config: Fix crash when using "-f <relative path>"
+   from non-root dir, 2010-01-26) may be merged to older maintenance
+   branches.
+
+ * "git fast-import" did not correctly handle large blobs that may
+   bust the pack size limit.
+
+ * When using "git status" or asking "git diff" to compare the work tree
+   with something, they used to consider that a checked-out submodule with
+   uncommitted changes is not modified; this could cause people to forget
+   committing these changes in the submodule before committing in the
+   superproject. They now consider such a change as a modification and
+   "git diff" will append a "-dirty" to the work tree side when generating
+   patch output or when used with the --submodule option.
+
+--
+exec >/var/tmp/1
+O=v1.7.0-rc1-42-g3bd8de5
+echo O=$(git describe master)
+git shortlog --no-merges $O..master ^maint
index f7728ec40c712723573db342c1143ae632a6929f..4c36aa95b7d3fc0d456d42bbdfdf5a118931094b 100644 (file)
@@ -64,7 +64,7 @@ The values following the equals sign in variable assign are all either
 a string, an integer, or a boolean.  Boolean values may be given as yes/no,
 0/1, true/false or on/off.  Case is not significant in boolean values, when
 converting value to the canonical form using '--bool' type specifier;
-'git-config' will ensure that the output is "true" or "false".
+'git config' will ensure that the output is "true" or "false".
 
 String values may be entirely or partially enclosed in double quotes.
 You need to enclose variable values in double quotes if you want to
@@ -130,6 +130,14 @@ advice.*::
                Advice shown when linkgit:git-merge[1] refuses to
                merge to avoid overwritting local changes.
                Default: true.
+       resolveConflict::
+               Advices shown by various commands when conflicts
+               prevent the operation from being performed.
+               Default: true.
+       implicitIdentity::
+               Advice on how to set your identity configuration when
+               your information is guessed from the system username and
+               domain name. Default: true.
 --
 
 core.fileMode::
@@ -409,6 +417,20 @@ You probably do not need to adjust this value.
 +
 Common unit suffixes of 'k', 'm', or 'g' are supported.
 
+core.bigFileThreshold::
+       Files larger than this size are stored deflated, without
+       attempting delta compression.  Storing large files without
+       delta compression avoids excessive memory usage, at the
+       slight expense of increased disk usage.
++
+Default is 512 MiB on all platforms.  This should be reasonable
+for most projects as source code and other text files can still
+be delta compressed, but larger binary media files won't be.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
++
+Currently only linkgit:git-fast-import[1] honors this setting.
+
 core.excludesfile::
        In addition to '.gitignore' (per-directory) and
        '.git/info/exclude', git looks into this file for patterns
@@ -442,8 +464,8 @@ core.pager::
 
 core.whitespace::
        A comma separated list of common whitespace problems to
-       notice.  'git-diff' will use `color.diff.whitespace` to
-       highlight them, and 'git-apply --whitespace=error' will
+       notice.  'git diff' will use `color.diff.whitespace` to
+       highlight them, and 'git apply --whitespace=error' will
        consider them as errors.  You can prefix `-` to disable
        any of them (e.g. `-trailing-space`):
 +
@@ -502,8 +524,12 @@ notes should be printed.
 This setting defaults to "refs/notes/commits", and can be overridden by
 the `GIT_NOTES_REF` environment variable.
 
+core.sparseCheckout::
+       Enable "sparse checkout" feature. See section "Sparse checkout" in
+       linkgit:git-read-tree[1] for more information.
+
 add.ignore-errors::
-       Tells 'git-add' to continue adding files when some files cannot be
+       Tells 'git add' to continue adding files when some files cannot be
        added due to indexing errors. Equivalent to the '--ignore-errors'
        option of linkgit:git-add[1].
 
@@ -525,19 +551,19 @@ executed from the top-level directory of a repository, which may
 not necessarily be the current directory.
 
 apply.ignorewhitespace::
-       When set to 'change', tells 'git-apply' to ignore changes in
+       When set to 'change', tells 'git apply' to ignore changes in
        whitespace, in the same way as the '--ignore-space-change'
        option.
-       When set to one of: no, none, never, false tells 'git-apply' to
+       When set to one of: no, none, never, false tells 'git apply' to
        respect all whitespace differences.
        See linkgit:git-apply[1].
 
 apply.whitespace::
-       Tells 'git-apply' how to handle whitespaces, in the same way
+       Tells 'git apply' how to handle whitespaces, in the same way
        as the '--whitespace' option. See linkgit:git-apply[1].
 
 branch.autosetupmerge::
-       Tells 'git-branch' and 'git-checkout' to set up new branches
+       Tells 'git branch' and 'git checkout' to set up new branches
        so that linkgit:git-pull[1] will appropriately merge from the
        starting point branch. Note that even if this option is not set,
        this behavior can be chosen per-branch using the `--track`
@@ -548,7 +574,7 @@ branch.autosetupmerge::
        branch. This option defaults to true.
 
 branch.autosetuprebase::
-       When a new branch is created with 'git-branch' or 'git-checkout'
+       When a new branch is created with 'git branch' or 'git checkout'
        that tracks another branch, this variable tells git to set
        up pull to rebase instead of merge (see "branch.<name>.rebase").
        When `never`, rebase is never automatically set to true.
@@ -563,24 +589,24 @@ branch.autosetuprebase::
        This option defaults to never.
 
 branch.<name>.remote::
-       When in branch <name>, it tells 'git-fetch' and 'git-push' which
+       When in branch <name>, it tells 'git fetch' and 'git push' which
        remote to fetch from/push to.  It defaults to `origin` if no remote is
        configured. `origin` is also used if you are not on any branch.
 
 branch.<name>.merge::
        Defines, together with branch.<name>.remote, the upstream branch
-       for the given branch. It tells 'git-fetch'/'git-pull' which
-       branch to merge and can also affect 'git-push' (see push.default).
-       When in branch <name>, it tells 'git-fetch' the default
+       for the given branch. It tells 'git fetch'/'git pull' which
+       branch to merge and can also affect 'git push' (see push.default).
+       When in branch <name>, it tells 'git fetch' the default
        refspec to be marked for merging in FETCH_HEAD. The value is
        handled like the remote part of a refspec, and must match a
        ref which is fetched from the remote given by
        "branch.<name>.remote".
-       The merge information is used by 'git-pull' (which at first calls
-       'git-fetch') to lookup the default branch for merging. Without
-       this option, 'git-pull' defaults to merge the first refspec fetched.
+       The merge information is used by 'git pull' (which at first calls
+       'git fetch') to lookup the default branch for merging. Without
+       this option, 'git pull' defaults to merge the first refspec fetched.
        Specify multiple values to get an octopus merge.
-       If you wish to setup 'git-pull' so that it merges into <name> from
+       If you wish to setup 'git pull' so that it merges into <name> from
        another branch in the local repository, you can point
        branch.<name>.merge to the desired branch, and use the special setting
        `.` (a period) for branch.<name>.remote.
@@ -652,14 +678,6 @@ color.grep::
        `never`), never.  When set to `true` or `auto`, use color only
        when the output is written to the terminal.  Defaults to `false`.
 
-color.grep.external::
-       The string value of this variable is passed to an external 'grep'
-       command as a command line option if match highlighting is turned
-       on.  If set to an empty string, no option is passed at all,
-       turning off coloring for external 'grep' calls; this is the default.
-       For GNU grep, set it to `--color=always` to highlight matches even
-       when a pager is used.
-
 color.grep.match::
        Use customized color for matches.  The value of this variable
        may be specified as in color.branch.<slot>.  It is passed using
@@ -673,7 +691,7 @@ color.interactive::
        colors only when the output is to the terminal. Defaults to false.
 
 color.interactive.<slot>::
-       Use customized color for 'git-add --interactive'
+       Use customized color for 'git add --interactive'
        output. `<slot>` may be `prompt`, `header`, `help` or `error`, for
        four distinct types of normal output from interactive
        commands.  The values of these variables may be specified as
@@ -712,20 +730,25 @@ color.ui::
        terminal. When more specific variables of color.* are set, they always
        take precedence over this setting. Defaults to false.
 
+commit.status::
+       A boolean to enable/disable inclusion of status information in the
+       commit message template when using an editor to prepare the commit
+       message.  Defaults to true.
+
 commit.template::
        Specify a file to use as the template for new commit messages.
        "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
        specified user's home directory.
 
 diff.autorefreshindex::
-       When using 'git-diff' to compare with work tree
+       When using 'git diff' to compare with work tree
        files, do not consider stat-only change as changed.
        Instead, silently run `git update-index --refresh` to
        update the cached stat information for paths whose
        contents in the work tree match the contents in the
        index.  This option defaults to true.  Note that this
-       affects only 'git-diff' Porcelain, and not lower level
-       'diff' commands such as 'git-diff-files'.
+       affects only 'git diff' Porcelain, and not lower level
+       'diff' commands such as 'git diff-files'.
 
 diff.external::
        If this config variable is set, diff generation is not
@@ -737,24 +760,24 @@ diff.external::
        your files, you might want to use linkgit:gitattributes[5] instead.
 
 diff.mnemonicprefix::
-       If set, 'git-diff' uses a prefix pair that is different from the
+       If set, 'git diff' uses a prefix pair that is different from the
        standard "a/" and "b/" depending on what is being compared.  When
        this configuration is in effect, reverse diff output also swaps
        the order of the prefixes:
-'git-diff';;
+`git diff`;;
        compares the (i)ndex and the (w)ork tree;
-'git-diff HEAD';;
+`git diff HEAD`;;
         compares a (c)ommit and the (w)ork tree;
-'git diff --cached';;
+`git diff --cached`;;
        compares a (c)ommit and the (i)ndex;
-'git-diff HEAD:file1 file2';;
+`git diff HEAD:file1 file2`;;
        compares an (o)bject and a (w)ork tree entity;
-'git diff --no-index a b';;
+`git diff --no-index a b`;;
        compares two non-git things (1) and (2).
 
 diff.renameLimit::
        The number of files to consider when performing the copy/rename
-       detection; equivalent to the 'git-diff' option '-l'.
+       detection; equivalent to the 'git diff' option '-l'.
 
 diff.renames::
        Tells git to detect renames.  If set to any boolean value, it
@@ -840,7 +863,7 @@ format.pretty::
        linkgit:git-whatchanged[1].
 
 format.thread::
-       The default threading style for 'git-format-patch'.  Can be
+       The default threading style for 'git format-patch'.  Can be
        a boolean value, or `shallow` or `deep`.  `shallow` threading
        makes every mail a reply to the head of the series,
        where the head is chosen from the cover letter, the
@@ -858,7 +881,7 @@ format.signoff::
 
 gc.aggressiveWindow::
        The window size parameter used in the delta compression
-       algorithm used by 'git-gc --aggressive'.  This defaults
+       algorithm used by 'git gc --aggressive'.  This defaults
        to 10.
 
 gc.auto::
@@ -878,33 +901,33 @@ gc.packrefs::
        Running `git pack-refs` in a repository renders it
        unclonable by Git versions prior to 1.5.1.2 over dumb
        transports such as HTTP.  This variable determines whether
-       'git gc' runs `git pack-refs`. This can be set to "nobare"
+       'git gc' runs `git pack-refs`. This can be set to `nobare`
        to enable it within all non-bare repos or it can be set to a
        boolean value.  The default is `true`.
 
 gc.pruneexpire::
-       When 'git-gc' is run, it will call 'prune --expire 2.weeks.ago'.
+       When 'git gc' is run, it will call 'prune --expire 2.weeks.ago'.
        Override the grace period with this config variable.  The value
        "now" may be used to disable this  grace period and always prune
        unreachable objects immediately.
 
 gc.reflogexpire::
-       'git-reflog expire' removes reflog entries older than
+       'git reflog expire' removes reflog entries older than
        this time; defaults to 90 days.
 
 gc.reflogexpireunreachable::
-       'git-reflog expire' removes reflog entries older than
+       'git reflog expire' removes reflog entries older than
        this time and are not reachable from the current tip;
        defaults to 30 days.
 
 gc.rerereresolved::
        Records of conflicted merge you resolved earlier are
-       kept for this many days when 'git-rerere gc' is run.
+       kept for this many days when 'git rerere gc' is run.
        The default is 60 days.  See linkgit:git-rerere[1].
 
 gc.rerereunresolved::
        Records of conflicted merge you have not resolved are
-       kept for this many days when 'git-rerere gc' is run.
+       kept for this many days when 'git rerere gc' is run.
        The default is 15 days.  See linkgit:git-rerere[1].
 
 gitcvs.commitmsgannotation::
@@ -1012,7 +1035,7 @@ gui.spellingdictionary::
        off.
 
 gui.fastcopyblame::
-       If true, 'git gui blame' uses '-C' instead of '-C -C' for original
+       If true, 'git gui blame' uses `-C` instead of `-C -C` for original
        location detection. It makes blame significantly faster on huge
        repositories at the expense of less thorough copy detection.
 
@@ -1136,6 +1159,12 @@ http.maxRequests::
        How many HTTP requests to launch in parallel. Can be overridden
        by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
 
+http.minSessions::
+       The number of curl sessions (counted across slots) to be kept across
+       requests. They will not be ended with curl_easy_cleanup() until
+       http_cleanup() is invoked. If USE_CURL_MULTI is not defined, this
+       value will be capped at 1. Defaults to 1.
+
 http.postBuffer::
        Maximum size in bytes of the buffer used by smart HTTP
        transports when POSTing data to the remote system.
@@ -1165,7 +1194,7 @@ i18n.commitEncoding::
 
 i18n.logOutputEncoding::
        Character encoding the commit messages are converted to when
-       running 'git-log' and friends.
+       running 'git log' and friends.
 
 imap::
        The configuration variables in the 'imap' section are described
@@ -1199,7 +1228,7 @@ interactive.singlekey::
 
 log.date::
        Set default date-time mode for the log command. Setting log.date
-       value is similar to using 'git-log'\'s --date option. The value is one of the
+       value is similar to using 'git log'\'s --date option. The value is one of the
        following alternatives: {relative,local,default,iso,rfc,short}.
        See linkgit:git-log[1].
 
@@ -1339,10 +1368,13 @@ you can use linkgit:git-index-pack[1] on the *.pack file to regenerate
 the `{asterisk}.idx` file.
 
 pack.packSizeLimit::
-       The default maximum size of a pack.  This setting only affects
-       packing to a file, i.e. the git:// protocol is unaffected.  It
-       can be overridden by the `\--max-pack-size` option of
-       linkgit:git-repack[1].
+       The maximum size of a pack.  This setting only affects
+       packing to a file when repacking, i.e. the git:// protocol
+       is unaffected.  It can be overridden by the `\--max-pack-size`
+       option of linkgit:git-repack[1]. The minimum size allowed is
+       limited to 1 MiB. The default is unlimited.
+       Common unit suffixes of 'k', 'm', or 'g' are
+       supported.
 
 pager.<cmd>::
        Allows turning on or off pagination of the output of a
@@ -1465,6 +1497,10 @@ remote.<name>.tagopt::
        Setting this value to \--no-tags disables automatic tag following when
        fetching from remote <name>
 
+remote.<name>.vcs::
+       Setting this to a value <vcs> will cause git to interact with
+       the remote with the git-remote-<vcs> helper.
+
 remotes.<group>::
        The list of remotes which are fetched by "git remote update
        <group>".  See linkgit:git-remote[1].
diff --git a/Documentation/date-formats.txt b/Documentation/date-formats.txt
new file mode 100644 (file)
index 0000000..c000f08
--- /dev/null
@@ -0,0 +1,26 @@
+DATE FORMATS
+------------
+
+The GIT_AUTHOR_DATE, GIT_COMMITTER_DATE environment variables
+ifdef::git-commit[]
+and the `--date` option
+endif::git-commit[]
+support the following date formats:
+
+Git internal format::
+       It is `<unix timestamp> <timezone offset>`, where `<unix
+       timestamp>` is the number of seconds since the UNIX epoch.
+       `<timezone offset>` is a positive or negative offset from UTC.
+       For example CET (which is 2 hours ahead UTC) is `+0200`.
+
+RFC 2822::
+       The standard email format as described by RFC 2822, for example
+       `Thu, 07 Apr 2005 22:13:13 +0200`.
+
+ISO 8601::
+       Time and date specified by the ISO 8601 standard, for example
+       `2005-04-07T22:13:13`. The parser accepts a space instead of the
+       `T` character as well.
++
+NOTE: In addition, the date part is accepted in the following formats:
+`YYYY.MM.DD`, `MM/DD/YYYY` and `DD.MM.YYYY`.
index b71712473ed13020bca3f133ea4a28c5081b7f9e..15c7e794f4adaccb8885683946a9f4e7e4dc92c2 100644 (file)
@@ -19,7 +19,7 @@ git-diff-tree [-r] <tree-ish-1> <tree-ish-2> [<pattern>...]::
 git-diff-files [<pattern>...]::
         compares the index and the files on the filesystem.
 
-The "git-diff-tree" command begins its ouput by printing the hash of
+The "git-diff-tree" command begins its output by printing the hash of
 what is being compared. After that, all the commands print one output
 line per changed file.
 
index ab6419fe6e44c7008cbff4e1fae48a70cfb82ac0..fe716b2e42642de5c6eefe600b98382069b41247 100644 (file)
@@ -19,7 +19,7 @@ endif::git-pull[]
 
 -f::
 --force::
-       When 'git-fetch' is used with `<rbranch>:<lbranch>`
+       When 'git fetch' is used with `<rbranch>:<lbranch>`
        refspec, it refuses to update the local branch
        `<lbranch>` unless the remote branch `<rbranch>` it
        fetches is a descendant of `<lbranch>`.  This option
@@ -61,16 +61,16 @@ endif::git-pull[]
 
 -u::
 --update-head-ok::
-       By default 'git-fetch' refuses to update the head which
+       By default 'git fetch' refuses to update the head which
        corresponds to the current branch.  This flag disables the
-       check.  This is purely for the internal use for 'git-pull'
-       to communicate with 'git-fetch', and unless you are
+       check.  This is purely for the internal use for 'git pull'
+       to communicate with 'git fetch', and unless you are
        implementing your own Porcelain you are not supposed to
        use it.
 
 --upload-pack <upload-pack>::
        When given, and the repository to fetch from is handled
-       by 'git-fetch-pack', '--exec=<upload-pack>' is passed to
+       by 'git fetch-pack', '--exec=<upload-pack>' is passed to
        the command to specify non-default path for the command
        run on the other end.
 
index 1f1b19996baa34b20bf08f4eb2889403cbc4c45d..f74fcf37378239daded405833737481cdccae8c7 100644 (file)
@@ -39,7 +39,7 @@ The `git add` command will not add ignored files by default.  If any
 ignored files were explicitly specified on the command line, `git add`
 will fail with a list of ignored files.  Ignored files reached by
 directory recursion or filename globbing performed by Git (quote your
-globs before the shell) will be silently ignored.  The `add` command can
+globs before the shell) will be silently ignored.  The 'git add' command can
 be used to add ignored files with the `-f` (force) option.
 
 Please see linkgit:git-commit[1] for alternative ways to add content to a
index 67ad5da9cc0e766882300e90ed114306cb3c9fa7..c3e4f12c44e95c5afa488fe4d69ab29b85b48448 100644 (file)
@@ -37,7 +37,7 @@ OPTIONS
 
 -k::
 --keep::
-       Pass `-k` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]).
+       Pass `-k` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
 -c::
 --scissors::
@@ -53,7 +53,7 @@ OPTIONS
 
 -u::
 --utf8::
-       Pass `-u` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]).
+       Pass `-u` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
        The proposed commit log message taken from the e-mail
        is re-coded into UTF-8 encoding (configuration variable
        `i18n.commitencoding` can be used to specify project's
@@ -63,7 +63,7 @@ This was optional in prior versions of git, but now it is the
 default.   You can use `--no-utf8` to override this.
 
 --no-utf8::
-       Pass `-n` flag to 'git-mailinfo' (see
+       Pass `-n` flag to 'git mailinfo' (see
        linkgit:git-mailinfo[1]).
 
 -3::
@@ -81,7 +81,7 @@ default.   You can use `--no-utf8` to override this.
 -p<n>::
 --directory=<dir>::
 --reject::
-       These flags are passed to the 'git-apply' (see linkgit:git-apply[1])
+       These flags are passed to the 'git apply' (see linkgit:git-apply[1])
        program that applies
        the patch.
 
@@ -121,7 +121,7 @@ default.   You can use `--no-utf8` to override this.
        to the screen before exiting.  This overrides the
        standard message informing you to use `--resolved`
        or `--skip` to handle the failure.  This is solely
-       for internal use between 'git-rebase' and 'git-am'.
+       for internal use between 'git rebase' and 'git am'.
 
 --abort::
        Restore the original branch and abort the patching operation.
index c2528a76545a154eb42a430b59caf26e7aad3bef..8463439ac5047d5f1921db4d6f2d765e7728c73b 100644 (file)
@@ -69,7 +69,7 @@ OPTIONS
        without using the working tree. This implies `--index`.
 
 --build-fake-ancestor=<file>::
-       Newer 'git-diff' output has embedded 'index information'
+       Newer 'git diff' output has embedded 'index information'
        for each blob to help identify the original version that
        the patch applies to.  When this flag is given, and if
        the original versions of the blobs are available locally,
@@ -83,7 +83,7 @@ the information is read from the current index instead.
        Apply the patch in reverse.
 
 --reject::
-       For atomicity, 'git-apply' by default fails the whole patch and
+       For atomicity, 'git apply' by default fails the whole patch and
        does not touch the working tree when some of the hunks
        do not apply.  This option makes it apply
        the parts of the patch that are applicable, and leave the
@@ -109,7 +109,7 @@ any of those replacements occurred.
        ever ignored.
 
 --unidiff-zero::
-       By default, 'git-apply' expects that the patch being
+       By default, 'git apply' expects that the patch being
        applied is a unified diff with at least one line of context.
        This provides good safety measures, but breaks down when
        applying a diff generated with `--unified=0`. To bypass these
@@ -120,7 +120,7 @@ discouraged.
 
 --apply::
        If you use any of the options marked "Turns off
-       'apply'" above, 'git-apply' reads and outputs the
+       'apply'" above, 'git apply' reads and outputs the
        requested information without actually applying the
        patch.  Give this flag after those flags to also apply
        the patch.
@@ -229,7 +229,7 @@ apply.whitespace::
 
 Submodules
 ----------
-If the patch contains any changes to submodules then 'git-apply'
+If the patch contains any changes to submodules then 'git apply'
 treats these changes as follows.
 
 If `--index` is specified (explicitly or implicitly), then the submodule
index c7a6e3ec050b7ceeec79d468b5ffa123314c8f5d..4d4325f222258e60b2242b13a4b4f1db2790bbd5 100644 (file)
@@ -29,17 +29,17 @@ branches that have different roots, it will refuse to run. In that case,
 edit your <archive/branch> parameters to define clearly the scope of the
 import.
 
-'git-archimport' uses `tla` extensively in the background to access the
+'git archimport' uses `tla` extensively in the background to access the
 Arch repository.
 Make sure you have a recent version of `tla` available in the path. `tla` must
-know about the repositories you pass to 'git-archimport'.
+know about the repositories you pass to 'git archimport'.
 
-For the initial import, 'git-archimport' expects to find itself in an empty
+For the initial import, 'git archimport' expects to find itself in an empty
 directory. To follow the development of a project that uses Arch, rerun
-'git-archimport' with the same parameters as the initial import to perform
+'git archimport' with the same parameters as the initial import to perform
 incremental imports.
 
-While 'git-archimport' will try to create sensible branch names for the
+While 'git archimport' will try to create sensible branch names for the
 archives that it imports, it is also possible to specify git branch names
 manually.  To do so, write a git branch name after each <archive/branch>
 parameter, separated by a colon.  This way, you can shorten the Arch
@@ -84,7 +84,7 @@ OPTIONS
 
 -o::
        Use this for compatibility with old-style branch names used by
-       earlier versions of 'git-archimport'.  Old-style branch names
+       earlier versions of 'git archimport'.  Old-style branch names
        were category--branch, whereas new-style branch names are
        archive,category--branch--version.  In both cases, names given
        on the command-line will override the automatically-generated
index e57979198ba880379056189860275724eec7fd3e..799c8b64bd42e3d10942d8ce27c0726a00840f9d 100644 (file)
@@ -21,13 +21,13 @@ structure for the named tree, and writes it out to the standard
 output.  If <prefix> is specified it is
 prepended to the filenames in the archive.
 
-'git-archive' behaves differently when given a tree ID versus when
+'git archive' behaves differently when given a tree ID versus when
 given a commit ID or tag ID.  In the first case the current time is
 used as the modification time of each file in the archive.  In the latter
 case the commit time as recorded in the referenced commit object is
 used instead.  Additionally the commit ID is stored in a global
 extended pax header if the tar format is used; it can be extracted
-using 'git-get-tar-commit-id'. In ZIP files it is stored as a file
+using 'git get-tar-commit-id'. In ZIP files it is stored as a file
 comment.
 
 OPTIONS
index 6b7b2e5497584489c8c3a50b621f0835ad9e34b7..86b3015c134938c03a396b9fbcfcd5cd47e96718 100644 (file)
@@ -799,7 +799,7 @@ fixed in the "main" branch by commit "F"?
 The result of such a bisection would be that we would find that H is
 the first bad commit, when in fact it's B. So that would be wrong!
 
-And yes it's can happen in practice that people working on one branch
+And yes it can happen in practice that people working on one branch
 are not aware that people working on another branch fixed a bug! It
 could also happen that F fixed more than one bug or that it is a
 revert of some big development effort that was not ready to be
index b786471dd8a9faa3a883969cc9745e53dee6831a..a27f43950fdfe8f6a0e8860d79ee31f0bc60750d 100644 (file)
@@ -21,7 +21,7 @@ last modified the line. Optionally, start annotating from the given revision.
 The command can also limit the range of lines annotated.
 
 The report does not tell you anything about lines which have been deleted or
-replaced; you need to use a tool such as 'git-diff' or the "pickaxe"
+replaced; you need to use a tool such as 'git diff' or the "pickaxe"
 interface briefly mentioned in the following paragraph.
 
 Apart from supporting file annotation, git also supports searching the
@@ -49,7 +49,7 @@ include::blame-options.txt[]
        file (see `-M`).  The first number listed is the score.
        This is the number of alphanumeric characters detected
        as having been moved between or within files.  This must be above
-       a certain threshold for 'git-blame' to consider those lines
+       a certain threshold for 'git blame' to consider those lines
        of code to have been moved.
 
 -f::
@@ -100,7 +100,7 @@ header elements later.
 SPECIFYING RANGES
 -----------------
 
-Unlike 'git-blame' and 'git-annotate' in older versions of git, the extent
+Unlike 'git blame' and 'git annotate' in older versions of git, the extent
 of the annotation can be limited to both line ranges and revision
 ranges.  When you are interested in finding the origin for
 lines 40-60 for file `foo`, you can use the `-L` option like so
@@ -118,7 +118,7 @@ which limits the annotation to the body of the `hello` subroutine.
 
 When you are not interested in changes older than version
 v2.6.18, or changes older than 3 weeks, you can use revision
-range specifiers  similar to 'git-rev-list':
+range specifiers  similar to 'git rev-list':
 
        git blame v2.6.18.. -- foo
        git blame --since=3.weeks -- foo
index 0e836809c20c83b8798fc77fa373f17fbf5f1eb6..6b6c3da2d95ad2d5d94949034d5dd723f48d977a 100644 (file)
@@ -11,7 +11,7 @@ SYNOPSIS
 'git branch' [--color | --no-color] [-r | -a]
        [-v [--abbrev=<length> | --no-abbrev]]
        [(--merged | --no-merged | --contains) [<commit>]]
-'git branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
+'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
 'git branch' (-d | -D) [-r] <branchname>...
 
@@ -38,7 +38,7 @@ working tree to it; use "git checkout <newbranch>" to switch to the
 new branch.
 
 When a local branch is started off a remote branch, git sets up the
-branch so that 'git-pull' will appropriately merge from
+branch so that 'git pull' will appropriately merge from
 the remote branch. This behavior may be changed via the global
 `branch.autosetupmerge` configuration flag. That setting can be
 overridden by using the `--track` and `--no-track` options.
@@ -55,7 +55,7 @@ has a reflog then the reflog will also be deleted.
 
 Use -r together with -d to delete remote-tracking branches. Note, that it
 only makes sense to delete remote-tracking branches if they no longer exist
-in the remote repository or if 'git-fetch' was configured not to fetch
+in the remote repository or if 'git fetch' was configured not to fetch
 them again. See also the 'prune' subcommand of linkgit:git-remote[1] for a
 way to clean up all obsolete remote-tracking branches.
 
@@ -76,7 +76,7 @@ OPTIONS
 -f::
 --force::
        Reset <branchname> to <startpoint> if <branchname> exists
-       already. Without `-f` 'git-branch' refuses to change an existing branch.
+       already. Without `-f` 'git branch' refuses to change an existing branch.
 
 -m::
        Move/rename a branch and the corresponding reflog.
@@ -129,6 +129,12 @@ start-point is either a local or remote branch.
        Do not set up "upstream" configuration, even if the
        branch.autosetupmerge configuration variable is true.
 
+--set-upstream::
+       If specified branch does not exist yet or if '--force' has been
+       given, acts exactly like '--track'. Otherwise sets up configuration
+       like '--track' would when creating the branch, except that where
+       branch points to is not changed.
+
 --contains <commit>::
        Only list branches which contain the specified commit.
 
index c3a066e60cb40d893287f813a4a007c4db42b1bb..a5ed8fb05b2f491f8f8ed7b95b808d04240327f6 100644 (file)
@@ -21,9 +21,9 @@ Some workflows require that one or more branches of development on one
 machine be replicated on another machine, but the two machines cannot
 be directly connected, and therefore the interactive git protocols (git,
 ssh, rsync, http) cannot be used.  This command provides support for
-'git-fetch' and 'git-pull' to operate by packaging objects and references
+'git fetch' and 'git pull' to operate by packaging objects and references
 in an archive at the originating machine, then importing those into
-another repository using 'git-fetch' and 'git-pull'
+another repository using 'git fetch' and 'git pull'
 after moving the archive by some means (e.g., by sneakernet).  As no
 direct connection between the repositories exists, the user must specify a
 basis for the bundle that is held by the destination repository: the
@@ -35,14 +35,14 @@ OPTIONS
 
 create <file>::
        Used to create a bundle named 'file'.  This requires the
-       'git-rev-list' arguments to define the bundle contents.
+       'git rev-list' arguments to define the bundle contents.
 
 verify <file>::
        Used to check that a bundle file is valid and will apply
        cleanly to the current repository.  This includes checks on the
        bundle format itself as well as checking that the prerequisite
        commits exist and are fully linked in the current repository.
-       'git-bundle' prints a list of missing commits, if any, and exits
+       'git bundle' prints a list of missing commits, if any, and exits
        with a non-zero status.
 
 list-heads <file>::
@@ -51,15 +51,15 @@ list-heads <file>::
        printed out.
 
 unbundle <file>::
-       Passes the objects in the bundle to 'git-index-pack'
+       Passes the objects in the bundle to 'git index-pack'
        for storage in the repository, then prints the names of all
        defined references. If a list of references is given, only
        references matching those in the list are printed. This command is
-       really plumbing, intended to be called only by 'git-fetch'.
+       really plumbing, intended to be called only by 'git fetch'.
 
 [git-rev-list-args...]::
-       A list of arguments, acceptable to 'git-rev-parse' and
-       'git-rev-list', that specifies the specific objects and references
+       A list of arguments, acceptable to 'git rev-parse' and
+       'git rev-list', that specifies the specific objects and references
        to transport.  For example, `master\~10..master` causes the
        current master reference to be packaged along with all objects
        added since its 10th ancestor commit.  There is no explicit
@@ -69,16 +69,16 @@ unbundle <file>::
 
 [refname...]::
        A list of references used to limit the references reported as
-       available. This is principally of use to 'git-fetch', which
+       available. This is principally of use to 'git fetch', which
        expects to receive only those references asked for and not
-       necessarily everything in the pack (in this case, 'git-bundle' acts
-       like 'git-fetch-pack').
+       necessarily everything in the pack (in this case, 'git bundle' acts
+       like 'git fetch-pack').
 
 SPECIFYING REFERENCES
 ---------------------
 
-'git-bundle' will only package references that are shown by
-'git-show-ref': this includes heads, tags, and remote heads.  References
+'git bundle' will only package references that are shown by
+'git show-ref': this includes heads, tags, and remote heads.  References
 such as `master\~1` cannot be packaged, but are perfectly suitable for
 defining the basis.  More than one reference may be packaged, and more
 than one basis can be specified.  The objects packaged are those not
index 0aeef24780f07e5d5fa021f6ebdaa7090a3dcdb7..d9a3326f58c0324b6e32fb130325213a413d35ab 100644 (file)
@@ -60,7 +60,7 @@ reference name expressions (see linkgit:git-rev-parse[1]):
 . A colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
   value and store it in dstref" in fetch and push operations.
   It may also be used to select a specific object such as with
-  'git-cat-file': "git cat-file blob v1.3.3:refs.c".
+  'git cat-file': "git cat-file blob v1.3.3:refs.c".
 
 . at-open-brace `@{` is used as a notation to access a reflog entry.
 
index 62d84836b8a0d77c2a6ea534566ff8462b0eb37a..d6aa6e14eb32967a09c5987fad26ece2749a65b8 100644 (file)
@@ -88,7 +88,7 @@ $ find . -name '*.h' -print0 | xargs -0 git checkout-index -f --
 which will force all existing `*.h` files to be replaced with their
 cached copies. If an empty command line implied "all", then this would
 force-refresh everything in the index, which was not the point.  But
-since 'git-checkout-index' accepts --stdin it would be faster to use:
+since 'git checkout-index' accepts --stdin it would be faster to use:
 
 ----------------
 $ find . -name '*.h' -print0 | git checkout-index -f -z --stdin
@@ -102,7 +102,7 @@ Using `--` is probably a good policy in scripts.
 Using --temp or --stage=all
 ---------------------------
 When `--temp` is used (or implied by `--stage=all`)
-'git-checkout-index' will create a temporary file for each index
+'git checkout-index' will create a temporary file for each index
 entry being checked out.  The index will not be updated with stat
 information.  These options can be useful if the caller needs all
 stages of all unmerged entries so that the unmerged files can be
@@ -147,9 +147,9 @@ To update and refresh only the files already checked out::
 $ git checkout-index -n -f -a && git update-index --ignore-missing --refresh
 ----------------
 
-Using 'git-checkout-index' to "export an entire tree"::
+Using 'git checkout-index' to "export an entire tree"::
        The prefix ability basically makes it trivial to use
-       'git-checkout-index' as an "export as tree" function.
+       'git checkout-index' as an "export as tree" function.
        Just read the desired tree into the index, and do:
 +
 ----------------
index b764130d26eb750d2b5173dcc98366828e413046..78f4714da0c226b9523f60f247f60ab13119c7c7 100644 (file)
@@ -24,7 +24,7 @@ OPTIONS
 
 -e::
 --edit::
-       With this option, 'git-cherry-pick' will let you edit the commit
+       With this option, 'git cherry-pick' will let you edit the commit
        message prior to committing.
 
 -x::
index 7deefdae8f995d843971f6beff24af8325b99f1f..fed115acd01a63801a647ae870357d4c2d59ea44 100644 (file)
@@ -14,7 +14,7 @@ DESCRIPTION
 The changeset (or "diff") of each commit between the fork-point and <head>
 is compared against each commit between the fork-point and <upstream>.
 The commits are compared with their 'patch id', obtained from
-the 'git-patch-id' program.
+the 'git patch-id' program.
 
 Every commit that doesn't exist in the <upstream> branch
 has its id (sha1) reported, prefixed by a symbol.  The ones that have
@@ -37,8 +37,8 @@ to and including <limit> are not reported:
               \__*__*__<limit>__-__+__> <head>
 
 
-Because 'git-cherry' compares the changeset rather than the commit id
-(sha1), you can use 'git-cherry' to find out if a commit you made locally
+Because 'git cherry' compares the changeset rather than the commit id
+(sha1), you can use 'git cherry' to find out if a commit you made locally
 has been applied <upstream> under a different commit id.  For example,
 this will happen if you're feeding patches <upstream> via email rather
 than pushing or pulling commits directly.
index 670cb02b6cc035e4fbcf1a1016f66b7a85cd4ef7..fb2753c97eb186c63ba16f9b7714c2e3a5b82a64 100644 (file)
@@ -14,9 +14,9 @@ DESCRIPTION
 A Tcl/Tk based graphical interface to review modified files, stage
 them into the index, enter a commit message and record the new
 commit onto the current branch.  This interface is an alternative
-to the less interactive 'git-commit' program.
+to the less interactive 'git commit' program.
 
-'git-citool' is actually a standard alias for `git gui citool`.
+'git citool' is actually a standard alias for `git gui citool`.
 See linkgit:git-gui[1] for more details.
 
 Author
index 9d291bdd266b49fe01b017dfce8e73dd288ad785..a81cb6c28050e83778f11f2957d3489f50fa6004 100644 (file)
@@ -33,8 +33,8 @@ OPTIONS
 
 -f::
 --force::
-       If the git configuration specifies clean.requireForce as true,
-       'git-clean' will refuse to run unless given -f or -n.
+       If the git configuration variable clean.requireForce is not set
+       to false, 'git clean' will refuse to run unless given -f or -n.
 
 -n::
 --dry-run::
@@ -48,7 +48,7 @@ OPTIONS
 -x::
        Don't use the ignore rules.  This allows removing all untracked
        files, including build products.  This can be used (possibly in
-       conjunction with 'git-reset') to create a pristine
+       conjunction with 'git reset') to create a pristine
        working directory to test a clean build.
 
 -X::
index 7ccd742a87db1541184868b794fa4ef597d04de8..f43c8b2c08ab4ca45f759369dc3d8e85c9f76415 100644 (file)
@@ -96,13 +96,19 @@ objects from the source repository into a pack in the cloned repository.
 
 --quiet::
 -q::
-       Operate quietly.  This flag is also passed to the `rsync'
+       Operate quietly.  Progress is not reported to the standard
+       error stream. This flag is also passed to the `rsync'
        command when given.
 
 --verbose::
 -v::
-       Display the progress bar, even in case the standard output is not
-       a terminal.
+       Run verbosely.
+
+--progress::
+       Progress status is reported on the standard error stream
+       by default when it is attached to a terminal, unless -q
+       is specified. This flag forces progress status even if the
+       standard error stream is not directed to a terminal.
 
 --no-checkout::
 -n::
index b8834baced4b25d6df47eb578c4d745d23e8cc73..61888547a16e904a766734c6883d95ee46dfc61e 100644 (file)
@@ -70,9 +70,10 @@ is taken from the configuration items user.name and user.email, or, if not
 present, system user name and fully qualified hostname.
 
 A commit comment is read from stdin. If a changelog
-entry is not provided via "<" redirection, 'git-commit-tree' will just wait
+entry is not provided via "<" redirection, 'git commit-tree' will just wait
 for one to be entered and terminated with ^D.
 
+include::date-formats.txt[]
 
 Diagnostics
 -----------
index d227cec9ba566caa098c36e8c91b19a8a27fcae5..e99bb14754c29a7aee8d16ba27d69a403707b85d 100644 (file)
@@ -11,7 +11,8 @@ SYNOPSIS
 'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run]
           [(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author]
           [--allow-empty] [--no-verify] [-e] [--author=<author>]
-          [--cleanup=<mode>] [--] [[-i | -o ]<file>...]
+          [--date=<date>] [--cleanup=<mode>] [--status | --no-status] [--]
+          [[-i | -o ]<file>...]
 
 DESCRIPTION
 -----------
@@ -20,11 +21,11 @@ with a log message from the user describing the changes.
 
 The content to be added can be specified in several ways:
 
-1. by using 'git-add' to incrementally "add" changes to the
+1. by using 'git add' to incrementally "add" changes to the
    index before using the 'commit' command (Note: even modified
    files must be "added");
 
-2. by using 'git-rm' to remove files from the working tree
+2. by using 'git rm' to remove files from the working tree
    and the index, again before using the 'commit' command;
 
 3. by listing files as arguments to the 'commit' command, in which
@@ -40,14 +41,14 @@ The content to be added can be specified in several ways:
 
 5. by using the --interactive switch with the 'commit' command to decide one
    by one which files should be part of the commit, before finalizing the
-   operation.  Currently, this is done by invoking 'git-add --interactive'.
+   operation.  Currently, this is done by invoking 'git add --interactive'.
 
 The `--dry-run` option can be used to obtain a
 summary of what is included by any of the above for the next
 commit by giving the same set of parameters (options and paths).
 
 If you make a commit and then find a mistake immediately after
-that, you can recover from it with 'git-reset'.
+that, you can recover from it with 'git reset'.
 
 
 OPTIONS
@@ -74,6 +75,20 @@ OPTIONS
        authorship of the resulting commit now belongs of the committer.
        This also renews the author timestamp.
 
+--short::
+       When doing a dry-run, give the output in the short-format. See
+       linkgit:git-status[1] for details. Implies `--dry-run`.
+
+--porcelain::
+       When doing a dry-run, give the output in a porcelain-ready
+       format. See linkgit:git-status[1] for details. Implies
+       `--dry-run`.
+
+-z::
+       When showing `short` or `porcelain` status output, terminate
+       entries in the status output with NUL, instead of LF. If no
+       format is given, implies the `--porcelain` output format.
+
 -F <file>::
 --file=<file>::
        Take the commit message from the given file.  Use '-' to
@@ -85,6 +100,9 @@ OPTIONS
        an existing commit that matches the given string and its author
        name is used.
 
+--date=<date>::
+       Override the author date used in the commit.
+
 -m <msg>::
 --message=<msg>::
        Use the given <msg> as the commit message.
@@ -167,7 +185,7 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].)
        Make a commit only from the paths specified on the
        command line, disregarding any contents that have been
        staged so far. This is the default mode of operation of
-       'git-commit' if any paths are given on the command line,
+       'git commit' if any paths are given on the command line,
        in which case this option can be omitted.
        If this option is specified together with '--amend', then
        no paths need to be specified, which can be used to amend
@@ -207,6 +225,17 @@ specified.
        to be committed, paths with local changes that will be left
        uncommitted and paths that are untracked.
 
+--status::
+       Include the output of linkgit:git-status[1] in the commit
+       message template when using an editor to prepare the commit
+       message.  Defaults to on, but can be used to override
+       configuration variable commit.status.
+
+--no-status::
+       Do not include the output of linkgit:git-status[1] in the
+       commit message template when using an editor to prepare the
+       default commit message.
+
 \--::
        Do not interpret any more arguments as options.
 
@@ -217,15 +246,17 @@ specified.
        these files are also staged for the next commit on top
        of what have been staged before.
 
+:git-commit: 1
+include::date-formats.txt[]
 
 EXAMPLES
 --------
 When recording your own work, the contents of modified files in
 your working tree are temporarily stored to a staging area
-called the "index" with 'git-add'.  A file can be
+called the "index" with 'git add'.  A file can be
 reverted back, only in the index but not in the working tree,
 to that of the last commit with `git reset HEAD -- <file>`,
-which effectively reverts 'git-add' and prevents the changes to
+which effectively reverts 'git add' and prevents the changes to
 this file from participating in the next commit.  After building
 the state to be committed incrementally with these commands,
 `git commit` (without any pathname parameter) is used to record what
@@ -281,13 +312,13 @@ $ git commit
 this second commit would record the changes to `hello.c` and
 `hello.h` as expected.
 
-After a merge (initiated by 'git-merge' or 'git-pull') stops
+After a merge (initiated by 'git merge' or 'git pull') stops
 because of conflicts, cleanly merged
 paths are already staged to be committed for you, and paths that
 conflicted are left in unmerged state.  You would have to first
-check which paths are conflicting with 'git-status'
+check which paths are conflicting with 'git status'
 and after fixing them manually in your working tree, you would
-stage the result as usual with 'git-add':
+stage the result as usual with 'git add':
 
 ------------
 $ git status | grep unmerged
index f68b198205d3a4d808ce58e6b0ac03e70bb160a4..543dd64a468a8e96daf7c97d9c52264ce426db65 100644 (file)
@@ -37,11 +37,12 @@ existing values that match the regexp are updated or unset.  If
 you want to handle the lines that do *not* match the regex, just
 prepend a single exclamation mark in front (see also <<EXAMPLES>>).
 
-The type specifier can be either '--int' or '--bool', which will make
-'git-config' ensure that the variable(s) are of the given type and
+The type specifier can be either '--int' or '--bool', to make
+'git config' ensure that the variable(s) are of the given type and
 convert the value to the canonical form (simple decimal number for int,
-a "true" or "false" string for bool).  If no type specifier is passed,
-no checks or transformations are performed on the value.
+a "true" or "false" string for bool), or '--path', which does some
+path expansion (see '--path' below).  If no type specifier is passed, no
+checks or transformations are performed on the value.
 
 The file-option can be one of '--system', '--global' or '--file'
 which specify where the values will be read from or written to.
@@ -124,18 +125,25 @@ See also <<FILES>>.
        List all variables set in config file.
 
 --bool::
-       'git-config' will ensure that the output is "true" or "false"
+       'git config' will ensure that the output is "true" or "false"
 
 --int::
-       'git-config' will ensure that the output is a simple
+       'git config' will ensure that the output is a simple
        decimal number.  An optional value suffix of 'k', 'm', or 'g'
        in the config file will cause the value to be multiplied
        by 1024, 1048576, or 1073741824 prior to output.
 
 --bool-or-int::
-       'git-config' will ensure that the output matches the format of
+       'git config' will ensure that the output matches the format of
        either --bool or --int, as described above.
 
+--path::
+       'git-config' will expand leading '{tilde}' to the value of
+       '$HOME', and '{tilde}user' to the home directory for the
+       specified user.  This option has no effect when setting the
+       value (but you can use 'git config bla {tilde}/' from the
+       command line to let your shell do the expansion).
+
 -z::
 --null::
        For all options that output values and/or keys, always
@@ -173,7 +181,7 @@ FILES
 -----
 
 If not set explicitly with '--file', there are three files where
-'git-config' will search for configuration options:
+'git config' will search for configuration options:
 
 $GIT_DIR/config::
        Repository specific configuration file. (The filename is
@@ -190,12 +198,12 @@ $(prefix)/etc/gitconfig::
 If no further options are given, all reading options will read all of these
 files that are available. If the global or the system-wide configuration
 file are not available they will be ignored. If the repository configuration
-file is not available or readable, 'git-config' will exit with a non-zero
+file is not available or readable, 'git config' will exit with a non-zero
 error code. However, in neither case will an error message be issued.
 
 All writing options will per default write to the repository specific
 configuration file. Note that this also affects options like '--replace-all'
-and '--unset'. *'git-config' will only ever change one file at a time*.
+and '--unset'. *'git config' will only ever change one file at a time*.
 
 You can override these rules either by command line options or by environment
 variables. The '--global' and the '--system' options will limit the file used
index abaaf273bb3f39fef065781d89592fd7203ea620..b2696efae95b20b693251a9d9785405218285084 100644 (file)
@@ -27,7 +27,7 @@ by default.
 
 Supports file additions, removals, and commits that affect binary files.
 
-If the commit is a merge commit, you must tell 'git-cvsexportcommit' what
+If the commit is a merge commit, you must tell 'git cvsexportcommit' what
 parent the changeset should be done against.
 
 OPTIONS
index 614e769f4e5351d2820ebb9bf0dd1ae519c965ba..ddfcb3d143239174d600c8fde9dbb58b772fff4b 100644 (file)
@@ -28,9 +28,9 @@ At least version 2.1 is required.
 Please see the section <<issues,ISSUES>> for further reference.
 
 You should *never* do any work of your own on the branches that are
-created by 'git-cvsimport'.  By default initial import will create and populate a
+created by 'git cvsimport'.  By default initial import will create and populate a
 "master" branch from the CVS repository's main branch which you're free
-to work with; after that, you need to 'git-merge' incremental imports, or
+to work with; after that, you need to 'git merge' incremental imports, or
 any CVS branches, yourself.  It is advisable to specify a named remote via
 -r to separate and protect the incoming branches.
 
@@ -49,13 +49,13 @@ OPTIONS
 -d <CVSROOT>::
        The root of the CVS archive. May be local (a simple path) or remote;
        currently, only the :local:, :ext: and :pserver: access methods
-       are supported. If not given, 'git-cvsimport' will try to read it
+       are supported. If not given, 'git cvsimport' will try to read it
        from `CVS/Root`. If no such file exists, it checks for the
        `CVSROOT` environment variable.
 
 <CVS_module>::
        The CVS module you want to import. Relative to <CVSROOT>.
-       If not given, 'git-cvsimport' tries to read it from
+       If not given, 'git cvsimport' tries to read it from
        `CVS/Repository`.
 
 -C <target-dir>::
@@ -65,14 +65,14 @@ OPTIONS
 -r <remote>::
        The git remote to import this CVS repository into.
        Moves all CVS branches into remotes/<remote>/<branch>
-       akin to the way 'git-clone' uses 'origin' by default.
+       akin to the way 'git clone' uses 'origin' by default.
 
 -o <branch-for-HEAD>::
        When no remote is specified (via -r) the 'HEAD' branch
        from CVS is imported to the 'origin' branch within the git
        repository, as 'HEAD' already has a special meaning for git.
        When a remote is specified the 'HEAD' branch is named
-       remotes/<remote>/master mirroring 'git-clone' behaviour.
+       remotes/<remote>/master mirroring 'git clone' behaviour.
        Use this option if you want to import into a different
        branch.
 +
@@ -145,17 +145,17 @@ This option can be used several times to provide several detection regexes.
 
 ---------
 +
-'git-cvsimport' will make it appear as those authors had
+'git cvsimport' will make it appear as those authors had
 their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly
 all along.
 +
 For convenience, this data is saved to `$GIT_DIR/cvs-authors`
 each time the '-A' option is provided and read from that same
-file each time 'git-cvsimport' is run.
+file each time 'git cvsimport' is run.
 +
 It is not recommended to use this feature if you intend to
 export changes back to CVS again later with
-'git-cvsexportcommit'.
+'git cvsexportcommit'.
 
 -h::
        Print a short usage message and exit.
index 99a7c14700ffa06090534682283320fef5001815..dbb053ee17fbc9e254aac42bf87d204655e00614 100644 (file)
@@ -22,7 +22,7 @@ cvspserver stream tcp nowait nobody /usr/bin/git-cvsserver git-cvsserver pserver
 Usage:
 
 [verse]
-'git cvsserver' [options] [pserver|server] [<directory> ...]
+'git-cvsserver' [options] [pserver|server] [<directory> ...]
 
 OPTIONS
 -------
@@ -206,7 +206,7 @@ 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
+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
@@ -277,6 +277,21 @@ In `dbdriver` and `dbuser` you can use the following variables:
        If no name can be determined, the
        numeric uid is used.
 
+ENVIRONMENT
+-----------
+
+These variables obviate the need for command-line options in some
+circumstances, allowing easier restricted usage through git-shell.
+
+GIT_CVSSERVER_BASE_PATH takes the place of the argument to --base-path.
+
+GIT_CVSSERVER_ROOT specifies a single-directory whitelist. The
+repository must still be configured to allow access through
+git-cvsserver, as described above.
+
+When these environment variables are set, the corresponding
+command-line arguments may not be used.
+
 Eclipse CVS Client Notes
 ------------------------
 
@@ -294,7 +309,7 @@ To get a checkout with the Eclipse CVS client:
 Protocol notes: If you are using anonymous access via pserver, just select that.
 Those using SSH access should choose the 'ext' protocol, and configure 'ext'
 access on the Preferences->Team->CVS->ExtConnection pane. Set CVS_SERVER to
-"'git cvsserver'". Note that password support is not good when using 'ext',
+"`git cvsserver`". Note that password support is not good when using 'ext',
 you will definitely want to have SSH keys setup.
 
 Alternatively, you can just use the non-standard extssh protocol that Eclipse
index a85121c689e394e86f7c97025b92ffa03fca0df9..01c9f8eb9eff634fe8ab99908c62207ebecd5e31 100644 (file)
@@ -28,36 +28,36 @@ that service if it is enabled.
 It verifies that the directory has the magic file "git-daemon-export-ok", and
 it will refuse to export any git directory that hasn't explicitly been marked
 for export this way (unless the '--export-all' parameter is specified). If you
-pass some directory paths as 'git-daemon' arguments, you can further restrict
+pass some directory paths as 'git daemon' arguments, you can further restrict
 the offers to a whitelist comprising of those.
 
 By default, only `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'.
+'git fetch-pack' and 'git ls-remote' clients, which are invoked
+from 'git fetch', 'git pull', and 'git clone'.
 
 This is ideally suited for read-only updates, i.e., pulling from
 git repositories.
 
-An `upload-archive` also exists to serve 'git-archive'.
+An `upload-archive` also exists to serve 'git archive'.
 
 OPTIONS
 -------
 --strict-paths::
        Match paths exactly (i.e. don't allow "/foo/repo" when the real path is
        "/foo/repo.git" or "/foo/repo/.git") and don't do user-relative paths.
-       'git-daemon' will refuse to start when this option is enabled and no
+       'git daemon' will refuse to start when this option is enabled and no
        whitelist is specified.
 
 --base-path=path::
        Remap all the path requests as relative to the given path.
-       This is sort of "GIT root" - if you run 'git-daemon' with
+       This is sort of "GIT root" - if you run 'git daemon' with
        '--base-path=/srv/git' on example.com, then if you later try to pull
-       'git://example.com/hello.git', 'git-daemon' will interpret the path
+       'git://example.com/hello.git', 'git daemon' will interpret the path
        as '/srv/git/hello.git'.
 
 --base-path-relaxed::
        If --base-path is enabled and repo lookup fails, with this option
-       'git-daemon' will attempt to lookup without prefixing the base path.
+       'git daemon' will attempt to lookup without prefixing the base path.
        This is useful for switching to --base-path usage, while still
        allowing the old paths.
 
@@ -143,7 +143,7 @@ OPTIONS
 +
 Giving these options is an error when used with `--inetd`; use
 the facility of inet daemon to achieve the same before spawning
-'git-daemon' if needed.
+'git daemon' if needed.
 
 --enable=service::
 --disable=service::
@@ -169,24 +169,24 @@ SERVICES
 
 These services can be globally enabled/disabled using the
 command line options of this command.  If a finer-grained
-control is desired (e.g. to allow 'git-archive' to be run
+control is desired (e.g. to allow 'git archive' to be run
 against only in a few selected repositories the daemon serves),
 the per-repository configuration file can be used to enable or
 disable them.
 
 upload-pack::
-       This serves 'git-fetch-pack' and 'git-ls-remote'
+       This serves 'git fetch-pack' and 'git ls-remote'
        clients.  It is enabled by default, but a repository can
        disable it by setting `daemon.uploadpack` configuration
        item to `false`.
 
 upload-archive::
-       This serves 'git-archive --remote'.  It is disabled by
+       This serves 'git archive --remote'.  It is disabled by
        default, but a repository can enable it by setting
        `daemon.uploadarch` configuration item to `true`.
 
 receive-pack::
-       This serves 'git-send-pack' clients, allowing anonymous
+       This serves 'git send-pack' clients, allowing anonymous
        push.  It is disabled by default, as there is _no_
        authentication in the protocol (in other words, anybody
        can push anything into the repository, including removal
@@ -204,8 +204,8 @@ $ grep 9418 /etc/services
 git            9418/tcp                # Git Version Control System
 ------------
 
-'git-daemon' as inetd server::
-       To set up 'git-daemon' as an inetd service that handles any
+'git daemon' as inetd server::
+       To set up 'git daemon' as an inetd service that handles any
        repository under the whitelisted set of directories, /pub/foo
        and /pub/bar, place an entry like the following into
        /etc/inetd all on one line:
@@ -217,8 +217,8 @@ git         9418/tcp                # Git Version Control System
 ------------------------------------------------
 
 
-'git-daemon' as inetd server for virtual hosts::
-       To set up 'git-daemon' as an inetd service that handles
+'git daemon' as inetd server for virtual hosts::
+       To set up 'git daemon' as an inetd service that handles
        repositories for different virtual hosts, `www.example.com`
        and `www.example.org`, place an entry like the following into
        `/etc/inetd` all on one line:
@@ -240,8 +240,8 @@ clients, a symlink from `/software` into the appropriate
 default repository could be made as well.
 
 
-'git-daemon' as regular daemon for virtual hosts::
-       To set up 'git-daemon' as a regular, non-inetd service that
+'git daemon' as regular daemon for virtual hosts::
+       To set up 'git daemon' as a regular, non-inetd service that
        handles repositories for multiple virtual hosts based on
        their IP addresses, start the daemon like this:
 +
@@ -258,7 +258,7 @@ Repositories can still be accessed by hostname though, assuming
 they correspond to these IP addresses.
 
 selectively enable/disable services per repository::
-       To enable 'git-archive --remote' and disable 'git-fetch' against
+       To enable 'git archive --remote' and disable 'git fetch' against
        a repository, have the following in the configuration file in the
        repository (that is the file 'config' next to 'HEAD', 'refs' and
        'objects').
@@ -272,7 +272,7 @@ selectively enable/disable services per repository::
 
 ENVIRONMENT
 -----------
-'git-daemon' will set REMOTE_ADDR to the IP address of the client
+'git daemon' will set REMOTE_ADDR to the IP address of the client
 that connected to it, if the IP address is available. REMOTE_ADDR will
 be available in the environment of hooks called when
 services are performed.
index 78b9808aa3a2f5fe2435ea188ac4e73508c2720a..6fc5323ee6a9d3cd22f8c68ebaf514f26c82f684 100644 (file)
@@ -106,7 +106,7 @@ of commits which would be displayed by "git log v1.0.4..parent".
 The hash suffix is "-g" + 7-char abbreviation for the tip commit
 of parent (which was `2414721b194453f058079d897d13c4e377f92dc6`).
 
-Doing a 'git-describe' on a tag-name will just show the tag name:
+Doing a 'git describe' on a tag-name will just show the tag name:
 
        [torvalds@g5 git]$ git describe v1.0.4
        v1.0.4
@@ -136,13 +136,13 @@ be sufficient to disambiguate these commits.
 SEARCH STRATEGY
 ---------------
 
-For each committish supplied, 'git-describe' will first look for
+For each committish supplied, 'git describe' will first look for
 a tag which tags exactly that commit.  Annotated tags will always
 be preferred over lightweight tags, and tags with newer dates will
 always be preferred over tags with older dates.  If an exact match
 is found, its name will be output and searching will stop.
 
-If an exact match was not found, 'git-describe' will walk back
+If an exact match was not found, 'git describe' will walk back
 through the commit history to locate an ancestor commit which
 has been tagged.  The ancestor's tag will be output along with an
 abbreviation of the input committish's SHA1.
index 4ef03578ebc58f3b678e8805644ae8c0ad4b998b..9cd8ccef37f131d7913926ef9b5b7b49fe6d3463 100644 (file)
@@ -15,7 +15,7 @@ DESCRIPTION
 Compares the files in the working tree and the index.  When paths
 are specified, compares only those named paths.  Otherwise all
 entries in the index are compared.  The output format is the
-same as for 'git-diff-index' and 'git-diff-tree'.
+same as for 'git diff-index' and 'git diff-tree'.
 
 OPTIONS
 -------
index 8b9ed2929980ec9928930496375df5e9d72e4b51..162cb741b3052560351d2dd3f53c56ece5e51e95 100644 (file)
@@ -31,7 +31,7 @@ include::diff-options.txt[]
 -m::
        By default, files recorded in the index but not checked
        out are reported as deleted.  This flag makes
-       'git-diff-index' say that all non-checked-out files are up
+       'git diff-index' say that all non-checked-out files are up
        to date.
 
 include::diff-format.txt[]
@@ -48,7 +48,7 @@ Cached Mode
 If '--cached' is specified, it allows you to ask:
 
        show me the differences between HEAD and the current index
-       contents (the ones I'd write using 'git-write-tree')
+       contents (the ones I'd write using 'git write-tree')
 
 For example, let's say that you have worked on your working directory, updated
 some files in the index and are ready to commit. You want to see exactly
@@ -60,7 +60,7 @@ object and compare it that way, and to do that, you just do
 Example: let's say I had renamed `commit.c` to `git-commit.c`, and I had
 done an `update-index` to make that effective in the index file.
 `git diff-files` wouldn't show anything at all, since the index file
-matches my working directory. But doing a 'git-diff-index' does:
+matches my working directory. But doing a 'git diff-index' does:
 
   torvalds@ppc970:~/git> git diff-index --cached HEAD
   -100644 blob    4161aecc6700a2eb579e842af0b7f22b98443f74        commit.c
@@ -69,10 +69,10 @@ matches my working directory. But doing a 'git-diff-index' does:
 You can see easily that the above is a rename.
 
 In fact, `git diff-index --cached` *should* always be entirely equivalent to
-actually doing a 'git-write-tree' and comparing that. Except this one is much
+actually doing a 'git write-tree' and comparing that. Except this one is much
 nicer for the case where you just want to check where you are.
 
-So doing a 'git-diff-index --cached' is basically very useful when you are
+So doing a `git diff-index --cached` is basically very useful when you are
 asking yourself "what have I already marked for being committed, and
 what's the difference to a previous tree".
 
@@ -80,20 +80,20 @@ Non-cached Mode
 ---------------
 The "non-cached" mode takes a different approach, and is potentially
 the more useful of the two in that what it does can't be emulated with
-a 'git-write-tree' + 'git-diff-tree'. Thus that's the default mode.
+a 'git write-tree' + 'git diff-tree'. Thus that's the default mode.
 The non-cached version asks the question:
 
   show me the differences between HEAD and the currently checked out
   tree - index contents _and_ files that aren't up-to-date
 
 which is obviously a very useful question too, since that tells you what
-you *could* commit. Again, the output matches the 'git-diff-tree -r'
+you *could* commit. Again, the output matches the 'git diff-tree -r'
 output to a tee, but with a twist.
 
 The twist is that if some file doesn't match the index, we don't have
 a backing store thing for it, and we use the magic "all-zero" sha1 to
 show that. So let's say that you have edited `kernel/sched.c`, but
-have not actually done a 'git-update-index' on it yet - there is no
+have not actually done a 'git update-index' on it yet - there is no
 "object" associated with the new state, and you get:
 
   torvalds@ppc970:~/v2.6/linux> git diff-index HEAD
@@ -104,11 +104,11 @@ not up-to-date and may contain new stuff. The all-zero sha1 means that to
 get the real diff, you need to look at the object in the working directory
 directly rather than do an object-to-object diff.
 
-NOTE: As with other commands of this type, 'git-diff-index' does not
+NOTE: As with other commands of this type, 'git diff-index' does not
 actually look at the contents of the file at all. So maybe
 `kernel/sched.c` hasn't actually changed, and it's just that you
 touched it. In either case, it's a note that you need to
-'git-update-index' it to make the index be in sync.
+'git update-index' it to make the index be in sync.
 
 NOTE: You can have a mixture of files show up as "has been updated"
 and "is still dirty in the working directory" together. You can always
index f2cef1260b7f74254b96ce65f8aa55267b618c29..a7e37b875fdf4bd25ce9adcb3296e2f2066f7130 100644 (file)
@@ -20,7 +20,7 @@ Compares the content and mode of the blobs found via two tree objects.
 If there is only one <tree-ish> given, the commit is compared with its parents
 (see --stdin below).
 
-Note that 'git-diff-tree' can use the tree encapsulated in a commit object.
+Note that 'git diff-tree' can use the tree encapsulated in a commit object.
 
 OPTIONS
 -------
@@ -67,25 +67,25 @@ The following flags further affect the behavior when comparing
 commits (but not trees).
 
 -m::
-       By default, 'git-diff-tree --stdin' does not show
+       By default, 'git diff-tree --stdin' does not show
        differences for merge commits.  With this flag, it shows
        differences to that commit from all of its parents. See
        also '-c'.
 
 -s::
-       By default, 'git-diff-tree --stdin' shows differences,
+       By default, 'git diff-tree --stdin' shows differences,
        either in machine-readable form (without '-p') or in patch
        form (with '-p').  This output can be suppressed.  It is
        only useful with '-v' flag.
 
 -v::
-       This flag causes 'git-diff-tree --stdin' to also show
+       This flag causes 'git diff-tree --stdin' to also show
        the commit message before the differences.
 
 include::pretty-options.txt[]
 
 --no-commit-id::
-       'git-diff-tree' outputs a line with the commit ID when
+       'git diff-tree' outputs a line with the commit ID when
        applicable.  This flag suppressed the commit ID output.
 
 -c::
index 0ac711230e24bf7238b2e3a215cdc3c4ce0d2087..723a64872fd4b43612d576781d915a7538189f46 100644 (file)
@@ -157,6 +157,10 @@ $ git diff -R                          <2>
 rewrites (very expensive).
 <2> Output diff in reverse.
 
+SEE ALSO
+--------
+linkgit:git-difftool[1]::
+       Show changes using common diff tools
 
 Author
 ------
index 8e9aed67d7f68c0234ee47dc147736bf3b8e5c4f..8250bad2ce95245ca8e2a41c0740d0029b92ae4b 100644 (file)
@@ -7,13 +7,13 @@ git-difftool - Show changes using common diff tools
 
 SYNOPSIS
 --------
-'git difftool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<'git diff' options>]
+'git difftool' [<options>] <commit>{0,2} [--] [<path>...]
 
 DESCRIPTION
 -----------
-'git-difftool' is a git command that allows you to compare and edit files
+'git difftool' is a git command that allows you to compare and edit files
 between revisions using common diff tools.  'git difftool' is a frontend
-to 'git-diff' and accepts the same options and arguments.
+to 'git diff' and accepts the same options and arguments.
 
 OPTIONS
 -------
@@ -33,23 +33,23 @@ OPTIONS
        kdiff3, kompare, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff,
        ecmerge, diffuse, opendiff, p4merge and araxis.
 +
-If a diff tool is not specified, 'git-difftool'
+If a diff tool is not specified, 'git difftool'
 will use the configuration variable `diff.tool`.  If the
-configuration variable `diff.tool` is not set, 'git-difftool'
+configuration variable `diff.tool` is not set, 'git difftool'
 will pick a suitable default.
 +
 You can explicitly provide a full path to the tool by setting the
 configuration variable `difftool.<tool>.path`. For example, you
 can configure the absolute path to kdiff3 by setting
-`difftool.kdiff3.path`. Otherwise, 'git-difftool' assumes the
+`difftool.kdiff3.path`. Otherwise, 'git difftool' assumes the
 tool is available in PATH.
 +
 Instead of running one of the known diff tools,
-'git-difftool' can be customized to run an alternative program
+'git difftool' can be customized to run an alternative program
 by specifying the command line to invoke in a configuration
 variable `difftool.<tool>.cmd`.
 +
-When 'git-difftool' is invoked with this tool (either through the
+When 'git difftool' is invoked with this tool (either through the
 `-t` or `--tool` option or the `diff.tool` configuration variable)
 the configured command line will be invoked with the following
 variables available: `$LOCAL` is set to the name of the temporary
@@ -58,16 +58,31 @@ is set to the name of the temporary file containing the contents
 of the diff post-image.  `$BASE` is provided for compatibility
 with custom merge tool commands and has the same value as `$LOCAL`.
 
+-x <command>::
+--extcmd=<command>::
+       Specify a custom command for viewing diffs.
+       'git-difftool' ignores the configured defaults and runs
+       `$command $LOCAL $REMOTE` when this option is specified.
+
+-g::
+--gui::
+       When 'git-difftool' is invoked with the `-g` or `--gui` option
+       the default diff tool will be read from the configured
+       `diff.guitool` variable instead of `diff.tool`.
+
 See linkgit:git-diff[1] for the full list of supported options.
 
 CONFIG VARIABLES
 ----------------
-'git-difftool' falls back to 'git-mergetool' config variables when the
+'git difftool' falls back to 'git mergetool' config variables when the
 difftool equivalents have not been defined.
 
 diff.tool::
        The default diff tool to use.
 
+diff.guitool::
+       The default diff tool to use when `--gui` is specified.
+
 difftool.<tool>.path::
        Override the path for the given tool.  This is useful in case
        your tool is not in the PATH.
index 75b06f33e73497fbb8916ef02d70ca59f4aab2f6..98ec6b5871b8dfb3cee2e1302099536d8b0dcd7f 100644 (file)
@@ -13,18 +13,18 @@ SYNOPSIS
 DESCRIPTION
 -----------
 This program dumps the given revisions in a form suitable to be piped
-into 'git-fast-import'.
+into 'git fast-import'.
 
 You can use it as a human-readable bundle replacement (see
 linkgit:git-bundle[1]), or as a kind of an interactive
-'git-filter-branch'.
+'git filter-branch'.
 
 
 OPTIONS
 -------
 --progress=<n>::
        Insert 'progress' statements every <n> objects, to be shown by
-       'git-fast-import' during import.
+       'git fast-import' during import.
 
 --signed-tags=(verbatim|warn|strip|abort)::
        Specify how to handle signed tags.  Since any transformation
@@ -37,7 +37,7 @@ unsigned, with 'verbatim', they will be silently exported
 and with 'warn', they will be exported, but you will see a warning.
 
 --tag-of-filtered-object=(abort|drop|rewrite)::
-       Specify how to handle tags whose tagged objectis filtered out.
+       Specify how to handle tags whose tagged object is filtered out.
        Since revisions and files to export can be limited by path,
        tagged objects may be filtered completely.
 +
@@ -91,8 +91,8 @@ marks the same across runs.
        already contains the necessary objects.
 
 [git-rev-list-args...]::
-       A list of arguments, acceptable to 'git-rev-parse' and
-       'git-rev-list', that specifies the specific objects and references
+       A list of arguments, acceptable to 'git rev-parse' and
+       'git rev-list', that specifies the specific objects and references
        to export.  For example, `master\~10..master` causes the
        current master reference to be exported along with all objects
        added since its 10th ancestor commit.
@@ -125,7 +125,7 @@ referenced by that revision range contains the string
 Limitations
 -----------
 
-Since 'git-fast-import' cannot tag trees, you will not be
+Since 'git fast-import' cannot tag trees, you will not be
 able to export the linux-2.6.git repository completely, as it contains
 a tag referencing a tree instead of a commit.
 
index e6d364f53cd14a1738a210e131bc7799fb746abb..6764ff188688070bfea379de7dccc13a1557cb20 100644 (file)
@@ -15,7 +15,7 @@ DESCRIPTION
 This program is usually not what the end user wants to run directly.
 Most end users want to use one of the existing frontend programs,
 which parses a specific type of foreign source and feeds the contents
-stored there to 'git-fast-import'.
+stored there to 'git fast-import'.
 
 fast-import reads a mixed command/data stream from standard input and
 writes one or more packfiles directly into the current repository.
@@ -24,7 +24,7 @@ updated branch and tag refs, fully updating the current repository
 with the newly imported data.
 
 The fast-import backend itself can import into an empty repository (one that
-has already been initialized by 'git-init') or incrementally
+has already been initialized by 'git init') or incrementally
 update an existing populated repository.  Whether or not incremental
 imports are supported from a particular foreign source depends on
 the frontend program in use.
@@ -44,12 +44,18 @@ OPTIONS
        not contain the old commit).
 
 --max-pack-size=<n>::
-       Maximum size of each output packfile, expressed in MiB.
-       The default is 4096 (4 GiB) as that is the maximum allowed
+       Maximum size of each output packfile.
+       The default is 4 GiB as that is the maximum allowed
        packfile size (due to file format limitations). Some
        importers may wish to lower this, such as to ensure the
        resulting packfiles fit on CDs.
 
+--big-file-threshold=<n>::
+       Maximum size of a blob that fast-import will attempt to
+       create a delta for, expressed in bytes.  The default is 512m
+       (512 MiB).  Some importers may wish to lower this on systems
+       with constrained memory.
+
 --depth=<n>::
        Maximum delta depth, for blob and tree deltification.
        Default is 10.
@@ -75,6 +81,20 @@ OPTIONS
        set of marks.  If a mark is defined to different values,
        the last file wins.
 
+--relative-marks::
+       After specifying --relative-marks= the paths specified
+       with --import-marks= and --export-marks= are relative
+       to an internal directory in the current repository.
+       In git-fast-import this means that the paths are relative
+       to the .git/info/fast-import directory. However, other
+       importers may use a different location.
+
+--no-relative-marks::
+       Negates a previous --relative-marks. Allows for combining
+       relative and non-relative marks by interweaving
+       --(no-)-relative-marks= with the --(import|export)-marks=
+       options.
+
 --export-pack-edges=<file>::
        After creating a packfile, print a line of data to
        <file> listing the filename of the packfile and the last
@@ -82,7 +102,7 @@ OPTIONS
        This information may be useful after importing projects
        whose total object set exceeds the 4 GiB packfile limit,
        as these commits can be used as edge points during calls
-       to 'git-pack-objects'.
+       to 'git pack-objects'.
 
 --quiet::
        Disable all non-fatal output, making fast-import silent when it
@@ -124,9 +144,9 @@ an ideal situation, given that most conversion tools are throw-away
 
 Parallel Operation
 ------------------
-Like 'git-push' or 'git-fetch', imports handled by fast-import are safe to
+Like 'git push' or 'git fetch', imports handled by fast-import are safe to
 run alongside parallel `git repack -a -d` or `git gc` invocations,
-or any other Git operation (including 'git-prune', as loose objects
+or any other Git operation (including 'git prune', as loose objects
 are never used by fast-import).
 
 fast-import does not lock the branch or tag refs it is actively importing.
@@ -138,7 +158,7 @@ fast-forward update, fast-import will skip updating that ref and instead
 prints a warning message.  fast-import will always attempt to update all
 branch refs, and does not stop on the first failure.
 
-Branch updates can be forced with \--force, but its recommended that
+Branch updates can be forced with \--force, but it's recommended that
 this only be used on an otherwise quiet repository.  Using \--force
 is not necessary for an initial import into an empty repository.
 
@@ -220,7 +240,7 @@ variation in formatting will cause fast-import to reject the value.
 +
 An example value is ``Tue Feb 6 11:22:18 2007 -0500''.  The Git
 parser is accurate, but a little on the lenient side.  It is the
-same parser used by 'git-am' when applying patches
+same parser used by 'git am' when applying patches
 received from email.
 +
 Some malformed strings may be accepted as valid dates.  In some of
@@ -253,10 +273,10 @@ is always copied into the identity string at the time it is being
 created by fast-import.  There is no way to specify a different time or
 timezone.
 +
-This particular format is supplied as its short to implement and
+This particular format is supplied as it's short to implement and
 may be useful to a process that wants to create a new commit
 right now, without needing to use a working directory or
-'git-update-index'.
+'git update-index'.
 +
 If separate `author` and `committer` commands are used in a `commit`
 the timestamps may not match, as the system clock will be polled
@@ -303,6 +323,15 @@ and control the current import process.  More detailed discussion
        standard output.  This command is optional and is not needed
        to perform an import.
 
+`feature`::
+       Require that fast-import supports the specified feature, or
+       abort if it does not.
+
+`option`::
+       Specify any of the options listed under OPTIONS that do not
+       change stream semantic to suit the frontend's needs. This
+       command is optional and is not needed to perform an import.
+
 `commit`
 ~~~~~~~~
 Create or update a branch with a new commit, recording one logical
@@ -397,7 +426,7 @@ quoting or escaping syntax is supported within `<committish>`.
 Here `<committish>` is any of the following:
 
 * The name of an existing branch already in fast-import's internal branch
-  table.  If fast-import doesn't know the name, its treated as a SHA-1
+  table.  If fast-import doesn't know the name, it's treated as a SHA-1
   expression.
 
 * A mark reference, `:<idnum>`, where `<idnum>` is the mark number.
@@ -690,7 +719,7 @@ recommended, as the frontend does not (easily) have access to the
 complete set of bytes which normally goes into such a signature.
 If signing is required, create lightweight tags from within fast-import with
 `reset`, then create the annotated versions of those tags offline
-with the standard 'git-tag' process.
+with the standard 'git tag' process.
 
 `reset`
 ~~~~~~~
@@ -736,7 +765,7 @@ assigned mark.
 
 The mark command is optional here as some frontends have chosen
 to generate the Git SHA-1 for the blob on their own, and feed that
-directly to `commit`.  This is typically more work than its worth
+directly to `commit`.  This is typically more work than it's worth
 however, as marks are inexpensive to store and easy to use.
 
 `data`
@@ -846,6 +875,62 @@ Placing a `progress` command immediately after a `checkpoint` will
 inform the reader when the `checkpoint` has been completed and it
 can safely access the refs that fast-import updated.
 
+`feature`
+~~~~~~~~~
+Require that fast-import supports the specified feature, or abort if
+it does not.
+
+....
+       'feature' SP <feature> LF
+....
+
+The <feature> part of the command may be any string matching
+^[a-zA-Z][a-zA-Z-]*$ and should be understood by fast-import.
+
+Feature work identical as their option counterparts with the
+exception of the import-marks feature, see below.
+
+The following features are currently supported:
+
+* date-format
+* import-marks
+* export-marks
+* relative-marks
+* no-relative-marks
+* force
+
+The import-marks behaves differently from when it is specified as
+commandline option in that only one "feature import-marks" is allowed
+per stream. Also, any --import-marks= specified on the commandline
+will override those from the stream (if any).
+
+`option`
+~~~~~~~~
+Processes the specified option so that git fast-import behaves in a
+way that suits the frontend's needs.
+Note that options specified by the frontend are overridden by any
+options the user may specify to git fast-import itself.
+
+....
+    'option' SP <option> LF
+....
+
+The `<option>` part of the command may contain any of the options
+listed in the OPTIONS section that do not change import semantics,
+without the leading '--' and is treated in the same way.
+
+Option commands must be the first commands on the input (not counting
+feature commands), to give an option command after any non-option
+command is an error.
+
+The following commandline options change import semantics and may therefore
+not be passed as option:
+
+* date-format
+* import-marks
+* export-marks
+* force
+
 Crash Reports
 -------------
 If fast-import is supplied invalid input it will terminate with a
@@ -991,7 +1076,7 @@ is not `refs/heads/TAG_FIXUP`).
 
 When committing fixups, consider using `merge` to connect the
 commit(s) which are supplying file revisions to the fixup branch.
-Doing so will allow tools such as 'git-blame' to track
+Doing so will allow tools such as 'git blame' to track
 through the real commit history and properly annotate the source
 files.
 
@@ -1020,7 +1105,7 @@ Repacking Historical Data
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 If you are repacking very old imported data (e.g. older than the
 last year), consider expending some extra CPU time and supplying
-\--window=50 (or higher) when you run 'git-repack'.
+\--window=50 (or higher) when you run 'git repack'.
 This will take longer, but will also produce a smaller packfile.
 You only need to expend the effort once, and everyone using your
 project will benefit from the smaller repository.
index 47448da22eeebf51fe5829717df2dc7129a9b17e..e9952e82108d3a0fa209bc9d720fa1f1229310b4 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Usually you would want to use 'git-fetch', which is a
+Usually you would want to use 'git fetch', which is a
 higher level wrapper of this command, instead.
 
 Invokes 'git-upload-pack' on a possibly remote repository
@@ -33,12 +33,12 @@ OPTIONS
 
 -q::
 --quiet::
-       Pass '-q' flag to 'git-unpack-objects'; this makes the
+       Pass '-q' flag to 'git unpack-objects'; this makes the
        cloning process less verbose.
 
 -k::
 --keep::
-       Do not invoke 'git-unpack-objects' on received data, but
+       Do not invoke 'git unpack-objects' on received data, but
        create a single packfile out of it instead, and store it
        in the object database. If provided twice then the pack is
        locked against repacking.
index 9b9e5686e4d4e1ca41bba1c5a50715463975cad1..948ea26c5a2b3825e61d0c6495d03829669a7351 100644 (file)
@@ -24,7 +24,7 @@ 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
-operation done by 'git-merge'.
+operation done by 'git merge'.
 
 When <refspec> stores the fetched result in tracking branches,
 the tags that point at these branches are automatically
index 394a77a35f2019f5c86f9e90e94b88cf56da04ae..020028cf9a54540f5e0e403b52e08dd2921a644e 100644 (file)
@@ -116,7 +116,7 @@ OPTIONS
 --commit-filter <command>::
        This is the filter for performing the commit.
        If this filter is specified, it will be called instead of the
-       'git-commit-tree' command, with arguments of the form
+       'git commit-tree' command, with arguments of the form
        "<TREE_ID> [-p <PARENT_COMMIT_ID>]..." and the log message on
        stdin.  The commit id is expected on stdout.
 +
@@ -127,10 +127,10 @@ have all of them as parents.
 You can use the 'map' convenience function in this filter, and other
 convenience functions, too.  For example, calling 'skip_commit "$@"'
 will leave out the current commit (but not its changes! If you want
-that, use 'git-rebase' instead).
+that, use 'git rebase' instead).
 +
-You can also use the 'git_commit_non_empty_tree "$@"' instead of
-'git commit-tree "$@"' if you don't wish to keep commits with a single parent
+You can also use the `git_commit_non_empty_tree "$@"` instead of
+`git commit-tree "$@"` if you don't wish to keep commits with a single parent
 and that makes no change to the tree.
 
 --tag-name-filter <command>::
@@ -179,7 +179,7 @@ the nearest ancestor that was not excluded.
        and only one parent, it will hence keep merges points. Also, this
        option is not compatible with the use of '--commit-filter'. Though you
        just need to use the function 'git_commit_non_empty_tree "$@"' instead
-       of the 'git commit-tree "$@"' idiom in your commit filter to make that
+       of the `git commit-tree "$@"` idiom in your commit filter to make that
        happen.
 
 --original <namespace>::
@@ -196,15 +196,15 @@ the nearest ancestor that was not excluded.
 
 -f::
 --force::
-       'git-filter-branch' refuses to start with an existing temporary
+       'git filter-branch' refuses to start with an existing temporary
        directory or when there are already refs starting with
        'refs/original/', unless forced.
 
 <rev-list options>...::
-       Arguments for 'git-rev-list'.  All positive refs included by
+       Arguments for 'git rev-list'.  All positive refs included by
        these options are rewritten.  You may also specify options
        such as '--all', but you must use '--' to separate them from
-       the 'git-filter-branch' options.
+       the 'git filter-branch' options.
 
 
 Examples
@@ -221,7 +221,7 @@ However, if the file is absent from the tree of some commit,
 a simple `rm filename` will fail for that tree and commit.
 Thus you may instead want to use `rm -f filename` as the script.
 
-Using `\--index-filter` with 'git-rm' yields a significantly faster
+Using `\--index-filter` with 'git rm' yields a significantly faster
 version.  Like with using `rm filename`, `git rm --cached filename`
 will fail if the file is absent from the tree of a commit.  If you
 want to "completely forget" a file, it does not matter when it entered
@@ -303,7 +303,7 @@ and all children of the merge will become merge commits with P1,P2
 as their parents instead of the merge commit.
 
 You can rewrite the commit log messages using `--msg-filter`.  For
-example, 'git-svn-id' strings in a repository created by 'git-svn' can
+example, 'git svn-id' strings in a repository created by 'git svn' can
 be removed this way:
 
 -------------------------------------------------------
@@ -314,7 +314,7 @@ git filter-branch --msg-filter '
 
 To restrict rewriting to only part of the history, specify a revision
 range in addition to the new branch name.  The new branch name will
-point to the top-most revision that a 'git-rev-list' of this range
+point to the top-most revision that a 'git rev-list' of this range
 will print.
 
 If you need to add 'Acked-by' lines to, say, the last 10 commits (none
@@ -330,7 +330,7 @@ git filter-branch --msg-filter '
 *NOTE* the changes introduced by the commits, and which are not reverted
 by subsequent commits, will still be in the rewritten branch. If you want
 to throw out _changes_ together with the commits, you should use the
-interactive mode of 'git-rebase'.
+interactive mode of 'git rebase'.
 
 
 Consider this history:
@@ -358,7 +358,7 @@ To move the whole tree into a subdirectory, or remove it from there:
 
 ---------------------------------------------------------------
 git filter-branch --index-filter \
-       'git ls-files -s | sed "s-\t-&newsubdir/-" |
+       'git ls-files -s | sed "s-\t\"*-&newsubdir/-" |
                GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                        git update-index --index-info &&
         mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD
index a586950b48bac6eda630383714c0ccd1b28820a8..a585dbe898171ddb8a205cc5a898d695540f80a6 100644 (file)
@@ -16,7 +16,7 @@ DESCRIPTION
 -----------
 Takes the list of merged objects on stdin and produces a suitable
 commit message to be used for the merge commit, usually to be
-passed as the '<merge-message>' argument of 'git-merge'.
+passed as the '<merge-message>' argument of 'git merge'.
 
 This command is intended mostly for internal use by scripts
 automatically invoking 'git merge'.
index 8dc873fd4465879ec3224732c690f1173a6e2dc2..7e83288d1846a7fcd53ec46776160a2f5ffbad84 100644 (file)
@@ -82,7 +82,7 @@ objecttype::
        The type of the object (`blob`, `tree`, `commit`, `tag`).
 
 objectsize::
-       The size of the object (the same as 'git-cat-file -s' reports).
+       The size of the object (the same as 'git cat-file -s' reports).
 
 objectname::
        The object name (aka SHA-1).
index f1fd0df08ae3067f8f5b41797e5575d465a859af..9674f9de67b18880b51382caf4c06d85778284b8 100644 (file)
@@ -29,7 +29,7 @@ DESCRIPTION
 Prepare each commit with its patch in
 one file per commit, formatted to resemble UNIX mailbox format.
 The output of this command is convenient for e-mail submission or
-for use with 'git-am'.
+for use with 'git am'.
 
 There are two ways to specify which commits to operate on.
 
@@ -221,7 +221,7 @@ EXAMPLES
 --------
 
 * Extract commits between revisions R1 and R2, and apply them on top of
-the current branch using 'git-am' to cherry-pick them:
+the current branch using 'git am' to cherry-pick them:
 +
 ------------
 $ git format-patch -k --stdout R1..R2 | git am -3 -k
index 6fe9484da325fa1f72809937df263574ffe6fb9b..3ad48a633663ec22af16b4737c42fd7e7664e4d2 100644 (file)
@@ -21,7 +21,7 @@ OPTIONS
 <object>::
        An object to treat as the head of an unreachability trace.
 +
-If no objects are given, 'git-fsck' defaults to using the
+If no objects are given, 'git fsck' defaults to using the
 index file, all SHA1 references in .git/refs/*, and all reflogs (unless
 --no-reflogs is given) as heads.
 
@@ -85,7 +85,7 @@ So for example
 
 will do quite a _lot_ of verification on the tree. There are a few
 extra validity tests to be added (make sure that tree objects are
-sorted properly etc), but on the whole if 'git-fsck' is happy, you
+sorted properly etc), but on the whole if 'git fsck' is happy, you
 do have a valid tree.
 
 Any corrupt objects you will have to find in backups or other archives
index 4cd9cdf9056fe78b3acbbf62aa080b1e47907b0a..189573a3b3d459822b465d39db2f90001ffc98d3 100644 (file)
@@ -15,13 +15,13 @@ DESCRIPTION
 Runs a number of housekeeping tasks within the current repository,
 such as compressing file revisions (to reduce disk space and increase
 performance) and removing unreachable objects which may have been
-created from prior invocations of 'git-add'.
+created from prior invocations of 'git add'.
 
 Users are encouraged to run this task on a regular basis within
 each repository to maintain good disk space utilization and good
 operating performance.
 
-Some git commands may automatically run 'git-gc'; see the `--auto` flag
+Some git commands may automatically run 'git gc'; see the `--auto` flag
 below for details. If you know what you're doing and all you want is to
 disable this behavior permanently without further considerations, just do:
 
@@ -33,15 +33,15 @@ OPTIONS
 -------
 
 --aggressive::
-       Usually 'git-gc' runs very quickly while providing good disk
+       Usually 'git gc' runs very quickly while providing good disk
        space utilization and performance.  This option will cause
-       'git-gc' to more aggressively optimize the repository at the expense
+       'git gc' to more aggressively optimize the repository at the expense
        of taking much more time.  The effects of this optimization are
        persistent, so this option only needs to be used occasionally; every
        few hundred changesets or so.
 
 --auto::
-       With this option, 'git-gc' checks whether any housekeeping is
+       With this option, 'git gc' checks whether any housekeeping is
        required; if not, it exits without performing any work.
        Some git commands run `git gc --auto` after performing
        operations that could create many loose objects.
@@ -50,13 +50,13 @@ Housekeeping is required if there are too many loose objects or
 too many packs in the repository. If the number of loose objects
 exceeds the value of the `gc.auto` configuration variable, then
 all loose objects are combined into a single pack using
-'git-repack -d -l'.  Setting the value of `gc.auto` to 0
+`git repack -d -l`.  Setting the value of `gc.auto` to 0
 disables automatic packing of loose objects.
 +
 If the number of packs exceeds the value of `gc.autopacklimit`,
 then existing packs (except those marked with a `.keep` file)
 are consolidated into a single pack by using the `-A` option of
-'git-repack'. Setting `gc.autopacklimit` to 0 disables
+'git repack'. Setting `gc.autopacklimit` to 0 disables
 automatic consolidation of packs.
 
 --prune=<date>::
@@ -97,7 +97,7 @@ how long records of conflicted merge you have not resolved are
 kept.  This defaults to 15 days.
 
 The optional configuration variable 'gc.packrefs' determines if
-'git-gc' runs 'git-pack-refs'. This can be set to "nobare" to enable
+'git gc' runs 'git pack-refs'. This can be set to "nobare" to enable
 it within all non-bare repos or it can be set to a boolean value.
 This defaults to true.
 
@@ -116,10 +116,10 @@ default is "2 weeks ago".
 Notes
 -----
 
-'git-gc' tries very hard to be safe about the garbage it collects. In
+'git gc' tries very hard to be safe about the garbage it collects. In
 particular, it will keep not only objects referenced by your current set
 of branches and tags, but also objects referenced by the index, remote
-tracking branches, refs saved by 'git-filter-branch' in
+tracking branches, refs saved by 'git filter-branch' in
 refs/original/, or reflogs (which may reference commits in branches
 that were later amended or rewound).
 
index 84f23ee525336fc2bdd289991b97eafecddc14b2..790af9573b02d643aa6dae117f8b50f7cb565b9b 100644 (file)
@@ -14,12 +14,12 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Acts as a filter, extracting the commit ID stored in archives created by
-'git-archive'.  It reads only the first 1024 bytes of input, thus its
+'git archive'.  It reads only the first 1024 bytes of input, thus its
 runtime is not influenced by the size of <tarfile> very much.
 
-If no commit ID is found, 'git-get-tar-commit-id' quietly exists with a
+If no commit ID is found, 'git get-tar-commit-id' quietly exists with a
 return code of 1.  This can happen if <tarfile> had not been created
-using 'git-archive' or if the first parameter of 'git-archive' had been
+using 'git archive' or if the first parameter of 'git archive' had been
 a tree ID instead of a commit ID or tag.
 
 
index 8c700200f55e1a92545cfb8218c8c1ba2cedee68..e019e760b4b4d58dfbe8819941947008319aedac 100644 (file)
@@ -16,7 +16,7 @@ SYNOPSIS
           [-F | --fixed-strings] [-n]
           [-l | --files-with-matches] [-L | --files-without-match]
           [-z | --null]
-          [-c | --count] [--all-match]
+          [-c | --count] [--all-match] [-q | --quiet]
           [--max-depth <depth>]
           [--color | --no-color]
           [-A <post-context>] [-B <pre-context>] [-C <context>]
@@ -98,7 +98,7 @@ OPTIONS
 --files-without-match::
        Instead of showing every matched line, show only the
        names of files that contain (or do not contain) matches.
-       For better compatibility with 'git-diff', --name-only is a
+       For better compatibility with 'git diff', --name-only is a
        synonym for --files-with-matches.
 
 -z::
@@ -158,6 +158,11 @@ OPTIONS
        this flag is specified to limit the match to files that
        have lines to match all of them.
 
+-q::
+--quiet::
+       Do not output matched lines; instead, exit with status 0 when
+       there is a match and with non-zero status when there isn't.
+
 `<tree>...`::
        Search blobs in the trees for specified patterns.
 
index d0bc98b85289b42727afc903a50e9e83d37def35..2563710b56bf441125bda6766365eb2a2b5eb46a 100644 (file)
@@ -11,19 +11,19 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-A Tcl/Tk based graphical user interface to Git.  'git-gui' focuses
+A Tcl/Tk based graphical user interface to Git.  'git gui' focuses
 on allowing users to make changes to their repository by making
 new commits, amending existing ones, creating branches, performing
 local merges, and fetching/pushing to remote repositories.
 
-Unlike 'gitk', 'git-gui' focuses on commit generation
+Unlike 'gitk', 'git gui' focuses on commit generation
 and single file annotation and does not show project history.
 It does however supply menu actions to start a 'gitk' session from
-within 'git-gui'.
+within 'git gui'.
 
-'git-gui' is known to work on all popular UNIX systems, Mac OS X,
+'git gui' is known to work on all popular UNIX systems, Mac OS X,
 and Windows (under both Cygwin and MSYS).  To the extent possible
-OS specific user interface guidelines are followed, making 'git-gui'
+OS specific user interface guidelines are followed, making 'git gui'
 a fairly native interface for users.
 
 COMMANDS
@@ -38,13 +38,13 @@ browser::
        browser are opened in the blame viewer.
 
 citool::
-       Start 'git-gui' and arrange to make exactly one commit before
+       Start 'git gui' and arrange to make exactly one commit before
        exiting and returning to the shell.  The interface is limited
        to only commit actions, slightly reducing the application's
        startup time and simplifying the menubar.
 
 version::
-       Display the currently running version of 'git-gui'.
+       Display the currently running version of 'git gui'.
 
 
 Examples
@@ -103,15 +103,15 @@ SEE ALSO
 linkgit:gitk[1]::
        The git repository browser.  Shows branches, commit history
        and file differences.  gitk is the utility started by
-       'git-gui''s Repository Visualize actions.
+       'git gui''s Repository Visualize actions.
 
 Other
 -----
-'git-gui' is actually maintained as an independent project, but stable
+'git gui' is actually maintained as an independent project, but stable
 versions are distributed as part of the Git suite for the convenience
 of end users.
 
-A 'git-gui' development repository can be obtained from:
+A 'git gui' development repository can be obtained from:
 
   git clone git://repo.or.cz/git-gui.git
 
index 0af40cfb85ca6e0eb6e540f0beb47e449ef25afd..479fce4693982526f48c32a36e5215bf13dcc3e6 100644 (file)
@@ -18,7 +18,7 @@ Computes the object ID value for an object with specified type
 with the contents of the named file (which can be outside of the
 work tree), and optionally writes the resulting object into the
 object database.  Reports its object ID to its standard output.
-This is used by 'git-cvsimport' to update the index
+This is used by 'git cvsimport' to update the index
 without modifying files in the work tree.  When <type> is not
 specified, it defaults to "blob".
 
index d9b9c34b3a60f09bea4f9e53b5127f33f356e042..f8df109d07240d4146ad37928147100f2d3a6bb2 100644 (file)
@@ -55,8 +55,8 @@ other display programs (see below).
 +
 The web browser can be specified using the configuration variable
 'help.browser', or 'web.browser' if the former is not set. If none of
-these config variables is set, the 'git-web--browse' helper script
-(called by 'git-help') will pick a suitable default. See
+these config variables is set, the 'git web--browse' helper script
+(called by 'git help') will pick a suitable default. See
 linkgit:git-web--browse[1] for more information about this.
 
 CONFIGURATION VARIABLES
@@ -67,7 +67,7 @@ help.format
 
 If no command line option is passed, the 'help.format' configuration
 variable will be checked. The following values are supported for this
-variable; they make 'git-help' behave as their corresponding command
+variable; they make 'git help' behave as their corresponding command
 line option:
 
 * "man" corresponds to '-m|--man',
@@ -122,7 +122,7 @@ man.<tool>.path
 You can explicitly provide a full path to your preferred man viewer by
 setting the configuration variable 'man.<tool>.path'. For example, you
 can configure the absolute path to konqueror by setting
-'man.konqueror.path'. Otherwise, 'git-help' assumes the tool is
+'man.konqueror.path'. Otherwise, 'git help' assumes the tool is
 available in PATH.
 
 man.<tool>.cmd
index 67aec067c8ffd75837ccc1d3e2524f5db2eb9a75..52388206570238636d50ed360120d3464f630a94 100644 (file)
@@ -8,21 +8,26 @@ git-http-backend - Server side implementation of Git over HTTP
 SYNOPSIS
 --------
 [verse]
-'git-http-backend'
+'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
+The program supports clients fetching using both the smart HTTP protocol
 and the backwards-compatible dumb HTTP protocol, as well as clients
 pushing using the smart HTTP protocol.
 
+It verifies that the directory has the magic file
+"git-daemon-export-ok", and it will refuse to export any git directory
+that hasn't explicitly been marked for export this way (unless the
+GIT_HTTP_EXPORT_ALL environmental variable is set).
+
 By default, only the `upload-pack` service is enabled, which serves
-'git-fetch-pack' and 'git-ls-remote' clients, which are invoked from
-'git-fetch', 'git-pull', and 'git-clone'.  If the client is authenticated,
-the `receive-pack` service is enabled, which serves 'git-send-pack'
-clients, which is invoked from 'git-push'.
+'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
 --------
@@ -38,12 +43,12 @@ http.getanyfile::
        by setting this configuration item to `false`.
 
 http.uploadpack::
-       This serves 'git-fetch-pack' and 'git-ls-remote' clients.
+       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
+       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
@@ -51,11 +56,11 @@ http.receivepack::
 
 URL TRANSLATION
 ---------------
-To determine the location of the repository on disk, 'git-http-backend'
+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
+set, 'git http-backend' reads PATH_TRANSLATED, which is also set
 automatically by the web server.
 
 EXAMPLES
@@ -70,6 +75,7 @@ Apache 2.x::
 +
 ----------------------------------------------------------------
 SetEnv GIT_PROJECT_ROOT /var/www/git
+SetEnv GIT_HTTP_EXPORT_ALL
 ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
 ----------------------------------------------------------------
 +
@@ -98,7 +104,7 @@ directive around the repository, or one of its parent directories:
 ----------------------------------------------------------------
 +
 To serve gitweb at the same url, use a ScriptAliasMatch to only
-those URLs that 'git-http-backend' can handle, and forward the
+those URLs that 'git http-backend' can handle, and forward the
 rest to gitweb:
 +
 ----------------------------------------------------------------
@@ -147,7 +153,7 @@ ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/
 
 ENVIRONMENT
 -----------
-'git-http-backend' relies upon the CGI environment variables set
+'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)
@@ -157,6 +163,10 @@ by the invoking web server, including:
 * QUERY_STRING
 * REQUEST_METHOD
 
+The GIT_HTTP_EXPORT_ALL environmental variable may be passed to
+'git-http-backend' to bypass the check for the "git-daemon-export-ok"
+file in each repository before allowing export of that repository.
+
 The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and
 GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}',
 ensuring that any reflogs created by 'git-receive-pack' contain some
index e7c796155fdd0ad644decf5dc488c6d780a2d164..d91cb7ff85b8c05a39cd18757bc66a16662c5150 100644 (file)
@@ -35,7 +35,7 @@ commit-id::
 
 --stdin::
        Instead of a commit id on the command line (which is not expected in this
-       case), 'git-http-fetch' expects lines on stdin in the format
+       case), 'git http-fetch' expects lines on stdin in the format
 
                <commit-id>['\t'<filename-as-in--w>]
 
index d016dafd491de5a4635e30b92607dadbaf7bf46f..57db955bd4cc82f643ed5b932b33d440d8cf409a 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-This command uploads a mailbox generated with 'git-format-patch'
+This command uploads a mailbox generated with 'git format-patch'
 into an IMAP drafts folder.  This allows patches to be sent as
 other email is when using mail clients that cannot read mailbox
 files directly.
index 4b5c743c1e5f11281e2b9df7508d57e9878ee5d2..65a301becefb837b74bffa2d2dd11d51e229a06a 100644 (file)
@@ -43,10 +43,10 @@ OPTIONS
        a default name determined from the pack content.  If
        <pack-file> is not specified consider using --keep to
        prevent a race condition between this process and
-       'git-repack'.
+       'git repack'.
 
 --fix-thin::
-       It is possible for 'git-pack-objects' to build
+       It is possible for 'git pack-objects' to build
        "thin" pack, which records objects in deltified form based on
        objects not included in the pack to reduce network traffic.
        Those objects are expected to be present on the receiving end
@@ -59,7 +59,7 @@ OPTIONS
        Before moving the index into its final destination
        create an empty .keep file for the associated pack file.
        This option is usually necessary with --stdin to prevent a
-       simultaneous 'git-repack' process from deleting
+       simultaneous 'git repack' process from deleting
        the newly constructed pack and index before refs can be
        updated to use objects contained in the pack.
 
@@ -86,7 +86,7 @@ Once the index has been created, the list of object names is sorted
 and the SHA1 hash of that list is printed to stdout. If --stdin was
 also used then this is prefixed by either "pack\t", or "keep\t" if a
 new .keep file was successfully created. This is useful to remove a
-.keep file used as a lock to prevent the race with 'git-repack'
+.keep file used as a lock to prevent the race with 'git repack'
 mentioned above.
 
 
index f081b24d9d8233677388d437b8f7e6d9402b261b..7ee102da485d0b6490c0b767a20966956bf0bd88 100644 (file)
@@ -95,11 +95,11 @@ If the object storage directory is specified via the `$GIT_OBJECT_DIRECTORY`
 environment variable then the sha1 directories are created underneath -
 otherwise the default `$GIT_DIR/objects` directory is used.
 
-Running 'git-init' in an existing repository is safe. It will not overwrite
-things that are already there. The primary reason for rerunning 'git-init'
+Running 'git init' in an existing repository is safe. It will not overwrite
+things that are already there. The primary reason for rerunning 'git init'
 is to pick up newly added templates.
 
-Note that 'git-init' is the same as 'git-init-db'.  The command
+Note that 'git init' is the same as 'git init-db'.  The command
 was primarily meant to initialize the object database, but over
 time it has become responsible for setting up the other aspects
 of the repository, such as installing the default hooks and
index 0771f254436c9024b935ef745c3bcd384bc18465..a1f17df0744361ab999314b1834c2e2384af90bd 100644 (file)
@@ -44,7 +44,7 @@ OPTIONS
 -b::
 --browser::
        The web browser that should be used to view the gitweb
-       page. This will be passed to the 'git-web--browse' helper
+       page. This will be passed to the 'git web--browse' helper
        script along with the URL of the gitweb instance. See
        linkgit:git-web--browse[1] for more information about this. If
        the script fails, the URL will be printed to stdout.
index 3d79de11ec36d0212610a4bec68789bdeec6c1fe..0e39bb61eebfce5d1bff032c65bf04bb77f8ce62 100644 (file)
@@ -14,9 +14,9 @@ DESCRIPTION
 -----------
 Shows the commit logs.
 
-The command takes options applicable to the 'git-rev-list'
+The command takes options applicable to the 'git rev-list'
 command to control what is shown and how, and options applicable to
-the 'git-diff-*' commands to control how the changes
+the 'git diff-*' commands to control how the changes
 each commit introduces are shown.
 
 
@@ -107,6 +107,17 @@ git log --follow builtin-rev-list.c::
        those commits that occurred before the file was given its
        present name.
 
+git log --branches --not --remotes=origin::
+
+       Shows all commits that are in any of local branches but not in
+       any of remote tracking branches for 'origin' (what you have that
+       origin doesn't).
+
+git log master --not --remotes=*/master::
+
+       Shows all commits that are in local master but not in any remote
+       repository master branches.
+
 Discussion
 ----------
 
index 625723e41fa072523fa850e37534677cfe8af631..3521637b582687978c088dc463e8784817a92217 100644 (file)
@@ -109,6 +109,7 @@ OPTIONS
        Identify the file status with the following tags (followed by
        a space) at the start of each line:
        H::     cached
+       S::     skip-worktree
        M::     unmerged
        R::     removed/deleted
        C::     modified/changed
@@ -140,12 +141,12 @@ OPTIONS
 
 Output
 ------
-show files just outputs the filename unless '--stage' is specified in
+'git ls-files' just outputs the filenames unless '--stage' is specified in
 which case it outputs:
 
         [<tag> ]<mode> <object> <stage> <file>
 
-'git-ls-files --unmerged' and 'git-ls-files --stage' can be used to examine
+'git ls-files --unmerged' and 'git ls-files --stage' can be used to examine
 detailed information on unmerged paths.
 
 For an unmerged path, instead of recording a single mode/SHA1 pair,
@@ -162,7 +163,7 @@ respectively.
 Exclude Patterns
 ----------------
 
-'git-ls-files' can use a list of "exclude patterns" when
+'git ls-files' can use a list of "exclude patterns" when
 traversing the directory tree and finding files to show when the
 flags --others or --ignored are specified.  linkgit:gitignore[5]
 specifies the format of exclude patterns.
@@ -178,7 +179,7 @@ These exclude patterns come from these places, in order:
      in the same order they appear in the file.
 
   3. command line flag --exclude-per-directory=<name> specifies
-     a name of the file in each directory 'git-ls-files'
+     a name of the file in each directory 'git ls-files'
      examines, normally `.gitignore`.  Files in deeper
      directories take precedence.  Patterns are ordered in the
      same order they appear in the files.
index c3fdccb4c2f66b8b3a3c6b60b21feac881b06cb4..1f89d3680043cc98ad39facb1f43ae770ff66e14 100644 (file)
@@ -28,7 +28,7 @@ in the current working directory.  Note that:
    in a directory 'sub' that has a directory 'dir', you can run 'git
    ls-tree -r HEAD dir' to list the contents of the tree (that is
    'sub/dir' in 'HEAD').  You don't want to give a tree that is not at the
-   root level (e.g. 'git ls-tree -r HEAD:sub dir') in this case, as that
+   root level (e.g. `git ls-tree -r HEAD:sub dir`) in this case, as that
    would result in asking for 'sub/sub/dir' in the 'HEAD' commit.
    However, the current working directory can be ignored by passing
    --full-tree option.
@@ -84,7 +84,7 @@ Output Format
 
 Unless the `-z` option is used, TAB, LF, and backslash characters
 in pathnames are represented as `\t`, `\n`, and `\\`, respectively.
-This output format is compatible with what '--index-info --stdin' of
+This output format is compatible with what `--index-info --stdin` of
 'git update-index' expects.
 
 When the `-l` option is used, format changes to
index b81ac98cf0d95ab961b228e514c8fdabc152a21e..e3d58cbac3f162cc01d8731485f220fd70fed17b 100644 (file)
@@ -16,7 +16,7 @@ DESCRIPTION
 Reads a single e-mail message from the standard input, and
 writes the commit log message in <msg> file, and the patches in
 <patch> file.  The author name, e-mail and e-mail subject are
-written out to the standard output to be used by 'git-am'
+written out to the standard output to be used by 'git am'
 to create a commit.  It is usually not necessary to use this
 command directly.  See linkgit:git-am[1] instead.
 
@@ -30,7 +30,7 @@ OPTIONS
        whitespaces, (3) '[' up to ']', typically '[PATCH]', and
        then prepends "[PATCH] ".  This flag forbids this
        munging, and is most useful when used to read back
-       'git-format-patch -k' output.
+       'git format-patch -k' output.
 
 -b::
        When -k is not in effect, all leading strings bracketed with '['
index 303537357b2fb5d1a0d7fa291d51803ddb97749c..234269ae59234de67ea2cce42edec83cebc400be 100644 (file)
@@ -10,20 +10,21 @@ SYNOPSIS
 --------
 [verse]
 'git merge-file' [-L <current-name> [-L <base-name> [-L <other-name>]]]
-       [-p|--stdout] [-q|--quiet] <current-file> <base-file> <other-file>
+       [--ours|--theirs] [-p|--stdout] [-q|--quiet]
+       <current-file> <base-file> <other-file>
 
 
 DESCRIPTION
 -----------
-'git-merge-file' incorporates all changes that lead from the `<base-file>`
+'git merge-file' incorporates all changes that lead from the `<base-file>`
 to `<other-file>` into `<current-file>`. The result ordinarily goes into
-`<current-file>`. 'git-merge-file' is useful for combining separate changes
+`<current-file>`. 'git merge-file' is useful for combining separate changes
 to an original. Suppose `<base-file>` is the original, and both
 `<current-file>` and `<other-file>` are modifications of `<base-file>`,
-then 'git-merge-file' combines both changes.
+then 'git merge-file' combines both changes.
 
 A conflict occurs if both `<current-file>` and `<other-file>` have changes
-in a common segment of lines. If a conflict is found, 'git-merge-file'
+in a common segment of lines. If a conflict is found, 'git merge-file'
 normally outputs a warning and brackets the conflict with lines containing
 <<<<<<< and >>>>>>> markers. A typical conflict will look like this:
 
@@ -34,12 +35,14 @@ normally outputs a warning and brackets the conflict with lines containing
        >>>>>>> B
 
 If there are conflicts, the user should edit the result and delete one of
-the alternatives.
+the alternatives.  When `--ours` or `--theirs` option is in effect, however,
+these conflicts are resolved favouring lines from `<current-file>` or
+lines from `<other-file>` respectively.
 
 The exit value of this program is negative on error, and the number of
 conflicts otherwise. If the merge was clean, the exit value is 0.
 
-'git-merge-file' is designed to be a minimal clone of RCS 'merge'; that is, it
+'git merge-file' is designed to be a minimal clone of RCS 'merge'; that is, it
 implements all of RCS 'merge''s functionality which is needed by
 linkgit:git[1].
 
@@ -62,6 +65,11 @@ OPTIONS
 -q::
        Quiet; do not warn about conflicts.
 
+--ours::
+--theirs::
+       Instead of leaving conflicts in the file, resolve conflicts
+       favouring our (or their) side of the lines.
+
 
 EXAMPLES
 --------
index 123e6d024a47537981526d0c078249565e72d74f..4d266de9ccb9d6881b18162c132ccd8475ca50ee 100644 (file)
@@ -36,14 +36,14 @@ OPTIONS
        failure usually indicates conflicts during the merge). This is for
        porcelains which might want to emit custom messages.
 
-If 'git-merge-index' is called with multiple <file>s (or -a) then it
+If 'git merge-index' is called with multiple <file>s (or -a) then it
 processes them in turn only stopping if merge returns a non-zero exit
 code.
 
 Typically this is run with a script calling git's imitation of
 the 'merge' command from the RCS package.
 
-A sample script called 'git-merge-one-file' is included in the
+A sample script called 'git merge-one-file' is included in the
 distribution.
 
 ALERT ALERT ALERT! The git "merge object order" is different from the
@@ -68,10 +68,10 @@ or
   This is added AA in the branch B.
   fatal: merge program failed
 
-where the latter example shows how 'git-merge-index' will stop trying to
+where the latter example shows how 'git merge-index' will stop trying to
 merge once anything has returned an error (i.e., `cat` returned an error
 for the AA file, because it didn't exist in the original, and thus
-'git-merge-index' didn't even try to merge the MM thing).
+'git merge-index' didn't even try to merge the MM thing).
 
 Author
 ------
index dc8a96adb00c0b674e12e071a4a56f89bfe8583d..a163cfca6993b61b3f3d198bd0980bd1fed37e52 100644 (file)
@@ -8,12 +8,12 @@ git-merge-one-file - The standard helper program to use with git-merge-index
 
 SYNOPSIS
 --------
-'git-merge-one-file'
+'git merge-one-file'
 
 DESCRIPTION
 -----------
-This is the standard helper program to use with 'git-merge-index'
-to resolve a merge after the trivial merge done with 'git-read-tree -m'.
+This is the standard helper program to use with 'git merge-index'
+to resolve a merge after the trivial merge done with 'git read-tree -m'.
 
 Author
 ------
index e886c2ef543501deea84909b2e88fb163a1c9d2b..9c9618cead5ae73a754ce741dfd423a7bd2298ca 100644 (file)
@@ -10,17 +10,45 @@ SYNOPSIS
 --------
 [verse]
 'git merge' [-n] [--stat] [--no-commit] [--squash] [-s <strategy>]...
-       [-m <msg>] <remote>...
-'git merge' <msg> HEAD <remote>...
+       [--[no-]rerere-autoupdate] [-m <msg>] <commit>...
+'git merge' <msg> HEAD <commit>...
 
 DESCRIPTION
 -----------
-This is the top-level interface to the merge machinery
-which drives multiple merge strategy scripts.
+Incorporates changes from the named commits (since the time their
+histories diverged from the current branch) into the current
+branch.  This command is used by 'git pull' to incorporate changes
+from another repository and can be used by hand to merge changes
+from one branch into another.
 
-The second syntax (<msg> `HEAD` <remote>) is supported for
+Assume the following history exists and the current branch is
+"`master`":
+
+------------
+         A---B---C topic
+        /
+    D---E---F---G master
+------------
+
+Then "`git merge topic`" will replay the changes made on the
+`topic` branch since it diverged from `master` (i.e., `E`) until
+its current commit (`C`) on top of `master`, and record the result
+in a new commit along with the names of the two parent commits and
+a log message from the user describing the changes.
+
+------------
+         A---B---C topic
+        /         \
+    D---E---F---G---H master
+------------
+
+The second syntax (<msg> `HEAD` <commit>...) is supported for
 historical reasons.  Do not use it from the command line or in
-new scripts.  It is the same as `git merge -m <msg> <remote>`.
+new scripts.  It is the same as `git merge -m <msg> <commit>...`.
+
+*Warning*: Running 'git merge' with uncommitted changes is
+discouraged: while possible, it leaves you in a state that is hard to
+back out of in the case of a conflict.
 
 
 OPTIONS
@@ -33,93 +61,83 @@ include::merge-options.txt[]
        used to give a good default for automated 'git merge'
        invocations.
 
-<remote>...::
-       Other branch heads to merge into our branch.  You need at
-       least one <remote>.  Specifying more than one <remote>
-       obviously means you are trying an Octopus.
+--rerere-autoupdate::
+--no-rerere-autoupdate::
+       Allow the rerere mechanism to update the index with the
+       result of auto-conflict resolution if possible.
 
-include::merge-strategies.txt[]
+<commit>...::
+       Commits, usually other branch heads, to merge into our branch.
+       You need at least one <commit>.  Specifying more than one
+       <commit> obviously means you are trying an Octopus.
 
 
-If you tried a merge which resulted in complex conflicts and
-want to start over, you can recover with 'git-reset'.
+PRE-MERGE CHECKS
+----------------
 
-CONFIGURATION
--------------
-include::merge-config.txt[]
+Before applying outside changes, you should get your own work in
+good shape and committed locally, so it will not be clobbered if
+there are conflicts.  See also linkgit:git-stash[1].
+'git pull' and 'git merge' will stop without doing anything when
+local uncommitted changes overlap with files that 'git pull'/'git
+merge' may need to update.
 
-branch.<name>.mergeoptions::
-       Sets default options for merging into branch <name>. The syntax and
-       supported options are the same as those of 'git merge', but option
-       values containing whitespace characters are currently not supported.
+To avoid recording unrelated changes in the merge commit,
+'git pull' and 'git merge' will also abort if there are any changes
+registered in the index relative to the `HEAD` commit.  (One
+exception is when the changed index entries are in the state that
+would result from the merge already.)
 
-HOW MERGE WORKS
----------------
-
-A merge is always between the current `HEAD` and one or more
-commits (usually, branch head or tag), and the index file must
-match the tree of `HEAD` commit (i.e. the contents of the last commit)
-when it starts out.  In other words, `git diff --cached HEAD` must
-report no changes.  (One exception is when the changed index
-entries are already in the same state that would result from
-the merge anyway.)
-
-Three kinds of merge can happen:
-
-* The merged commit is already contained in `HEAD`. This is the
-  simplest case, called "Already up-to-date."
-
-* `HEAD` is already contained in the merged commit. This is the
-  most common case especially when invoked from 'git pull':
-  you are tracking an upstream repository, have committed no local
-  changes and now you want to update to a newer upstream revision.
-  Your `HEAD` (and the index) is updated to point at the merged
-  commit, without creating an extra merge commit.  This is
-  called "Fast-forward".
-
-* Both the merged commit and `HEAD` are independent and must be
-  tied together by a merge commit that has both of them as its parents.
-  The rest of this section describes this "True merge" case.
-
-The chosen merge strategy merges the two commits into a single
-new source tree.
-When things merge cleanly, this is what happens:
-
-1. The results are updated both in the index file and in your
-   working tree;
-2. Index file is written out as a tree;
-3. The tree gets committed; and
-4. The `HEAD` pointer gets advanced.
-
-Because of 2., we require that the original state of the index
-file matches exactly the current `HEAD` commit; otherwise we
-will write out your local changes already registered in your
-index file along with the merge result, which is not good.
-Because 1. involves only those paths differing between your
-branch and the remote branch you are pulling from during the
-merge (which is typically a fraction of the whole tree), you can
-have local modifications in your working tree as long as they do
-not overlap with what the merge updates.
-
-When there are conflicts, the following happens:
-
-1. `HEAD` stays the same.
-
-2. Cleanly merged paths are updated both in the index file and
-   in your working tree.
+If all named commits are already ancestors of `HEAD`, 'git merge'
+will exit early with the message "Already up-to-date."
+
+FAST-FORWARD MERGE
+------------------
+
+Often the current branch head is an ancestor of the named commit.
+This is the most common case especially when invoked from 'git
+pull': you are tracking an upstream repository, you have committed
+no local changes, and now you want to update to a newer upstream
+revision.  In this case, a new commit is not needed to store the
+combined history; instead, the `HEAD` (along with the index) is
+updated to point at the named commit, without creating an extra
+merge commit.
 
-3. For conflicting paths, the index file records up to three
-   versions; stage1 stores the version from the common ancestor,
-   stage2 from `HEAD`, and stage3 from the remote branch (you
+This behavior can be suppressed with the `--no-ff` option.
+
+TRUE MERGE
+----------
+
+Except in a fast-forward merge (see above), the branches to be
+merged must be tied together by a merge commit that has both of them
+as its parents.
+
+A merged version reconciling the changes from all branches to be
+merged is committed, and your `HEAD`, index, and working tree are
+updated to it.  It is possible to have modifications in the working
+tree as long as they do not overlap; the update will preserve them.
+
+When it is not obvious how to reconcile the changes, the following
+happens:
+
+1. The `HEAD` pointer stays the same.
+2. The `MERGE_HEAD` ref is set to point to the other branch head.
+3. Paths that merged cleanly are updated both in the index file and
+   in your working tree.
+4. For conflicting paths, the index file records up to three
+   versions: stage 1 stores the version from the common ancestor,
+   stage 2 from `HEAD`, and stage 3 from `MERGE_HEAD` (you
    can inspect the stages with `git ls-files -u`).  The working
    tree files contain the result of the "merge" program; i.e. 3-way
-   merge results with familiar conflict markers `<<< === >>>`.
-
-4. No other changes are done.  In particular, the local
+   merge results with familiar conflict markers `<<<` `===` `>>>`.
+5. No other changes are made.  In particular, the local
    modifications you had before you started merge will stay the
    same and the index entries for them stay as they were,
    i.e. matching `HEAD`.
 
+If you tried a merge which resulted in complex conflicts and
+want to start over, you can recover with `git reset --merge`.
+
 HOW CONFLICTS ARE PRESENTED
 ---------------------------
 
@@ -189,28 +207,30 @@ After seeing a conflict, you can do two things:
 
  * Decide not to merge.  The only clean-ups you need are to reset
    the index file to the `HEAD` commit to reverse 2. and to clean
-   up working tree changes made by 2. and 3.; 'git-reset --hard' can
+   up working tree changes made by 2. and 3.; `git-reset --hard` can
    be used for this.
 
  * Resolve the conflicts.  Git will mark the conflicts in
    the working tree.  Edit the files into shape and
-   'git-add' them to the index.  Use 'git-commit' to seal the deal.
+   'git add' them to the index.  Use 'git commit' to seal the deal.
 
 You can work through the conflict with a number of tools:
 
- * Use a mergetool.  'git mergetool' to launch a graphical
+ * Use a mergetool.  `git mergetool` to launch a graphical
    mergetool which will work you through the merge.
 
- * Look at the diffs.  'git diff' will show a three-way diff,
-   highlighting changes from both the HEAD and remote versions.
+ * Look at the diffs.  `git diff` will show a three-way diff,
+   highlighting changes from both the `HEAD` and `MERGE_HEAD`
+   versions.
 
- * Look at the diffs on their own. 'git log --merge -p <path>'
-   will show diffs first for the HEAD version and then the
-   remote version.
+ * Look at the diffs from each branch. `git log --merge -p <path>`
+   will show diffs first for the `HEAD` version and then the
+   `MERGE_HEAD` version.
 
- * Look at the originals.  'git show :1:filename' shows the
-   common ancestor, 'git show :2:filename' shows the HEAD
-   version and 'git show :3:filename' shows the remote version.
+ * Look at the originals.  `git show :1:filename` shows the
+   common ancestor, `git show :2:filename` shows the `HEAD`
+   version, and `git show :3:filename` shows the `MERGE_HEAD`
+   version.
 
 
 EXAMPLES
@@ -245,6 +265,17 @@ changes into a merge commit.  Small fixups like bumping
 release/version name would be acceptable.
 
 
+include::merge-strategies.txt[]
+
+CONFIGURATION
+-------------
+include::merge-config.txt[]
+
+branch.<name>.mergeoptions::
+       Sets default options for merging into branch <name>. The syntax and
+       supported options are the same as those of 'git merge', but option
+       values containing whitespace characters are currently not supported.
+
 SEE ALSO
 --------
 linkgit:git-fmt-merge-msg[1], linkgit:git-pull[1],
index 4a6f7f3a2d189c40e4abfe4f74178f79d1c563fd..55735faf7b58dfa56f7a7b93b5e0d7f19b98a808 100644 (file)
@@ -13,11 +13,11 @@ DESCRIPTION
 -----------
 
 Use `git mergetool` to run one of several merge utilities to resolve
-merge conflicts.  It is typically run after 'git-merge'.
+merge conflicts.  It is typically run after 'git merge'.
 
 If one or more <file> parameters are given, the merge tool program will
 be run to resolve differences on each file.  If no <file> names are
-specified, 'git-mergetool' will run the merge tool program on every file
+specified, 'git mergetool' will run the merge tool program on every file
 with merge conflicts.
 
 OPTIONS
@@ -29,23 +29,23 @@ OPTIONS
        kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge,
        diffuse, tortoisemerge, opendiff, p4merge and araxis.
 +
-If a merge resolution program is not specified, 'git-mergetool'
+If a merge resolution program is not specified, 'git mergetool'
 will use the configuration variable `merge.tool`.  If the
-configuration variable `merge.tool` is not set, 'git-mergetool'
+configuration variable `merge.tool` is not set, 'git mergetool'
 will pick a suitable default.
 +
 You can explicitly provide a full path to the tool by setting the
 configuration variable `mergetool.<tool>.path`. For example, you
 can configure the absolute path to kdiff3 by setting
-`mergetool.kdiff3.path`. Otherwise, 'git-mergetool' assumes the
+`mergetool.kdiff3.path`. Otherwise, 'git mergetool' assumes the
 tool is available in PATH.
 +
 Instead of running one of the known merge tool programs,
-'git-mergetool' can be customized to run an alternative program
+'git mergetool' can be customized to run an alternative program
 by specifying the command line to invoke in a configuration
 variable `mergetool.<tool>.cmd`.
 +
-When 'git-mergetool' is invoked with this tool (either through the
+When 'git mergetool' is invoked with this tool (either through the
 `-t` or `--tool` option or the `merge.tool` configuration
 variable) the configured command line will be invoked with `$BASE`
 set to the name of a temporary file containing the common base for
@@ -59,7 +59,7 @@ merge resolution.
 If the custom merge tool correctly indicates the success of a
 merge resolution with its exit code, then the configuration
 variable `mergetool.<tool>.trustExitCode` can be set to `true`.
-Otherwise, 'git-mergetool' will prompt the user to indicate the
+Otherwise, 'git mergetool' will prompt the user to indicate the
 success of the resolution after the custom tool has exited.
 
 -y::
index 7ca8a7b48cea191b58db93581f02fa4ff265acdc..2108237c36cead52b14cd9a593c1398ea717a231 100644 (file)
@@ -15,7 +15,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Finds symbolic names suitable for human digestion for revisions given in any
-format parsable by 'git-rev-parse'.
+format parsable by 'git rev-parse'.
 
 
 OPTIONS
@@ -55,7 +55,7 @@ wrote you about that fantastic commit 33db5f4d9027a10e477ccf054b2c1ab94f74c85a.
 Of course, you look into the commit, but that only tells you what happened, but
 not the context.
 
-Enter 'git-name-rev':
+Enter 'git name-rev':
 
 ------------
 % git name-rev 33db5f4d9027a10e477ccf054b2c1ab94f74c85a
index 94cceb13193795ff4fcd2b79f7fd8241b8d393ee..d4487cab5284670644f88d95680581ffd29ae952 100644 (file)
@@ -8,7 +8,7 @@ git-notes - Add/inspect commit notes
 SYNOPSIS
 --------
 [verse]
-'git-notes' (edit [-F <file> | -m <msg>] | show) [commit]
+'git notes' (edit [-F <file> | -m <msg>] | show) [commit]
 
 DESCRIPTION
 -----------
index f54d433d36b50184bd83f217c310935d1bbb541f..ffd5025f7bdf68b8285f2c98c2c1b37c551e2cb4 100644 (file)
@@ -32,7 +32,7 @@ Placing both in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or
 any of the directories on $GIT_ALTERNATE_OBJECT_DIRECTORIES)
 enables git to read from such an archive.
 
-The 'git-unpack-objects' command can read the packed archive and
+The 'git unpack-objects' command can read the packed archive and
 expand the objects contained in the pack into "one-file
 one-object" format; this is typically done by the smart-pull
 commands when a pack is created on-the-fly for efficient network
@@ -61,7 +61,7 @@ base-name::
 --revs::
        Read the revision arguments from the standard input, instead of
        individual object names.  The revision arguments are processed
-       the same way as 'git-rev-list' with the `--objects` flag
+       the same way as 'git rev-list' with the `--objects` flag
        uses its `commit` arguments to build the list of objects it
        outputs.  The objects on the resulting list are packed.
 
@@ -105,8 +105,9 @@ base-name::
        `--window-memory=0` makes memory usage unlimited, which is the
        default.
 
---max-pack-size=<n>::
-       Maximum size of each output packfile, expressed in MiB.
+--max-pack-size=[N]::
+       Maximum size of each output pack file. The size can be suffixed with
+       "k", "m", or "g". The minimum size allowed is limited to 1 MiB.
        If specified,  multiple packfiles may be created.
        The default is unlimited, unless the config variable
        `pack.packSizeLimit` is set.
@@ -182,7 +183,7 @@ base-name::
        A packed archive can express base object of a delta as
        either 20-byte object name or as an offset in the
        stream, but older version of git does not understand the
-       latter.  By default, 'git-pack-objects' only uses the
+       latter.  By default, 'git pack-objects' only uses the
        former format for better compatibility.  This option
        allows the command to use the latter format for
        compactness.  Depending on the average delta chain
index 5f9435e59b49fec1e37c65f1bfdc38be3704c4e5..d0607879db7ffc125e3a3aad2046b5e77730dcd6 100644 (file)
@@ -16,7 +16,7 @@ This program computes which packs in your repository
 are redundant. The output is suitable for piping to
 `xargs rm` if you are in the root of the repository.
 
-'git-pack-redundant' accepts a list of objects on standard input. Any objects
+'git pack-redundant' accepts a list of objects on standard input. Any objects
 given will be ignored when checking which packs are required. This makes the
 following command useful when wanting to remove packs which contain unreachable
 objects.
index 253fc0fc255a0f82778d120647031eaa66eb647a..4dae1390a5c663867b93e1dce6cd4d2bf59e84ab 100644 (file)
@@ -18,7 +18,7 @@ ID" are almost guaranteed to be the same thing.
 
 IOW, you can use this thing to look for likely duplicate commits.
 
-When dealing with 'git-diff-tree' output, it takes advantage of
+When dealing with 'git diff-tree' output, it takes advantage of
 the fact that the patch is prefixed with the object name of the
 commit, and outputs two 40-byte hexadecimal strings.  The first
 string is the patch ID, and the second string is the commit ID.
index 8282a5e82b6e897ac501ef05c982d5e69415363f..87dacd797f6e36d47024bc0d2ebc82bb0b5bb387 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-This command is deprecated; use 'git-ls-remote' instead.
+This command is deprecated; use 'git ls-remote' instead.
 
 OPTIONS
 -------
index da6055d4b8cf78aff16fa553e684b0b3ed57138e..3bb7304517ea3adfd2b5b89b5e75f9d830e57aff 100644 (file)
@@ -8,21 +8,21 @@ git-prune - Prune all unreachable objects from the object database
 
 SYNOPSIS
 --------
-'git-prune' [-n] [-v] [--expire <expire>] [--] [<head>...]
+'git prune' [-n] [-v] [--expire <expire>] [--] [<head>...]
 
 DESCRIPTION
 -----------
 
-NOTE: In most cases, users should run 'git-gc', which calls
-'git-prune'. See the section "NOTES", below.
+NOTE: In most cases, users should run 'git gc', which calls
+'git prune'. See the section "NOTES", below.
 
-This runs 'git-fsck --unreachable' using all the refs
+This runs 'git fsck --unreachable' using all the refs
 available in `$GIT_DIR/refs`, optionally with additional set of
 objects specified on the command line, and prunes all unpacked
 objects unreachable from any of these head objects from the object database.
 In addition, it
 prunes the unpacked objects that are also found in packs by
-running 'git-prune-packed'.
+running 'git prune-packed'.
 
 Note that unreachable, packed objects will remain.  If this is
 not desired, see linkgit:git-repack[1].
@@ -62,12 +62,12 @@ $ git prune $(cd ../another && $(git rev-parse --all))
 Notes
 -----
 
-In most cases, users will not need to call 'git-prune' directly, but
-should instead call 'git-gc', which handles pruning along with
+In most cases, users will not need to call 'git prune' directly, but
+should instead call 'git gc', which handles pruning along with
 many other housekeeping tasks.
 
 For a description of which objects are considered for pruning, see
-'git-fsck''s --unreachable option.
+'git fsck''s --unreachable option.
 
 SEE ALSO
 --------
index b93201158fa6fda914b6093af5911b7884328838..31f42ea21a249abfa1ab2e220a077fee30d3d5e4 100644 (file)
@@ -13,16 +13,20 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Runs 'git-fetch' with the given parameters, and calls 'git-merge'
+Runs 'git fetch' with the given parameters, and calls 'git merge'
 to merge the retrieved head(s) into the current branch.
-With `--rebase`, calls 'git-rebase' instead of 'git-merge'.
+With `--rebase`, calls 'git rebase' instead of 'git merge'.
 
 Note that you can use `.` (current directory) as the
 <repository> to pull from the local repository -- this is useful
 when merging local branches into the current branch.
 
-Also note that options meant for 'git-pull' itself and underlying
-'git-merge' must be given before the options meant for 'git-fetch'.
+Also note that options meant for 'git pull' itself and underlying
+'git merge' must be given before the options meant for 'git fetch'.
+
+*Warning*: Running 'git pull' (actually, the underlying 'git merge')
+with uncommitted changes is discouraged: while possible, it leaves you
+in a state that is hard to back out of in the case of a conflict.
 
 OPTIONS
 -------
@@ -148,7 +152,7 @@ $ git merge origin/next
 
 
 If you tried a pull which resulted in a complex conflicts and
-would want to start over, you can recover with 'git-reset'.
+would want to start over, you can recover with 'git reset'.
 
 
 SEE ALSO
index 52c0538df528ecbb5b755a2e75fc1715762bfc00..73a921ca0f224506a0a5587205495c352909db53 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
-          [--repo=<repository>] [-f | --force] [-v | --verbose]
+          [--repo=<repository>] [-f | --force] [-v | --verbose] [-u | --set-upstream]
           [<repository> <refspec>...]
 
 DESCRIPTION
@@ -91,6 +91,10 @@ nor in any Push line of the corresponding remotes file---see below).
        will be tab-separated and sent to stdout instead of stderr.  The full
        symbolic names of the refs will be given.
 
+--delete::
+       All listed refs are deleted from the remote repository. This is
+       the same as prefixing all refs with a colon.
+
 --tags::
        All refs under `$GIT_DIR/refs/tags` are pushed, in
        addition to refspecs explicitly listed on the command
@@ -112,7 +116,7 @@ nor in any Push line of the corresponding remotes file---see below).
 
 --repo=<repository>::
        This option is only relevant if no <repository> argument is
-       passed in the invocation. In this case, 'git-push' derives the
+       passed in the invocation. In this case, 'git push' derives the
        remote name from the current branch: If it tracks a remote
        branch, then that remote repository is pushed to. Otherwise,
        the name "origin" is used. For this latter case, this option
@@ -126,11 +130,18 @@ git push --repo=public  #2
 +
 is that #1 always pushes to "public" whereas #2 pushes to "public"
 only if the current branch does not track a remote branch. This is
-useful if you write an alias or script around 'git-push'.
+useful if you write an alias or script around 'git push'.
+
+-u::
+--set-upstream::
+       For every branch that is up to date or successfully pushed, add
+       upstream (tracking) reference, used by argument-less
+       linkgit:git-pull[1] and other commands. For more information,
+       see 'branch.<name>.merge' in linkgit:git-config[1].
 
 --thin::
 --no-thin::
-       These options are passed to 'git-send-pack'.  Thin
+       These options are passed to 'git send-pack'.  Thin
        transfer spends extra cycles to minimize the number of
        objects to be sent and meant to be used on slower connection.
 
index a10ce4ba40cc509a0f3a8f39551310fdd9231e71..567671c013589d10b04b2239062dec3b1f6cd787 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]
                [-u [--exclude-per-directory=<gitignore>] | -i]]
-               [--index-output=<file>]
+               [--index-output=<file>] [--no-sparse-checkout]
                <tree-ish1> [<tree-ish2> [<tree-ish3>]]
 
 
@@ -25,8 +25,8 @@ fast-forward (i.e. 2-way) merge, or a 3-way merge, with the `-m`
 flag.  When used with `-m`, the `-u` flag causes it to also update
 the files in the work tree with the result of the merge.
 
-Trivial merges are done by 'git-read-tree' itself.  Only conflicting paths
-will be in unmerged state when 'git-read-tree' returns.
+Trivial merges are done by 'git read-tree' itself.  Only conflicting paths
+will be in unmerged state when 'git read-tree' returns.
 
 OPTIONS
 -------
@@ -57,13 +57,13 @@ OPTIONS
        Show the progress of checking files out.
 
 --trivial::
-       Restrict three-way merge by 'git-read-tree' to happen
+       Restrict three-way merge by 'git read-tree' to happen
        only if there is no file-level merging required, instead
        of resolving merge for trivial cases and leaving
        conflicting files unresolved in the index.
 
 --aggressive::
-       Usually a three-way merge by 'git-read-tree' resolves
+       Usually a three-way merge by 'git read-tree' resolves
        the merge for really trivial cases and leaves other
        cases unresolved in the index, so that Porcelains can
        implement different merge policies.  This flag makes the
@@ -110,13 +110,17 @@ OPTIONS
        directories the index file and index output file are
        located in.
 
+--no-sparse-checkout::
+       Disable sparse checkout support even if `core.sparseCheckout`
+       is true.
+
 <tree-ish#>::
        The id of the tree object(s) to be read/merged.
 
 
 Merging
 -------
-If `-m` is specified, 'git-read-tree' can perform 3 kinds of
+If `-m` is specified, 'git read-tree' can perform 3 kinds of
 merge, a single tree merge if only 1 tree is given, a
 fast-forward merge with 2 trees, or a 3-way merge if 3 trees are
 provided.
@@ -124,18 +128,18 @@ provided.
 
 Single Tree Merge
 ~~~~~~~~~~~~~~~~~
-If only 1 tree is specified, 'git-read-tree' operates as if the user did not
+If only 1 tree is specified, 'git read-tree' operates as if the user did not
 specify `-m`, except that if the original index has an entry for a
 given pathname, and the contents of the path matches with the tree
 being read, the stat info from the index is used. (In other words, the
 index's stat()s take precedence over the merged tree's).
 
 That means that if you do a `git read-tree -m <newtree>` followed by a
-`git checkout-index -f -u -a`, the 'git-checkout-index' only checks out
+`git checkout-index -f -u -a`, the 'git checkout-index' only checks out
 the stuff that really changed.
 
-This is used to avoid unnecessary false hits when 'git-diff-files' is
-run after 'git-read-tree'.
+This is used to avoid unnecessary false hits when 'git diff-files' is
+run after 'git read-tree'.
 
 
 Two Tree Merge
@@ -146,7 +150,7 @@ 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).
 
-When two trees are specified, the user is telling 'git-read-tree'
+When two trees are specified, the user is telling 'git read-tree'
 the following:
 
      1. The current index and work tree is derived from $H, but
@@ -199,10 +203,10 @@ Here are the "carry forward" rules:
 
 In all "keep index" cases, the index entry stays as in the
 original index file.  If the entry were not up to date,
-'git-read-tree' keeps the copy in the work tree intact when
+'git read-tree' keeps the copy in the work tree intact when
 operating under the -u flag.
 
-When this form of 'git-read-tree' returns successfully, you can
+When this form of 'git read-tree' returns successfully, you can
 see what "local changes" you made are carried forward by running
 `git diff-index --cached $M`.  Note that this does not
 necessarily match `git diff-index --cached $H` would have
@@ -225,7 +229,7 @@ of the path is kept as long as $H and $M are the same.
 Each "index" entry has two bits worth of "stage" state. stage 0 is the
 normal one, and is the only one you'd see in any kind of normal use.
 
-However, when you do 'git-read-tree' with three trees, the "stage"
+However, when you do 'git read-tree' with three trees, the "stage"
 starts out at 1.
 
 This means that you can do
@@ -241,7 +245,7 @@ branch into the current branch, we use the common ancestor tree
 as <tree1>, the current branch head as <tree2>, and the other
 branch head as <tree3>.
 
-Furthermore, 'git-read-tree' has special-case logic that says: if you see
+Furthermore, 'git read-tree' has special-case logic that says: if you see
 a file that matches in all respects in the following states, it
 "collapses" back to "stage0":
 
@@ -257,7 +261,7 @@ a file that matches in all respects in the following states, it
    - stage 1 and stage 3 are the same and stage 2 is different take
      stage 2 (we did something while they did nothing)
 
-The 'git-write-tree' command refuses to write a nonsensical tree, and it
+The 'git write-tree' command refuses to write a nonsensical tree, and it
 will complain about unmerged entries if it sees a single entry that is not
 stage 0.
 
@@ -273,7 +277,7 @@ start a 3-way merge with an index file that is already
 populated.  Here is an outline of how the algorithm works:
 
 - if a file exists in identical format in all three trees, it will
-  automatically collapse to "merged" state by 'git-read-tree'.
+  automatically collapse to "merged" state by 'git read-tree'.
 
 - a file that has _any_ difference what-so-ever in the three trees
   will stay as separate entries in the index. It's up to "porcelain
@@ -297,8 +301,8 @@ populated.  Here is an outline of how the algorithm works:
     matching "stage1" entry if it exists too.  .. all the normal
     trivial rules ..
 
-You would normally use 'git-merge-index' with supplied
-'git-merge-one-file' to do this last step.  The script updates
+You would normally use 'git merge-index' with supplied
+'git merge-one-file' to do this last step.  The script updates
 the files in the working tree as it merges each path and at the
 end of a successful merge.
 
@@ -320,7 +324,7 @@ $ JC=`git rev-parse --verify "HEAD^0"`
 $ git checkout-index -f -u -a $JC
 ----------------
 
-You do random edits, without running 'git-update-index'.  And then
+You do random edits, without running 'git update-index'.  And then
 you notice that the tip of your "upstream" tree has advanced
 since you pulled from him:
 
@@ -346,20 +350,66 @@ your work-in-progress changes, and your work tree would be
 updated to the result of the merge.
 
 However, if you have local changes in the working tree that
-would be overwritten by this merge, 'git-read-tree' will refuse
+would be overwritten by this merge, 'git read-tree' will refuse
 to run to prevent your changes from being lost.
 
 In other words, there is no need to worry about what exists only
 in the working tree.  When you have local changes in a part of
 the project that is not involved in the merge, your changes do
 not interfere with the merge, and are kept intact.  When they
-*do* interfere, the merge does not even start ('git-read-tree'
+*do* interfere, the merge does not even start ('git read-tree'
 complains loudly and fails without modifying anything).  In such
 a case, you can simply continue doing what you were in the
 middle of doing, and when your working tree is ready (i.e. you
 have finished your work-in-progress), attempt the merge again.
 
 
+Sparse checkout
+---------------
+
+"Sparse checkout" allows to sparsely populate working directory.
+It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell
+Git whether a file on working directory is worth looking at.
+
+"git read-tree" and other merge-based commands ("git merge", "git
+checkout"...) can help maintaining skip-worktree bitmap and working
+directory update. `$GIT_DIR/info/sparse-checkout` is used to
+define the skip-worktree reference bitmap. When "git read-tree" needs
+to update working directory, it will reset skip-worktree bit in index
+based on this file, which uses the same syntax as .gitignore files.
+If an entry matches a pattern in this file, skip-worktree will be
+set on that entry. Otherwise, skip-worktree will be unset.
+
+Then it compares the new skip-worktree value with the previous one. If
+skip-worktree turns from unset to set, it will add the corresponding
+file back. If it turns from set to unset, that file will be removed.
+
+While `$GIT_DIR/info/sparse-checkout` is usually used to specify what
+files are in. You can also specify what files are _not_ in, using
+negate patterns. For example, to remove file "unwanted":
+
+----------------
+*
+!unwanted
+----------------
+
+Another tricky thing is fully repopulating working directory when you
+no longer want sparse checkout. You cannot just disable "sparse
+checkout" because skip-worktree are still in the index and you working
+directory is still sparsely populated. You should re-populate working
+directory with the `$GIT_DIR/info/sparse-checkout` file content as
+follows:
+
+----------------
+*
+----------------
+
+Then you can disable sparse checkout. Sparse checkout support in "git
+read-tree" and similar commands is disabled by default. You need to
+turn `core.sparseCheckout` on in order to have sparse checkout
+support.
+
+
 SEE ALSO
 --------
 linkgit:git-write-tree[1]; linkgit:git-ls-files[1];
index ca5e1e8653be7a1d2b0911ec572ff0a85300ebb9..823f2a4638c5b53671e294faf7a99a56d17c897a 100644 (file)
@@ -17,7 +17,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-If <branch> is specified, 'git-rebase' will perform an automatic
+If <branch> is specified, 'git rebase' will perform an automatic
 `git checkout <branch>` before doing anything else.  Otherwise
 it remains on the current branch.
 
@@ -170,8 +170,8 @@ This is useful if F and G were flawed in some way, or should not be
 part of topicA.  Note that the argument to --onto and the <upstream>
 parameter can be any valid commit-ish.
 
-In case of conflict, 'git-rebase' will stop at the first problematic commit
-and leave conflict markers in the tree.  You can use 'git-diff' to locate
+In case of conflict, 'git rebase' will stop at the first problematic commit
+and leave conflict markers in the tree.  You can use 'git diff' to locate
 the markers (<<<<<<) and make edits to resolve the conflict.  For each
 file you edit, you need to tell git that the conflict has been resolved,
 typically this would be done with
@@ -187,7 +187,7 @@ desired resolution, you can continue the rebasing process with
     git rebase --continue
 
 
-Alternatively, you can undo the 'git-rebase' with
+Alternatively, you can undo the 'git rebase' with
 
 
     git rebase --abort
@@ -238,10 +238,10 @@ other words, the sides are swapped.
 -s <strategy>::
 --strategy=<strategy>::
        Use the given merge strategy.
-       If there is no `-s` option 'git-merge-recursive' is used
+       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
+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.
@@ -280,13 +280,13 @@ which makes little sense.
 
 --ignore-whitespace::
 --whitespace=<option>::
-       These flag are passed to the 'git-apply' program
+       These flag are passed to the 'git apply' program
        (see linkgit:git-apply[1]) that applies the patch.
        Incompatible with the --interactive option.
 
 --committer-date-is-author-date::
 --ignore-date::
-       These flags are passed to 'git-am' to easily change the dates
+       These flags are passed to 'git am' to easily change the dates
        of the rebased commits (see linkgit:git-am[1]).
 
 -i::
@@ -308,12 +308,22 @@ which makes little sense.
        root commits will be rewritten to have <newbase> as parent
        instead.
 
+--autosquash::
+       When the commit log message begins with "squash! ..." (or
+       "fixup! ..."), and there is a commit whose title begins with
+       the same ..., automatically modify the todo list of rebase -i
+       so that the commit marked for squashing comes right after the
+       commit to be modified, and change the action of the moved
+       commit from `pick` to `squash` (or `fixup`).
++
+This option is only valid when '--interactive' option is used.
+
 include::merge-strategies.txt[]
 
 NOTES
 -----
 
-You should understand the implications of using 'git-rebase' on a
+You should understand the implications of using 'git rebase' on a
 repository that you share.  See also RECOVERING FROM UPSTREAM REBASE
 below.
 
@@ -369,12 +379,12 @@ pick fa1afe1 The oneline of the next commit
 ...
 -------------------------------------------
 
-The oneline descriptions are purely for your pleasure; 'git-rebase' will
+The oneline descriptions are purely for your pleasure; 'git rebase' will
 not look at them but at the commit names ("deadbee" and "fa1afe1" in this
 example), so do not delete or edit the names.
 
 By replacing the command "pick" with the command "edit", you can tell
-'git-rebase' to stop after applying that commit, so that you can edit
+'git rebase' to stop after applying that commit, so that you can edit
 the files and/or the commit message, amend the commit, and continue
 rebasing.
 
@@ -382,17 +392,20 @@ If you just want to edit the commit message for a commit, replace the
 command "pick" with the command "reword".
 
 If you want to fold two or more commits into one, replace the command
-"pick" with "squash" for the second and subsequent commit.  If the
-commits had different authors, it will attribute the squashed commit to
-the author of the first commit.
-
-'git-rebase' will stop when "pick" has been replaced with "edit" or
+"pick" for the second and subsequent commits with "squash" or "fixup".
+If the commits had different authors, the folded commit will be
+attributed to the author of the first commit.  The suggested commit
+message for the folded commit is the concatenation of the commit
+messages of the first commit and of those with the "squash" command,
+but omits the commit messages of commits with the "fixup" command.
+
+'git rebase' will stop when "pick" has been replaced with "edit" or
 when a command fails due to merge errors. When you are done editing
 and/or resolving conflicts you can continue with `git rebase --continue`.
 
 For example, if you want to reorder the last 5 commits, such that what
 was HEAD~4 becomes the new HEAD. To achieve that, you would call
-'git-rebase' like this:
+'git rebase' like this:
 
 ----------------------
 $ git rebase -i HEAD~5
@@ -422,7 +435,7 @@ SPLITTING COMMITS
 -----------------
 
 In interactive mode, you can mark commits with the action "edit".  However,
-this does not necessarily mean that 'git-rebase' expects the result of this
+this does not necessarily mean that 'git rebase' expects the result of this
 edit to be exactly one commit.  Indeed, you can undo the commit, or you can
 add other commits.  This can be used to split a commit into two:
 
@@ -438,7 +451,7 @@ add other commits.  This can be used to split a commit into two:
 
 - Now add the changes to the index that you want to have in the first
   commit.  You can use `git add` (possibly interactively) or
-  'git-gui' (or both) to do that.
+  'git gui' (or both) to do that.
 
 - Commit the now-current index with whatever commit message is appropriate
   now.
@@ -449,7 +462,7 @@ add other commits.  This can be used to split a commit into two:
 
 If you are not absolutely sure that the intermediate revisions are
 consistent (they compile, pass the testsuite, etc.) you should use
-'git-stash' to stash away the not-yet-committed changes
+'git stash' to stash away the not-yet-committed changes
 after each commit, test, and amend the commit if fixes are necessary.
 
 
@@ -512,8 +525,8 @@ Easy case: The changes are literally the same.::
 Hard case: The changes are not the same.::
 
        This happens if the 'subsystem' rebase had conflicts, or used
-       `\--interactive` to omit, edit, or squash commits; or if the
-       upstream used one of `commit \--amend`, `reset`, or
+       `\--interactive` to omit, edit, squash, or fixup commits; or
+       if the upstream used one of `commit \--amend`, `reset`, or
        `filter-branch`.
 
 
@@ -524,7 +537,7 @@ Only works if the changes (patch IDs based on the diff contents) on
 'subsystem' are literally the same before and after the rebase
 'subsystem' did.
 
-In that case, the fix is easy because 'git-rebase' knows to skip
+In that case, the fix is easy because 'git rebase' knows to skip
 changes that are already present in the new upstream.  So if you say
 (assuming you're on 'topic')
 ------------
@@ -551,12 +564,12 @@ NOTE: While an "easy case recovery" sometimes appears to be successful
       example, a commit that was removed via `git rebase
       \--interactive` will be **resurrected**!
 
-The idea is to manually tell 'git-rebase' "where the old 'subsystem'
+The idea is to manually tell 'git rebase' "where the old 'subsystem'
 ended and your 'topic' began", that is, what the old merge-base
 between them was.  You will have to find a way to name the last commit
 of the old 'subsystem', for example:
 
-* With the 'subsystem' reflog: after 'git-fetch', the old tip of
+* With the 'subsystem' reflog: after 'git fetch', the old tip of
   'subsystem' is at `subsystem@\{1}`.  Subsequent fetches will
   increase the number.  (See linkgit:git-reflog[1].)
 
index cb5f4052806d81b04d4a096d3697e0230d13e8ed..2790eebaff3b9dd9a56358cc470396f36eb87085 100644 (file)
@@ -8,15 +8,15 @@ git-receive-pack - Receive what is pushed into the repository
 
 SYNOPSIS
 --------
-'git receive-pack' <directory>
+'git-receive-pack' <directory>
 
 DESCRIPTION
 -----------
-Invoked by 'git-send-pack' and updates the repository with the
+Invoked by 'git send-pack' and updates the repository with the
 information fed from the remote end.
 
 This command is usually not invoked directly by the end user.
-The UI for the protocol is on the 'git-send-pack' side, and the
+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].
 
index 7f7a5445c7d043dc2f28cfbca63732a417d91a87..802bd5791cdc74dfa487d3a5591aea67c22f6060 100644 (file)
@@ -60,7 +60,7 @@ OPTIONS
        refs.
 +
 This computation involves traversing all the reachable objects, i.e. it
-has the same cost as 'git-prune'.  Fortunately, once this is run, we
+has the same cost as 'git prune'.  Fortunately, once this is run, we
 should not have to ever worry about missing objects, because the current
 prune and pack-objects know about reflogs and protect objects referred by
 them.
index 8beb42dbb9952059650d51d2e7a5ca2efe9591fb..1b5f61aa0b85ec592c6efbfa8be08fe83f576be5 100644 (file)
@@ -25,7 +25,10 @@ Commands are given by the caller on the helper's standard input, one per line.
 
 'capabilities'::
        Lists the capabilities of the helper, one per line, ending
-       with a blank line.
+       with a blank line. Each capability may be preceded with '*'.
+       This marks them mandatory for git version using the remote
+       helper to understand (unknown mandatory capability is fatal
+       error).
 
 'list'::
        Lists the refs, one per line, in the format "<value> <name>
@@ -79,6 +82,31 @@ style string if it contains an LF.
 +
 Supported if the helper has the "push" capability.
 
+'import' <name>::
+       Produces a fast-import stream which imports the current value
+       of the named ref. It may additionally import other refs as
+       needed to construct the history efficiently. The script writes
+       to a helper-specific private namespace. The value of the named
+       ref should be written to a location in this namespace derived
+       by applying the refspecs from the "refspec" capability to the
+       name of the ref.
++
+Supported if the helper has the "import" capability.
+
+'connect' <service>::
+       Connects to given service. Standard input and standard output
+       of helper are connected to specified service (git prefix is
+       included in service name so e.g. fetching uses 'git-upload-pack'
+       as service) on remote side. Valid replies to this command are
+       empty line (connection established), 'fallback' (no smart
+       transport support, fall back to dumb transports) and just
+       exiting with error message printed (can't connect, don't
+       bother trying to fall back). After line feed terminating the
+       positive (empty) response, the output of service starts. After
+       the connection ends, the remote helper exits.
++
+Supported if the helper has the "connect" capability.
+
 If a fatal error occurs, the program writes the error message to
 stderr and exits. The caller should expect that a suitable error
 message has been printed if the child closes the connection without
@@ -99,6 +127,22 @@ CAPABILITIES
 'push'::
        This helper supports the 'push' command.
 
+'import'::
+       This helper supports the 'import' command.
+
+'refspec' 'spec'::
+       When using the import command, expect the source ref to have
+       been written to the destination ref. The earliest applicable
+       refspec takes precedence. For example
+       "refs/heads/*:refs/svn/origin/branches/*" means that, after an
+       "import refs/heads/name", the script has written to
+       refs/svn/origin/branches/name. If this capability is used at
+       all, it must cover all refs reported by the list command; if
+       it is not used, it is effectively "*:*"
+
+'connect'::
+       This helper supports the 'connect' command.
+
 REF LIST ATTRIBUTES
 -------------------
 
@@ -107,6 +151,10 @@ REF LIST ATTRIBUTES
        commands.  A helper might chose to acquire the ref list by
        opening a different type of connection to the destination.
 
+'unchanged'::
+       This ref is unchanged since the last import or fetch, although
+       the helper cannot necessarily determine what value that produced.
+
 OPTIONS
 -------
 'option verbosity' <N>::
@@ -137,9 +185,15 @@ OPTIONS
        but don't actually change any repository data.  For most
        helpers this only applies to the 'push', if supported.
 
+'option servpath <c-style-quoted-path>'::
+       Set service path (--upload-pack, --receive-pack etc.) for
+       next connect. Remote helper MAY support this option. Remote
+       helper MUST NOT rely on this option being set before
+       connect request occurs.
+
 Documentation
 -------------
-Documentation by Daniel Barkalow.
+Documentation by Daniel Barkalow and Ilari Liusvaara
 
 GIT
 ---
index c272c92d4bef4d451505634a412eaf3a10762d95..3fc599c0c7d5495ada81b20e4d37fa4936708270 100644 (file)
@@ -14,6 +14,9 @@ SYNOPSIS
 'git remote rename' <old> <new>
 'git remote rm' <name>
 'git remote set-head' <name> (-a | -d | <branch>)
+'git remote set-url' [--push] <name> <newurl> [<oldurl>]
+'git remote set-url --add' [--push] <name> <newurl>
+'git remote set-url --delete' [--push] <name> <url>
 'git remote' [-v | --verbose] 'show' [-n] <name>
 'git remote prune' [-n | --dry-run] <name>
 'git remote' [-v | --verbose] 'update' [-p | --prune] [group | remote]...
@@ -101,6 +104,20 @@ remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to
 `refs/remotes/origin/master` already exists; if not it must be fetched first.
 +
 
+'set-url'::
+
+Changes URL remote points to. Sets first URL remote points to matching
+regex <oldurl> (first URL if no <oldurl> is given) to <newurl>. If
+<oldurl> doesn't match any URL, error occurs and nothing is changed.
++
+With '--push', push URLs are manipulated instead of fetch URLs.
++
+With '--add', instead of changing some URL, new URL is added.
++
+With '--delete', instead of changing some URL, all URLs matching
+regex <url> are deleted. Trying to delete all non-push URLs is an
+error.
+
 'show'::
 
 Gives some information about the remote <name>.
@@ -161,7 +178,7 @@ $ git checkout -b nfs linux-nfs/master
 ...
 ------------
 
-* Imitate 'git-clone' but track only selected branches
+* Imitate 'git clone' but track only selected branches
 +
 ------------
 $ mkdir project.git
index c9257a10c956432fdd2e456c7f910cc2996c1534..8c67d1724f705c94fb8faf6801ee2bc7cd459629 100644 (file)
@@ -49,16 +49,16 @@ other objects in that pack they already have locally.
        deleted by way of being left in the old pack and then
        removed.  Instead, the loose unreachable objects
        will be pruned according to normal expiry rules
-       with the next 'git-gc' invocation. See linkgit:git-gc[1].
+       with the next 'git gc' invocation. See linkgit:git-gc[1].
 
 -d::
        After packing, if the newly created packs make some
        existing packs redundant, remove the redundant packs.
-       Also run  'git-prune-packed' to remove redundant
+       Also run  'git prune-packed' to remove redundant
        loose object files.
 
 -l::
-       Pass the `--local` option to 'git-pack-objects'. See
+       Pass the `--local` option to 'git pack-objects'. See
        linkgit:git-pack-objects[1].
 
 -f::
@@ -66,12 +66,12 @@ other objects in that pack they already have locally.
        linkgit:git-pack-objects[1].
 
 -q::
-       Pass the `-q` option to 'git-pack-objects'. See
+       Pass the `-q` option to 'git pack-objects'. See
        linkgit:git-pack-objects[1].
 
 -n::
        Do not update the server information with
-       'git-update-server-info'.  This option skips
+       'git update-server-info'.  This option skips
        updating local catalog files needed to publish
        this repository (or a direct copy of it)
        over HTTP or FTP.  See linkgit:git-update-server-info[1].
@@ -98,24 +98,26 @@ other objects in that pack they already have locally.
        `--window-memory=0` makes memory usage unlimited, which is the
        default.
 
---max-pack-size=<n>::
-       Maximum size of each output packfile, expressed in MiB.
+--max-pack-size=[N]::
+       Maximum size of each output pack file. The size can be suffixed with
+       "k", "m", or "g". The minimum size allowed is limited to 1 MiB.
        If specified,  multiple packfiles may be created.
-       The default is unlimited.
+       The default is unlimited, unless the config variable
+       `pack.packSizeLimit` is set.
 
 
 Configuration
 -------------
 
-When configuration variable `repack.UseDeltaBaseOffset` is set
-for the repository, the command passes `--delta-base-offset`
-option to 'git-pack-objects'; this typically results in slightly
-smaller packs, but the generated packs are incompatible with
-versions of git older than (and including) v1.4.3; do not set
-the variable in a repository that older version of git needs to
-be able to read (this includes repositories from which packs can
-be copied out over http or rsync, and people who obtained packs
-that way can try to use older git with it).
+By default, the command passes `--delta-base-offset` option to
+'git pack-objects'; this typically results in slightly smaller packs,
+but the generated packs are incompatible with versions of Git older than
+version 1.4.4. If you need to share your repository with such ancient Git
+versions, either directly or via the dumb http or rsync protocol, then you
+need to set the configuration variable `repack.UseDeltaBaseOffset` to
+"false" and repack. Access from old Git versions over the native protocol
+is unaffected by this option as the conversion is performed on the fly
+as needed in that case.
 
 
 Author
index 65a0da508a6be033e46850dc46b4fb700c93ad50..fde209258234edd221dea2f5b096c78f1978eeb4 100644 (file)
@@ -65,7 +65,7 @@ OPTIONS
 BUGS
 ----
 Comparing blobs or trees that have been replaced with those that
-replace them will not work properly. And using 'git reset --hard' to
+replace them will not work properly. And using `git reset --hard` to
 go back to a replaced commit will move the branch to the replacement
 commit instead of the replaced commit.
 
index 7dd515b8ccde701ccf5743f80b3b762813ea90c9..acc220a00f0013d115366cfc8b7a48aeeef47711 100644 (file)
@@ -30,14 +30,14 @@ enable this command.
 COMMANDS
 --------
 
-Normally, 'git-rerere' is run without arguments or user-intervention.
+Normally, 'git rerere' is run without arguments or user-intervention.
 However, it has several commands that allow it to interact with
 its working state.
 
 'clear'::
 
 This resets the metadata used by rerere if a merge resolution is to be
-aborted.  Calling 'git-am [--skip|--abort]' or 'git-rebase [--skip|--abort]'
+aborted.  Calling 'git am [--skip|--abort]' or 'git rebase [--skip|--abort]'
 will automatically invoke this command.
 
 'diff'::
@@ -142,32 +142,32 @@ finally ready and merged into the master branch.  This merge
 would require you to resolve the conflict, introduced by the
 commits marked with `*`.  However, this conflict is often the
 same conflict you resolved when you created the test merge you
-blew away.  'git-rerere' helps you resolve this final
+blew away.  'git rerere' helps you resolve this final
 conflicted merge using the information from your earlier hand
 resolve.
 
-Running the 'git-rerere' command immediately after a conflicted
+Running the 'git rerere' command immediately after a conflicted
 automerge records the conflicted working tree files, with the
 usual conflict markers `<<<<<<<`, `=======`, and `>>>>>>>` in
 them.  Later, after you are done resolving the conflicts,
-running 'git-rerere' again will record the resolved state of these
+running 'git rerere' again will record the resolved state of these
 files.  Suppose you did this when you created the test merge of
 master into the topic branch.
 
 Next time, after seeing the same conflicted automerge,
-running 'git-rerere' will perform a three-way merge between the
+running 'git rerere' will perform a three-way merge between the
 earlier conflicted automerge, the earlier manual resolution, and
 the current conflicted automerge.
 If this three-way merge resolves cleanly, the result is written
 out to your working tree file, so you do not have to manually
-resolve it.  Note that 'git-rerere' leaves the index file alone,
+resolve it.  Note that 'git rerere' leaves the index file alone,
 so you still need to do the final sanity checks with `git diff`
-(or `git diff -c`) and 'git-add' when you are satisfied.
+(or `git diff -c`) and 'git add' when you are satisfied.
 
-As a convenience measure, 'git-merge' automatically invokes
-'git-rerere' upon exiting with a failed automerge and 'git-rerere'
+As a convenience measure, 'git merge' automatically invokes
+'git rerere' upon exiting with a failed automerge and 'git rerere'
 records the hand resolve when it is a new conflict, or reuses the earlier hand
-resolve when it is not.  'git-commit' also invokes 'git-rerere'
+resolve when it is not.  'git commit' also invokes 'git rerere'
 when committing a merge result.  What this means is that you do
 not have to do anything special yourself (besides enabling
 the rerere.enabled config variable).
@@ -177,8 +177,8 @@ resolution is recorded, and it will be reused when you do the
 actual merge later with the updated master and topic branch, as long
 as the recorded resolution is still applicable.
 
-The information 'git-rerere' records is also used when running
-'git-rebase'.  After blowing away the test merge and continuing
+The information 'git rerere' records is also used when running
+'git rebase'.  After blowing away the test merge and continuing
 development on the topic branch:
 
 ------------
@@ -197,7 +197,7 @@ you could run `git rebase master topic`, to bring yourself
 up-to-date before your topic is ready to be sent upstream.
 This would result in falling back to a three-way merge, and it
 would conflict the same way as the test merge you resolved earlier.
-'git-rerere' will be run by 'git-rebase' to help you resolve this
+'git rerere' will be run by 'git rebase' to help you resolve this
 conflict.
 
 
index 2d27e405a39c77f1a7d7507db2f063a4819bec47..168db08627e009c8d760bae36a4344d433923632 100644 (file)
@@ -39,7 +39,7 @@ OPTIONS
 --soft::
        Does not touch the index file nor the working tree at all, but
        requires them to be in a good order. This leaves all your changed
-       files "Changes to be committed", as 'git-status' would
+       files "Changes to be committed", as 'git status' would
        put it.
 
 --hard::
@@ -62,11 +62,101 @@ This means that `git reset -p` is the opposite of `git add -p` (see
 linkgit:git-add[1]).
 
 -q::
+--quiet::
        Be quiet, only report errors.
 
 <commit>::
        Commit to make the current HEAD. If not given defaults to HEAD.
 
+DISCUSSION
+----------
+
+The tables below show what happens when running:
+
+----------
+git reset --option target
+----------
+
+to reset the HEAD to another commit (`target`) with the different
+reset options depending on the state of the files.
+
+In these tables, A, B, C and D are some different states of a
+file. For example, the first line of the first table means that if a
+file is in state A in the working tree, in state B in the index, in
+state C in HEAD and in state D in the target, then "git reset --soft
+target" will put the file in state A in the working tree, in state B
+in the index and in state D in HEAD.
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       A       B     C    D     --soft   A       B     D
+                               --mixed  A       D     D
+                               --hard   D       D     D
+                               --merge (disallowed)
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       A       B     C    C     --soft   A       B     C
+                               --mixed  A       C     C
+                               --hard   C       C     C
+                               --merge (disallowed)
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       B       B     C    D     --soft   B       B     D
+                               --mixed  B       D     D
+                               --hard   D       D     D
+                               --merge  D       D     D
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       B       B     C    C     --soft   B       B     C
+                               --mixed  B       C     C
+                               --hard   C       C     C
+                               --merge  C       C     C
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       B       C     C    D     --soft   B       C     D
+                               --mixed  B       D     D
+                               --hard   D       D     D
+                               --merge (disallowed)
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       B       C     C    C     --soft   B       C     C
+                               --mixed  B       C     C
+                               --hard   C       C     C
+                               --merge  B       C     C
+
+"reset --merge" is meant to be used when resetting out of a conflicted
+merge. Any mergy operation guarantees that the work tree file that is
+involved in the merge does not have local change wrt the index before
+it starts, and that it writes the result out to the work tree. So if
+we see some difference between the index and the target and also
+between the index and the work tree, then it means that we are not
+resetting out from a state that a mergy operation left after failing
+with a conflict. That is why we disallow --merge option in this case.
+
+The following tables show what happens when there are unmerged
+entries:
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       X       U     A    B     --soft  (disallowed)
+                               --mixed  X       B     B
+                               --hard   B       B     B
+                               --merge  B       B     B
+
+      working index HEAD target         working index HEAD
+      ----------------------------------------------------
+       X       U     A    A     --soft  (disallowed)
+                               --mixed  X       A     A
+                               --hard   A       A     A
+                               --merge  A       A     A
+
+X means any state and U means an unmerged index.
+
 Examples
 --------
 
index 3341d1b62f34fa99afcdab91da1191ceacc2c24f..173f3fc78599f268c1e483c4d1abe51e95ad0e59 100644 (file)
@@ -9,7 +9,7 @@ git-rev-list - Lists commit objects in reverse chronological order
 SYNOPSIS
 --------
 [verse]
-'git-rev-list' [ \--max-count=number ]
+'git rev-list' [ \--max-count=number ]
             [ \--skip=number ]
             [ \--max-age=timestamp ]
             [ \--min-age=timestamp ]
@@ -21,9 +21,10 @@ SYNOPSIS
             [ \--full-history ]
             [ \--not ]
             [ \--all ]
-            [ \--branches ]
-            [ \--tags ]
-            [ \--remotes ]
+            [ \--branches[=pattern] ]
+            [ \--tags[=pattern] ]
+            [ \--remotes[=pattern] ]
+            [ \--glob=glob-pattern ]
             [ \--stdin ]
             [ \--quiet ]
             [ \--topo-order ]
@@ -93,8 +94,8 @@ between the two operands.  The following two commands are equivalent:
 'rev-list' is a very essential git command, since it
 provides the ability to build and traverse commit ancestry graphs. For
 this reason, it has a lot of different options that enables it to be
-used by commands as different as 'git-bisect' and
-'git-repack'.
+used by commands as different as 'git bisect' and
+'git repack'.
 
 OPTIONS
 -------
index 82045a2522799ccf3a03479bf4f4cd1fa1809879..fc731525da7a50cda2f55fcfd4fa9e17f531582b 100644 (file)
@@ -15,16 +15,16 @@ DESCRIPTION
 
 Many git porcelainish commands take mixture of flags
 (i.e. parameters that begin with a dash '-') and parameters
-meant for the underlying 'git-rev-list' command they use internally
+meant for the underlying 'git rev-list' command they use internally
 and flags and parameters for the other commands they use
-downstream of 'git-rev-list'.  This command is used to
+downstream of 'git rev-list'.  This command is used to
 distinguish between them.
 
 
 OPTIONS
 -------
 --parseopt::
-       Use 'git-rev-parse' in option parsing mode (see PARSEOPT section below).
+       Use 'git rev-parse' in option parsing mode (see PARSEOPT section below).
 
 --keep-dashdash::
        Only meaningful in `--parseopt` mode. Tells the option parser to echo
@@ -33,20 +33,20 @@ OPTIONS
 --stop-at-non-option::
        Only meaningful in `--parseopt` mode.  Lets the option parser stop at
        the first non-option argument.  This can be used to parse sub-commands
-       that take options themself.
+       that take options themselves.
 
 --sq-quote::
-       Use 'git-rev-parse' in shell quoting mode (see SQ-QUOTE
+       Use 'git rev-parse' in shell quoting mode (see SQ-QUOTE
        section below). In contrast to the `--sq` option below, this
        mode does only quoting. Nothing else is done to command input.
 
 --revs-only::
        Do not output flags and parameters not meant for
-       'git-rev-list' command.
+       'git rev-list' command.
 
 --no-revs::
        Do not output flags and parameters meant for
-       'git-rev-list' command.
+       'git rev-list' command.
 
 --flags::
        Do not output non-flag parameters.
@@ -74,7 +74,7 @@ OPTIONS
        properly quoted for consumption by shell.  Useful when
        you expect your parameter to contain whitespaces and
        newlines (e.g. when using pickaxe `-S` with
-       'git-diff-\*'). In contrast to the `--sq-quote` option,
+       'git diff-\*'). In contrast to the `--sq-quote` option,
        the command input is still interpreted as usual.
 
 --not::
@@ -103,14 +103,27 @@ OPTIONS
 --all::
        Show all refs found in `$GIT_DIR/refs`.
 
---branches::
-       Show branch refs found in `$GIT_DIR/refs/heads`.
+--branches[=pattern]::
+--tags[=pattern]::
+--remotes[=pattern]::
+       Show all branches, tags, or remote-tracking branches,
+       respectively (i.e., refs found in `$GIT_DIR/refs/heads`,
+       `$GIT_DIR/refs/tags`, or `$GIT_DIR/refs/remotes`,
+       respectively).
++
+If a `pattern` is given, only refs matching the given shell glob are
+shown.  If the pattern does not contain a globbing character (`?`,
+`\*`, or `[`), it is turned into a prefix match by appending `/\*`.
 
---tags::
-       Show tag refs found in `$GIT_DIR/refs/tags`.
+--glob=pattern::
+       Show all refs matching the shell glob pattern `pattern`. If
+       the pattern does not start with `refs/`, this is automatically
+       prepended.  If the pattern does not contain a globbing
+       character (`?`, `\*`, or `[`), it is turned into a prefix
+       match by appending `/\*`.
 
---remotes::
-       Show tag refs found in `$GIT_DIR/refs/remotes`.
+--show-toplevel::
+       Show the absolute path of the top-level directory.
 
 --show-prefix::
        When the command is invoked from a subdirectory, show the
@@ -145,12 +158,12 @@ OPTIONS
 --since=datestring::
 --after=datestring::
        Parse the date string, and output the corresponding
-       --max-age= parameter for 'git-rev-list'.
+       --max-age= parameter for 'git rev-list'.
 
 --until=datestring::
 --before=datestring::
        Parse the date string, and output the corresponding
-       --min-age= parameter for 'git-rev-list'.
+       --min-age= parameter for 'git rev-list'.
 
 <args>...::
        Flags and parameters to be parsed.
@@ -171,7 +184,7 @@ blobs contained in a commit.
   name the same commit object if there are no other object in
   your repository whose object name starts with dae86e.
 
-* An output from 'git-describe'; i.e. a closest tag, optionally
+* An output from 'git describe'; i.e. a closest tag, optionally
   followed by a dash and a number of commits, followed by a dash, a
   `g`, and an abbreviated object name.
 
@@ -197,13 +210,13 @@ blobs contained in a commit.
 +
 HEAD names the commit your changes in the working tree is based on.
 FETCH_HEAD records the branch you fetched from a remote repository
-with your last 'git-fetch' invocation.
+with your last 'git fetch' invocation.
 ORIG_HEAD is created by commands that moves your HEAD in a drastic
 way, to record the position of the HEAD before their operation, so that
 you can change the tip of the branch back to the state before you ran
 them easily.
 MERGE_HEAD records the commit(s) you are merging into your branch
-when you run 'git-merge'.
+when you run 'git merge'.
 
 * A ref followed by the suffix '@' with a date specification
   enclosed in a brace
@@ -231,6 +244,10 @@ when you run 'git-merge'.
 * The special construct '@\{-<n>\}' means the <n>th branch checked out
   before the current one.
 
+* The suffix '@{upstream}' to a ref (short form 'ref@{u}') refers to
+  the branch the ref is set to build on top of.  Missing ref defaults
+  to the current branch.
+
 * A suffix '{caret}' to a revision parameter means the first parent of
   that commit object.  '{caret}<n>' means the <n>th parent (i.e.
   'rev{caret}'
@@ -308,7 +325,7 @@ G   H   I   J
 SPECIFYING RANGES
 -----------------
 
-History traversing commands such as 'git-log' operate on a set
+History traversing commands such as 'git log' operate on a set
 of commits, not just a single commit.  To these commands,
 specifying a single revision with the notation described in the
 previous section means the set of commits reachable from that
@@ -349,7 +366,7 @@ Here are a handful of examples:
 PARSEOPT
 --------
 
-In `--parseopt` mode, 'git-rev-parse' helps massaging options to bring to shell
+In `--parseopt` mode, 'git rev-parse' helps massaging options to bring to shell
 scripts the same facilities C builtins have. It works as an option normalizer
 (e.g. splits single switches aggregate values), a bit like `getopt(1)` does.
 
@@ -361,7 +378,7 @@ usage on the standard error stream, and exits with code 129.
 Input Format
 ~~~~~~~~~~~~
 
-'git-rev-parse --parseopt' input format is fully text based. It has two parts,
+'git rev-parse --parseopt' input format is fully text based. It has two parts,
 separated by a line that contains only `--`. The lines before the separator
 (should be more than one) are used for the usage.
 The lines after the separator describe the options.
@@ -420,13 +437,13 @@ eval `echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?`
 SQ-QUOTE
 --------
 
-In `--sq-quote` mode, 'git-rev-parse' echoes on the standard output a
+In `--sq-quote` mode, 'git rev-parse' echoes on the standard output a
 single line suitable for `sh(1)` `eval`. This line is made by
 normalizing the arguments following `--sq-quote`. Nothing other than
 quoting the arguments is done.
 
 If you want command input to still be interpreted as usual by
-'git-rev-parse' before the output is shell quoted, see the `--sq`
+'git rev-parse' before the output is shell quoted, see the `--sq`
 option.
 
 Example
index 5e1175800a11d9e6a63102063af782757464d72e..c66bf8072e6720d3edfc17d234fc09c8d01bbb2c 100644 (file)
@@ -20,8 +20,8 @@ effect of an earlier commit (often a faulty one).  If you want to
 throw away all uncommitted changes in your working directory, you
 should see linkgit:git-reset[1], particularly the '--hard' option.  If
 you want to extract specific files as they were in another commit, you
-should see linkgit:git-checkout[1], specifically the 'git checkout
-<commit> -- <filename>' syntax.  Take care with these alternatives as
+should see linkgit:git-checkout[1], specifically the `git checkout
+<commit> -- <filename>` syntax.  Take care with these alternatives as
 both will discard uncommitted changes in your working directory.
 
 OPTIONS
@@ -33,7 +33,7 @@ OPTIONS
 
 -e::
 --edit::
-       With this option, 'git-revert' will let you edit the commit
+       With this option, 'git revert' will let you edit the commit
        message prior to committing the revert. This is the default if
        you run the command from a terminal.
 
@@ -54,7 +54,7 @@ See the link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for
 more details.
 
 --no-edit::
-       With this option, 'git-revert' will not start the commit
+       With this option, 'git revert' will not start the commit
        message editor.
 
 -n::
index 8c482f40b98f81460da7230a8656a9a4a97d77df..ced35b2f532dde3580f162a0c23b642002a0e508 100644 (file)
@@ -84,7 +84,7 @@ See the CONFIGURATION section for 'sendemail.multiedit'.
 --in-reply-to=<identifier>::
        Specify the contents of the first In-Reply-To header.
        Subsequent emails will refer to the previous email
-       instead of this if --chain-reply-to is set (the default)
+       instead of this if --chain-reply-to is set.
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
@@ -172,8 +172,8 @@ Automating
        email sent.  If disabled with "--no-chain-reply-to", all emails after
        the first will be sent as replies to the first email sent.  When using
        this, it is recommended that the first file given be an overview of the
-       entire patch series. Default is the value of the 'sendemail.chainreplyto'
-       configuration value; if that is unspecified, default to --chain-reply-to.
+       entire patch series. Disabled by default, but the 'sendemail.chainreplyto'
+       configuration variable can be used to enable it.
 
 --identity=<identity>::
        A configuration identity. When given, causes values in the
index 5a04c6eaf78adb8c6ccff573d873392e19640fdd..8178d9264251e45d5af5cabfca2d193252ac1768 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Usually you would want to use 'git-push', which is a
+Usually you would want to use 'git push', which is a
 higher-level wrapper of this command, instead. See linkgit:git-push[1].
 
 Invokes 'git-receive-pack' on a possibly remote repository, and
@@ -86,7 +86,7 @@ and the destination side (after the colon).  The ref to be
 pushed is determined by finding a match that matches the source
 side, and where it is pushed is determined by using the
 destination side. The rules used to match a ref are the same
-rules used by 'git-rev-parse' to resolve a symbolic ref
+rules used by 'git rev-parse' to resolve a symbolic ref
 name. See linkgit:git-rev-parse[1].
 
  - It is an error if <src> does not match exactly one of the
index 18f14b5be89b4e0240f59b13313308f3c09d012c..3da241304b0d2fdaa376bca740beb024a36b2cb6 100644 (file)
@@ -16,7 +16,7 @@ This is not a command the end user would want to run.  Ever.
 This documentation is meant for people who are studying the
 Porcelain-ish scripts and/or are writing new ones.
 
-The 'git-sh-setup' scriptlet is designed to be sourced (using
+The 'git sh-setup' scriptlet is designed to be sourced (using
 `.`) by other shell scripts to set up some variables pointing at
 the normal git directories and a few helper shell functions.
 
index 42463a955dd3f5dad25f3a45f0eec62d8712f731..dfd4d0c2233df09b64a10d1ad31a36afaa01ec7c 100644 (file)
@@ -3,17 +3,17 @@ git-shortlog(1)
 
 NAME
 ----
-git-shortlog - Summarize 'git-log' output
+git-shortlog - Summarize 'git log' output
 
 SYNOPSIS
 --------
 [verse]
 git log --pretty=short | 'git shortlog' [-h] [-n] [-s] [-e] [-w]
-git shortlog [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] [<committish>...]
+'git shortlog' [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] [<committish>...]
 
 DESCRIPTION
 -----------
-Summarizes 'git-log' output in a format suitable for inclusion
+Summarizes 'git log' output in a format suitable for inclusion
 in release announcements. Each commit will be grouped by author and
 the first line of the commit message will be shown.
 
index e3285aacfd310cc269cdb03aa9243c939c24def7..8382fbe0ec5e59545e265317bdff38bfa098e0ed 100644 (file)
@@ -14,10 +14,10 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Reads given idx file for packed git archive created with
-'git-pack-objects' command, and dumps its contents.
+'git pack-objects' command, and dumps its contents.
 
 The information it outputs is subset of what you can get from
-'git-verify-pack -v'; this command only shows the packfile
+'git verify-pack -v'; this command only shows the packfile
 offset and SHA1 of each object.
 
 
index 70f400b2664df422160195a9615d566152a1b877..df17d49b87c260c6f5b3fd75d4aad41b77fcf8c3 100644 (file)
@@ -72,7 +72,7 @@ OPTIONS
 
 --exclude-existing[=<pattern>]::
 
-       Make 'git-show-ref' act as a filter that reads refs from stdin of the
+       Make 'git show-ref' act as a filter that reads refs from stdin of the
        form "^(?:<anything>\s)?<refname>(?:\^\{\})?$" and performs the
        following actions on each:
        (1) strip "^{}" at the end of line if any;
@@ -135,7 +135,7 @@ When using the '--verify' flag, the command requires an exact path:
 
 will only match the exact branch called "master".
 
-If nothing matches, 'git-show-ref' will return an error code of 1,
+If nothing matches, 'git show-ref' will return an error code of 1,
 and in the case of verification, it will show an error message.
 
 For scripting, you can ask it to be quiet with the "--quiet" flag, which
index 48b612e2ae50c319bcef567c7619f60396dd8408..55e687a7c7f2113615c80e1719c4f3a2120ba7fa 100644 (file)
@@ -16,16 +16,16 @@ Shows one or more objects (blobs, trees, tags and commits).
 
 For commits it shows the log message and textual diff. It also
 presents the merge commit in a special format as produced by
-'git-diff-tree --cc'.
+'git diff-tree --cc'.
 
 For tags, it shows the tag message and the referenced objects.
 
-For trees, it shows the names (equivalent to 'git-ls-tree'
+For trees, it shows the names (equivalent to 'git ls-tree'
 with \--name-only).
 
 For plain blobs, it shows the plain contents.
 
-The command takes options applicable to the 'git-diff-tree' command to
+The command takes options applicable to the 'git diff-tree' command to
 control how the changes the commit introduces are shown.
 
 This manual page describes only the most frequently used options.
index 3f14b727b899fbf3b4f7cb6513bd1b14e5938160..84e555d81d9bdd25afeacf868f55a6a868773cd0 100644 (file)
@@ -20,7 +20,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 
-Use 'git stash' when you want to record the current state of the
+Use `git stash` when you want to record the current state of the
 working directory and the index, but want to go back to a clean
 working directory.  The command saves your local modifications away
 and reverts the working directory to match the `HEAD` commit.
@@ -77,7 +77,7 @@ stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation
 stash@{1}: On master: 9cc0589... Add git-stash
 ----------------------------------------------------------------
 +
-The command takes options applicable to the 'git-log'
+The command takes options applicable to the 'git log'
 command to control what is shown and how. See linkgit:git-log[1].
 
 show [<stash>]::
@@ -85,7 +85,7 @@ show [<stash>]::
        Show the changes recorded in the stash as a diff between the
        stashed state and its original parent. When no `<stash>` is given,
        shows the latest one. By default, the command shows the diffstat, but
-       it will accept any format known to 'git-diff' (e.g., `git stash show
+       it will accept any format known to 'git diff' (e.g., `git stash show
        -p stash@\{1}` to view the second most recent stash in patch form).
 
 pop [--index] [-q|--quiet] [<stash>]::
@@ -201,7 +201,7 @@ $ git reset --soft HEAD^
 # ... continue hacking ...
 ----------------------------------------------------------------
 +
-You can use 'git-stash' to simplify the above, like this:
+You can use 'git stash' to simplify the above, like this:
 +
 ----------------------------------------------------------------
 # ... hack hack hack ...
index 84f60f3407499c40a8e0caadf9d40ed5e9b8386b..1cab91b53455e0129e6c4940a2698620e2197624 100644 (file)
@@ -8,7 +8,7 @@ git-status - Show the working tree status
 
 SYNOPSIS
 --------
-'git status' <options>...
+'git status' [<options>...] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -17,28 +17,93 @@ current HEAD commit, paths that have differences between the working
 tree and the index file, and paths in the working tree that are not
 tracked by git (and are not ignored by linkgit:gitignore[5]). The first
 are what you _would_ commit by running `git commit`; the second and
-third are what you _could_ commit by running 'git-add' before running
+third are what you _could_ commit by running 'git add' before running
 `git commit`.
 
-The command takes the same set of options as 'git-commit'; it
-shows what would be committed if the same options are given to
-'git-commit'.
-
-If there is no path that is different between the index file and
-the current HEAD commit (i.e., there is nothing to commit by running
-`git commit`), the command exits with non-zero status.
+OPTIONS
+-------
+
+-s::
+--short::
+       Give the output in the short-format.
+
+--porcelain::
+       Give the output in a stable, easy-to-parse format for scripts.
+       Currently this is identical to --short output, but is guaranteed
+       not to change in the future, making it safe for scripts.
+
+-u[<mode>]::
+--untracked-files[=<mode>]::
+       Show untracked files (Default: 'all').
++
+The mode parameter is optional, and is used to specify
+the handling of untracked files. The possible options are:
++
+--
+       - 'no'     - Show no untracked files
+       - 'normal' - Shows untracked files and directories
+       - 'all'    - Also shows individual files in untracked directories.
+--
++
+See linkgit:git-config[1] for configuration variable
+used to change the default for when the option is not
+specified.
+
+-z::
+       Terminate entries with NUL, instead of LF.  This implies
+       the `--porcelain` output format if no other format is given.
 
 
 OUTPUT
 ------
 The output from this command is designed to be used as a commit
 template comment, and all the output lines are prefixed with '#'.
+The default, long format, is designed to be human readable,
+verbose and descriptive.  They are subject to change in any time.
 
 The paths mentioned in the output, unlike many other git commands, are
 made relative to the current directory if you are working in a
 subdirectory (this is on purpose, to help cutting and pasting). See
 the status.relativePaths config option below.
 
+In short-format, the status of each path is shown as
+
+       XY PATH1 -> PATH2
+
+where `PATH1` is the path in the `HEAD`, and ` -> PATH2` part is
+shown only when `PATH1` corresponds to a different path in the
+index/worktree (i.e. renamed).
+
+For unmerged entries, `X` shows the status of stage #2 (i.e. ours) and `Y`
+shows the status of stage #3 (i.e. theirs).
+
+For entries that do not have conflicts, `X` shows the status of the index,
+and `Y` shows the status of the work tree.  For untracked paths, `XY` are
+`??`.
+
+    X          Y     Meaning
+    -------------------------------------------------
+              [MD]   not updated
+    M        [ MD]   updated in index
+    A        [ MD]   added to index
+    D        [ MD]   deleted from index
+    R        [ MD]   renamed in index
+    C        [ MD]   copied in index
+    [MARC]           index and work tree matches
+    [ MARC]     M    work tree changed since index
+    [ MARC]     D    deleted in work tree
+    -------------------------------------------------
+    D           D    unmerged, both deleted
+    A           U    unmerged, added by us
+    U           D    unmerged, deleted by them
+    U           A    unmerged, added by them
+    D           U    unmerged, deleted by us
+    A           A    unmerged, both added
+    U           U    unmerged, both modified
+    -------------------------------------------------
+    ?           ?    untracked
+    -------------------------------------------------
+
 
 CONFIGURATION
 -------------
@@ -53,9 +118,9 @@ paths shown are relative to the repository root, not to the current
 directory.
 
 If `status.submodulesummary` is set to a non zero number or true (identical
-to -1 or an unlimited number), the submodule summary will be enabled and a
-summary of commits for modified submodules will be shown (see --summary-limit
-option of linkgit:git-submodule[1]).
+to -1 or an unlimited number), the submodule summary will be enabled for
+the long format and a summary of commits for modified submodules will be
+shown (see --summary-limit option of linkgit:git-submodule[1]).
 
 SEE ALSO
 --------
@@ -63,8 +128,7 @@ linkgit:gitignore[5]
 
 Author
 ------
-Written by Linus Torvalds <torvalds@osdl.org> and
-Junio C Hamano <gitster@pobox.com>.
+Written by Junio C Hamano <gitster@pobox.com>.
 
 Documentation
 --------------
index 4ef70c42ebf512ee6dc946ca2ceee284b3211b54..2502531a3dfd0cee67f6d1d9fc1db1d69cf20e30 100644 (file)
@@ -99,11 +99,11 @@ locate the submodule using the relative URL in .gitmodules.
 status::
        Show the status of the submodules. This will print the SHA-1 of the
        currently checked out commit for each submodule, along with the
-       submodule path and the output of 'git-describe' for the
+       submodule path and the output of 'git describe' for the
        SHA-1. Each SHA-1 will be prefixed with `-` if the submodule is not
        initialized and `+` if the currently checked out submodule commit
        does not match the SHA-1 found in the index of the containing
-       repository. This command is the default command for 'git-submodule'.
+       repository. This command is the default command for 'git submodule'.
 +
 If '--recursive' is specified, this command will recurse into nested
 submodules, and show their status as well.
@@ -114,8 +114,8 @@ init::
        The key used in .git/config is `submodule.$name.url`.
        This command does not alter existing information in .git/config.
        You can then customize the submodule clone URLs in .git/config
-       for your local setup and proceed to 'git submodule update';
-       you can also just use 'git submodule update --init' without
+       for your local setup and proceed to `git submodule update`;
+       you can also just use `git submodule update --init` without
        the explicit 'init' step if you do not intend to customize
        any submodule locations.
 
@@ -218,7 +218,7 @@ OPTIONS
        This option is only valid for the update command.
        Rebase the current branch onto the commit recorded in the
        superproject. If this option is given, the submodule's HEAD will not
-       be detached. If a merge failure prevents this process, you will have
+       be detached. If a merge failure prevents this process, you will have
        to resolve these failures with linkgit:git-rebase[1].
        If the key `submodule.$name.update` is set to `rebase`, this option is
        implicit.
index 4cdca0d87435b4995e5c051750a6dd02720006f8..99f3c1ea6c41b1cda7069f82bd8e38bbbe27a711 100644 (file)
@@ -62,6 +62,8 @@ COMMANDS
        Set the 'useSvnsyncProps' option in the [svn-remote] config.
 --rewrite-root=<URL>;;
        Set the 'rewriteRoot' option in the [svn-remote] config.
+--rewrite-uuid=<UUID>;;
+       Set the 'rewriteUUID' option in the [svn-remote] config.
 --username=<USER>;;
        For transports that SVN handles authentication for (http,
        https, and plain svn), specify the username.  For other
@@ -239,6 +241,19 @@ discouraged.
 where <name> is the name of the SVN repository as specified by the -R option to
 'init' (or "svn" by default).
 
+--username;;
+       Specify the SVN username to perform the commit as.  This option overrides
+       configuration property 'username'.
+
+--commit-url;;
+       Use the specified URL to connect to the destination Subversion
+       repository.  This is useful in cases where the source SVN
+       repository is read-only.  This option overrides configuration
+       property 'commiturl'.
++
+       git config --get-all svn-remote.<name>.commiturl
++
+
 'tag'::
        Create a tag in the SVN repository. This is a shorthand for
        'branch -t'.
@@ -616,6 +631,12 @@ svn-remote.<name>.rewriteRoot::
        the repository with a public http:// or svn:// URL in the
        metadata so users of it will see the public URL.
 
+svn-remote.<name>.rewriteUUID::
+       Similar to the useSvmProps option; this is for users who need
+       to remap the UUID manually. This may be useful in situations
+       where the original UUID is not available via either useSvmProps
+       or useSvnsyncProps.
+
 svn.brokenSymlinkWorkaround::
        This disables potentially expensive checks to workaround
        broken symlinks checked into SVN by broken clients.  Set this
@@ -625,13 +646,14 @@ svn.brokenSymlinkWorkaround::
        revision fetched.  If unset, 'git svn' assumes this option to
        be "true".
 
-Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps
+Since the noMetadata, rewriteRoot, rewriteUUID, useSvnsyncProps and useSvmProps
 options all affect the metadata generated and used by 'git svn'; they
 *must* be set in the configuration file before any history is imported
 and these settings should never be changed once they are set.
 
-Additionally, only one of these four options can be used per-svn-remote
-section because they affect the 'git-svn-id:' metadata line.
+Additionally, only one of these options can be used per svn-remote
+section because they affect the 'git-svn-id:' metadata line, except
+for rewriteRoot and rewriteUUID which can be used together.
 
 
 BASIC EXAMPLES
@@ -816,6 +838,22 @@ independent path component (surrounded by '/' or EOL).   This
 type of configuration is not automatically created by 'init' and
 should be manually entered with a text-editor or using 'git config'.
 
+It is also possible to fetch a subset of branches or tags by using a
+comma-separated list of names within braces. For example:
+
+------------------------------------------------------------------------
+[svn-remote "huge-project"]
+       url = http://server.org/svn
+       fetch = trunk/src:refs/remotes/trunk
+       branches = branches/{red,green}/src:refs/remotes/branches/*
+       tags = tags/{1.0,2.0}/src:refs/remotes/tags/*
+------------------------------------------------------------------------
+
+Note that git-svn keeps track of the highest revision in which a branch
+or tag has appeared. If the subset of branches or tags is changed after
+fetching, then .git/svn/.metadata must be manually edited to remove (or
+reset) branches-maxRev and/or tags-maxRev as appropriate.
+
 SEE ALSO
 --------
 linkgit:git-rebase[1]
index 63925388073b17c3087d32813979513580adfa2a..33a15362949bed9e23debb7ac50db35585ed23ce 100644 (file)
@@ -49,7 +49,7 @@ cumbersome.  On some platforms, `ln -sf` does not even work as
 advertised (horrors).  Therefore symbolic links are now deprecated
 and symbolic refs are used by default.
 
-'git-symbolic-ref' will exit with status 0 if the contents of the
+'git symbolic-ref' will exit with status 0 if the contents of the
 symbolic ref were printed correctly, with status 1 if the requested
 name is not a symbolic ref, or 128 if another error occurs.
 
index 299b04f726b2e7662fb7db64169726eec82a5c14..31c78a81e094ffc8a66639119eadefbd1e760e35 100644 (file)
@@ -95,7 +95,7 @@ OPTIONS
 
 CONFIGURATION
 -------------
-By default, 'git-tag' in sign-with-default mode (-s) will use your
+By default, 'git tag' in sign-with-default mode (-s) will use your
 committer identity (of the form "Your Name <your@email.address>") to
 find a key.  If you want to use a different default key, you can specify
 it in the repository configuration as follows:
@@ -131,12 +131,12 @@ and be done with it.
 
 . The insane thing.
 You really want to call the new version "X" too, 'even though'
-others have already seen the old one. So just use 'git-tag -f'
+others have already seen the old one. So just use 'git tag -f'
 again, as if you hadn't already published the old one.
 
 However, Git does *not* (and it should not) change tags behind
 users back. So if somebody already got the old tag, doing a
-'git-pull' on your tree shouldn't just make them overwrite the old
+'git pull' on your tree shouldn't just make them overwrite the old
 one.
 
 If somebody got a release tag from you, you cannot just change
@@ -190,7 +190,7 @@ private anchor point tags from the other person.
 
 You would notice "please pull" messages on the mailing list says
 repo URL and branch name alone.  This is designed to be easily
-cut&pasted to a 'git-fetch' command line:
+cut&pasted to a 'git fetch' command line:
 
 ------------
 Linus, please pull from
index a5d9558dd1eabd71e838026721d707c5f1ecc369..3c786bd283ce839ca9339f0a8ac7412a1f985d3a 100644 (file)
@@ -12,19 +12,19 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-THIS COMMAND IS DEPRECATED.  Use 'git-archive' with `--format=tar`
+THIS COMMAND IS DEPRECATED.  Use 'git archive' with `--format=tar`
 option instead (and move the <base> argument to `--prefix=base/`).
 
 Creates a tar archive containing the tree structure for the named tree.
 When <base> is specified it is added as a leading path to the files in the
 generated tar archive.
 
-'git-tar-tree' behaves differently when given a tree ID versus when given
+'git tar-tree' behaves differently when given a tree ID versus when given
 a commit ID or tag ID.  In the first case the current time is used as
 modification time of each file in the archive.  In the latter case the
 commit time as recorded in the referenced commit object is used instead.
 Additionally the commit ID is stored in a global extended pax header.
-It can be extracted using 'git-get-tar-commit-id'.
+It can be extracted using 'git get-tar-commit-id'.
 
 OPTIONS
 -------
index 6052484ab9e28d5565067885b1c4ed5667e8e90f..68dc1879fe912b1a01b3caa6b1c0168f6a7b8072 100644 (file)
@@ -15,6 +15,7 @@ SYNOPSIS
             [--cacheinfo <mode> <object> <file>]\*
             [--chmod=(+|-)x]
             [--assume-unchanged | --no-assume-unchanged]
+            [--skip-worktree | --no-skip-worktree]
             [--ignore-submodules]
             [--really-refresh] [--unresolve] [--again | -g]
             [--info-only] [--index-info]
@@ -31,7 +32,7 @@ cleared.
 See also linkgit:git-add[1] for a more user-friendly way to do some of
 the most common operations on the index.
 
-The way 'git-update-index' handles files it is told about can be modified
+The way 'git update-index' handles files it is told about can be modified
 using the various options:
 
 OPTIONS
@@ -53,7 +54,7 @@ OPTIONS
 -q::
         Quiet.  If --refresh finds that the index needs an update, the
         default behavior is to error out.  This option makes
-       'git-update-index' continue anyway.
+       'git update-index' continue anyway.
 
 --ignore-submodules::
        Do not try to update submodules.  This option is only respected
@@ -61,7 +62,7 @@ OPTIONS
 
 --unmerged::
         If --refresh finds unmerged changes in the index, the default
-       behavior is to error out.  This option makes 'git-update-index'
+       behavior is to error out.  This option makes 'git update-index'
         continue anyway.
 
 --ignore-missing::
@@ -103,9 +104,16 @@ you will need to handle the situation manually.
        Like '--refresh', but checks stat information unconditionally,
        without regard to the "assume unchanged" setting.
 
+--skip-worktree::
+--no-skip-worktree::
+       When one of these flags is specified, the object name recorded
+       for the paths are not updated. Instead, these options
+       set and unset the "skip-worktree" bit for the paths. See
+       section "Skip-worktree bit" below for more information.
+
 -g::
 --again::
-       Runs 'git-update-index' itself on the paths whose index
+       Runs 'git update-index' itself on the paths whose index
        entries are different from those from the `HEAD` commit.
 
 --unresolve::
@@ -123,7 +131,7 @@ you will need to handle the situation manually.
 
 --replace::
        By default, when a file `path` exists in the index,
-       'git-update-index' refuses an attempt to add `path/file`.
+       'git update-index' refuses an attempt to add `path/file`.
        Similarly if a file `path/file` exists, a file `path`
        cannot be added.  With --replace flag, existing entries
        that conflict with the entry being added are
@@ -159,7 +167,7 @@ up-to-date for mode/content changes. But what it *does* do is to
 can refresh the index for a file that hasn't been changed but where
 the stat entry is out of date.
 
-For example, you'd want to do this after doing a 'git-read-tree', to link
+For example, you'd want to do this after doing a 'git read-tree', to link
 up the stat index details with the proper files.
 
 Using --cacheinfo or --info-only
@@ -200,13 +208,13 @@ back on 3-way merge.
 
     . mode SP type SP sha1          TAB path
 +
-The second format is to stuff 'git-ls-tree' output
+The second format is to stuff 'git ls-tree' output
 into the index file.
 
     . mode         SP sha1 SP stage TAB path
 +
 This format is to put higher order stages into the
-index file and matches 'git-ls-files --stage' output.
+index file and matches 'git ls-files --stage' output.
 
 To place a higher stage entry to the index, the path should
 first be removed by feeding a mode=0 entry for the path, and
@@ -263,8 +271,8 @@ option.  To unset, use `--no-assume-unchanged`.
 The command looks at `core.ignorestat` configuration variable.  When
 this is true, paths updated with `git update-index paths...` and
 paths updated with other git commands that update both index and
-working tree (e.g. 'git-apply --index', 'git-checkout-index -u',
-and 'git-read-tree -u') are automatically marked as "assume
+working tree (e.g. 'git apply --index', 'git checkout-index -u',
+and 'git read-tree -u') are automatically marked as "assume
 unchanged".  Note that "assume unchanged" bit is *not* set if
 `git update-index --refresh` finds the working tree file matches
 the index (use `git update-index --really-refresh` if you want
@@ -308,6 +316,27 @@ M foo.c
 <9> now it checks with lstat(2) and finds it has been changed.
 
 
+Skip-worktree bit
+-----------------
+
+Skip-worktree bit can be defined in one (long) sentence: When reading
+an entry, if it is marked as skip-worktree, then Git pretends its
+working directory version is up to date and read the index version
+instead.
+
+To elaborate, "reading" means checking for file existence, reading
+file attributes or file content. The working directory version may be
+present or absent. If present, its content may match against the index
+version or not. Writing is not affected by this bit, content safety
+is still first priority. Note that Git _can_ update working directory
+file, that is marked skip-worktree, if it is safe to do so (i.e.
+working directory version matches index version)
+
+Although this bit looks similar to assume-unchanged bit, its goal is
+different from assume-unchanged bit's. Skip-worktree also takes
+precedence over assume-unchanged bit when both are set.
+
+
 Configuration
 -------------
 
@@ -317,7 +346,7 @@ 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
 executable bit.   On such an unfortunate filesystem, you may
-need to use 'git-update-index --chmod='.
+need to use 'git update-index --chmod='.
 
 Quite similarly, if `core.symlinks` configuration variable is set
 to 'false' (see linkgit:git-config[1]), symbolic links are checked out
index bbd7617587084b0c66fd8e0b9f623cac50be2c03..f5f2b3908b2a1550f44ad6615d9335d40e264112 100644 (file)
@@ -12,11 +12,11 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Invoked by 'git-archive --remote' and sends a generated archive to the
+Invoked by 'git archive --remote' and sends a generated archive to the
 other end over the git protocol.
 
 This command is usually not invoked directly by the end user.  The UI
-for the protocol is on the 'git-archive' side, and the program pair
+for the protocol is on the 'git archive' side, and the program pair
 is meant to be used to get an archive from a remote repository.
 
 OPTIONS
index b8e49dce4a19a4d7083459468f27c273c1d91fea..71ca4ef442e08acad27b4d7873a7f153dc4c79dc 100644 (file)
@@ -8,17 +8,17 @@ git-upload-pack - Send objects packed back to git-fetch-pack
 
 SYNOPSIS
 --------
-'git upload-pack' [--strict] [--timeout=<n>] <directory>
+'git-upload-pack' [--strict] [--timeout=<n>] <directory>
 
 DESCRIPTION
 -----------
-Invoked by 'git-fetch-pack', learns what
+Invoked by 'git fetch-pack', learns what
 objects the other side is missing, and sends them after packing.
 
 This command is usually not invoked directly by the end user.
-The UI for the protocol is on the 'git-fetch-pack' side, and the
+The UI for the protocol is on the 'git fetch-pack' side, and the
 program pair is meant to be used to pull updates from a remote
-repository.  For push operations, see 'git-send-pack'.
+repository.  For push operations, see 'git send-pack'.
 
 
 OPTIONS
index ef6aa81872eb9e67eaf8183cfd5359d97e6c772b..bb981822a470b2df6c79156db8aa94569869e43e 100644 (file)
@@ -20,7 +20,7 @@ OPTIONS
        Cause the logical variables to be listed. In addition, all the
        variables of the git configuration file .git/config are listed
        as well. (However, the configuration variables listing functionality
-       is deprecated in favor of 'git config -l'.)
+       is deprecated in favor of `git config -l`.)
 
 EXAMPLE
 --------
index 97f7f9165eca2311e4806e9cab8ef18c8c94f7d5..916a38aa99020efe281907670932517a66d075e1 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Reads given idx file for packed git archive created with the
-'git-pack-objects' command and verifies idx file and the
+'git pack-objects' command and verifies idx file and the
 corresponding pack file.
 
 OPTIONS
index 84e70a02348105c98a004c080875ab8e85fe099c..dada21242c915148543dda8485b3322894aeb4c5 100644 (file)
@@ -11,7 +11,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Validates the gpg signature created by 'git-tag'.
+Validates the gpg signature created by 'git tag'.
 
 OPTIONS
 -------
index 278cf735279c13ab129ee28f1ef8e638ccf61dee..75720491b29e29135cc486b8280b2fbf0d166f4e 100644 (file)
@@ -62,7 +62,7 @@ browser.<tool>.path
 You can explicitly provide a full path to your preferred browser by
 setting the configuration variable 'browser.<tool>.path'. For example,
 you can configure the absolute path to firefox by setting
-'browser.firefox.path'. Otherwise, 'git-web--browse' assumes the tool
+'browser.firefox.path'. Otherwise, 'git web--browse' assumes the tool
 is available in PATH.
 
 browser.<tool>.cmd
@@ -71,7 +71,7 @@ browser.<tool>.cmd
 When the browser, specified by options or configuration variables, is
 not among the supported ones, then the corresponding
 'browser.<tool>.cmd' configuration variable will be looked up. If this
-variable exists then 'git-web--browse' will treat the specified tool
+variable exists then 'git web--browse' will treat the specified tool
 as a custom command and will use a shell eval to run the command with
 the URLs passed as arguments.
 
@@ -113,7 +113,7 @@ See linkgit:git-config[1] for more information about this.
 Author
 ------
 Written by Christian Couder <chriscool@tuxfamily.org> and the git-list
-<git@vger.kernel.org>, based on 'git-mergetool' by Theodore Y. Ts'o.
+<git@vger.kernel.org>, based on 'git mergetool' by Theodore Y. Ts'o.
 
 Documentation
 -------------
index cadfbd90403766d44598c8d96d89dc5a0e4e2ef8..ea753cdafce66c33a0a0e8d9a52d75c974194ad5 100644 (file)
@@ -13,8 +13,8 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Shows commit logs and diff output each commit introduces.  The
-command internally invokes 'git-rev-list' piped to
-'git-diff-tree', and takes command line options for both of
+command internally invokes 'git rev-list' piped to
+'git diff-tree', and takes command line options for both of
 these commands.
 
 This manual page describes only the most frequently used options.
index c8899d528adfe37459b9301e16faab90e7a7e63d..bfceacacb34262b73507a87fa23995d1183a3edc 100644 (file)
@@ -17,17 +17,17 @@ tree object is printed to standard output.
 
 The index must be in a fully merged state.
 
-Conceptually, 'git-write-tree' sync()s the current index contents
+Conceptually, 'git write-tree' sync()s the current index contents
 into a set of tree files.
 In order to have that match what is actually in your directory right
-now, you need to have done a 'git-update-index' phase before you did the
-'git-write-tree'.
+now, you need to have done a 'git update-index' phase before you did the
+'git write-tree'.
 
 
 OPTIONS
 -------
 --missing-ok::
-       Normally 'git-write-tree' ensures that the objects referenced by the
+       Normally 'git write-tree' ensures that the objects referenced by the
        directory exist in the object database.  This option disables this
        check.
 
index b6df39ba36eddd640c456293a0233894beb1da5c..8c5f5b05cb986960db392a0469cff357fa866211 100644 (file)
@@ -575,8 +575,8 @@ other
        linkgit:git-config[1].
 
 'GIT_SSH'::
-       If this environment variable is set then 'git-fetch'
-       and 'git-push' will use this command instead
+       If this environment variable is set then 'git fetch'
+       and 'git push' will use this command instead
        of 'ssh' when they need to connect to a remote system.
        The '$GIT_SSH' command will be given exactly two arguments:
        the 'username@host' (or just 'host') from the URL and the
@@ -592,8 +592,8 @@ for further details.
 
 'GIT_FLUSH'::
        If this environment variable is set to "1", then commands such
-       as 'git-blame' (in incremental mode), 'git-rev-list', 'git-log',
-       and 'git-whatchanged' will force a flush of the output stream
+       as 'git blame' (in incremental mode), 'git rev-list', 'git log',
+       and 'git whatchanged' will force a flush of the output stream
        after each commit-oriented record have been flushed.   If this
        variable is set to "0", the output of these commands will be done
        using completely buffered I/O.   If this environment variable is
index 5a45e51890b1f4b13db3d8082da83731dc76ceac..b396a871b32de52962eaa36f9d6be19d108d88ee 100644 (file)
@@ -88,9 +88,9 @@ Checking-out and checking-in
 
 These attributes affect how the contents stored in the
 repository are copied to the working tree files when commands
-such as 'git-checkout' and 'git-merge' run.  They also affect how
+such as 'git checkout' and 'git merge' run.  They also affect how
 git stores the contents you prepare in the working tree in the
-repository upon 'git-add' and 'git-commit'.
+repository upon 'git add' and 'git commit'.
 
 `crlf`
 ^^^^^^
@@ -148,16 +148,16 @@ an irreversible conversion.  The safety triggers to prevent such
 a conversion done to the files in the work tree, but there are a
 few exceptions.  Even though...
 
-- 'git-add' itself does not touch the files in the work tree, the
+- 'git add' itself does not touch the files in the work tree, the
   next checkout would, so the safety triggers;
 
-- 'git-apply' to update a text file with a patch does touch the files
+- 'git apply' to update a text file with a patch does touch the files
   in the work tree, but the operation is about text files and CRLF
   conversion is about fixing the line ending inconsistencies, so the
   safety does not trigger;
 
-- 'git-diff' itself does not touch the files in the work tree, it is
-  often run to inspect the changes you intend to next 'git-add'.  To
+- 'git diff' itself does not touch the files in the work tree, it is
+  often run to inspect the changes you intend to next 'git add'.  To
   catch potential problems early, safety triggers.
 
 
@@ -525,6 +525,23 @@ When left unspecified, the driver itself is used for both
 internal merge and the final merge.
 
 
+`conflict-marker-size`
+^^^^^^^^^^^^^^^^^^^^^^
+
+This attribute controls the length of conflict markers left in
+the work tree file during a conflicted merge.  Only setting to
+the value to a positive integer has any meaningful effect.
+
+For example, this line in `.gitattributes` can be used to tell the merge
+machinery to leave much longer (instead of the usual 7-character-long)
+conflict markers when merging the file `Documentation/git-merge.txt`
+results in a conflict.
+
+------------------------
+Documentation/git-merge.txt    conflict-marker-size=32
+------------------------
+
+
 Checking whitespace errors
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
index f762dca440d264cc8c9ccf34ad217e174a9bdf77..f7815e96a268f0eac7602e03255c28ea2fe04e6b 100644 (file)
@@ -27,6 +27,14 @@ interfaces on top of it called "porcelain". You may not want to use the
 plumbing directly very often, but it can be good to know what the
 plumbing does for when the porcelain isn't flushing.
 
+Back when this document was originally written, many porcelain
+commands were shell scripts. For simplicity, it still uses them as
+examples to illustrate how plumbing is fit together to form the
+porcelain commands. The source tree includes some of these scripts in
+contrib/examples/ for reference. Although these are not implemented as
+shell scripts anymore, the description of what the plumbing layer
+commands do is still valid.
+
 [NOTE]
 Deeper technical details are often marked as Notes, which you can
 skip on your first reading.
@@ -44,7 +52,7 @@ to import into git.
 For our first example, we're going to start a totally new repository from
 scratch, with no pre-existing files, and we'll call it 'git-tutorial'.
 To start up, create a subdirectory for it, change into that
-subdirectory, and initialize the git infrastructure with 'git-init':
+subdirectory, and initialize the git infrastructure with 'git init':
 
 ------------------------------------------------
 $ mkdir git-tutorial
@@ -139,7 +147,7 @@ but to actually check in your hard work, you will have to go through two steps:
  - commit that index file as an object.
 
 The first step is trivial: when you want to tell git about any changes
-to your working tree, you use the 'git-update-index' program. That
+to your working tree, you use the 'git update-index' program. That
 program normally just takes a list of filenames you want to update, but
 to avoid trivial mistakes, it refuses to add new entries to the index
 (or remove existing ones) unless you explicitly tell it that you're
@@ -173,14 +181,14 @@ and see two files:
 which correspond with the objects with names of `557db...` and
 `f24c7...` respectively.
 
-If you want to, you can use 'git-cat-file' to look at those objects, but
+If you want to, you can use 'git cat-file' to look at those objects, but
 you'll have to use the object name, not the filename of the object:
 
 ----------------
 $ git cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238
 ----------------
 
-where the `-t` tells 'git-cat-file' to tell you what the "type" of the
+where the `-t` tells 'git cat-file' to tell you what the "type" of the
 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
 
@@ -205,7 +213,7 @@ hexadecimal digits in most places.
 Anyway, as we mentioned previously, you normally never actually take a
 look at the objects themselves, and typing long 40-character hex
 names is not something you'd normally want to do. The above digression
-was just to show that 'git-update-index' did something magical, and
+was just to show that 'git update-index' did something magical, and
 actually saved away the contents of your files into the git object
 database.
 
@@ -228,7 +236,7 @@ $ echo "It's a new day for git" >>hello
 
 and you can now, since you told git about the previous state of `hello`, ask
 git what has changed in the tree compared to your old index, using the
-'git-diff-files' command:
+'git diff-files' command:
 
 ------------
 $ git diff-files
@@ -239,7 +247,7 @@ version of a 'diff', but that internal version really just tells you
 that it has noticed that "hello" has been modified, and that the old object
 contents it had have been replaced with something else.
 
-To make it readable, we can tell 'git-diff-files' to output the
+To make it readable, we can tell 'git diff-files' to output the
 differences as a patch, using the `-p` flag:
 
 ------------
@@ -255,7 +263,7 @@ index 557db03..263414f 100644
 
 i.e. the diff of the change we caused by adding another line to `hello`.
 
-In other words, 'git-diff-files' always shows us the difference between
+In other words, 'git diff-files' always shows us the difference between
 what is recorded in the index, and what is currently in the working
 tree. That's very useful.
 
@@ -283,7 +291,7 @@ that in two phases: creating a 'tree' object, and committing that 'tree'
 object as a 'commit' object together with an explanation of what the
 tree was all about, along with information of how we came to that state.
 
-Creating a tree object is trivial, and is done with 'git-write-tree'.
+Creating a tree object is trivial, and is done with 'git write-tree'.
 There are no options or other input: `git write-tree` will take the
 current index state, and write an object that describes that whole
 index. In other words, we're now tying together all the different
@@ -307,23 +315,23 @@ is not a "blob" object, but a "tree" object (you can also use
 `git cat-file` to actually output the raw object contents, but you'll see
 mainly a binary mess, so that's less interesting).
 
-However -- normally you'd never use 'git-write-tree' on its own, because
+However -- normally you'd never use 'git write-tree' on its own, because
 normally you always commit a tree into a commit object using the
-'git-commit-tree' command. In fact, it's easier to not actually use
-'git-write-tree' on its own at all, but to just pass its result in as an
-argument to 'git-commit-tree'.
+'git commit-tree' command. In fact, it's easier to not actually use
+'git write-tree' on its own at all, but to just pass its result in as an
+argument to 'git commit-tree'.
 
-'git-commit-tree' normally takes several arguments -- it wants to know
+'git commit-tree' normally takes several arguments -- it wants to know
 what the 'parent' of a commit was, but since this is the first commit
 ever in this new repository, and it has no parents, we only need to pass in
-the object name of the tree. However, 'git-commit-tree' also wants to get a
+the object name of the tree. However, 'git commit-tree' also wants to get a
 commit message on its standard input, and it will write out the resulting
 object name for the commit to its standard output.
 
 And this is where we create the `.git/refs/heads/master` file
 which is pointed at by `HEAD`. This file is supposed to contain
 the reference to the top-of-tree of the master branch, and since
-that's exactly what 'git-commit-tree' spits out, we can do this
+that's exactly what 'git commit-tree' spits out, we can do this
 all with a sequence of simple shell commands:
 
 ------------------------------------------------
@@ -345,11 +353,11 @@ instead, and it would have done the above magic scripting for you.
 Making a change
 ---------------
 
-Remember how we did the 'git-update-index' on file `hello` and then we
+Remember how we did the 'git update-index' on file `hello` and then we
 changed `hello` afterward, and could compare the new state of `hello` with the
 state we saved in the index file?
 
-Further, remember how I said that 'git-write-tree' writes the contents
+Further, remember how I said that 'git write-tree' writes the contents
 of the *index* file to the tree, and thus what we just committed was in
 fact the *original* contents of the file `hello`, not the new ones. We did
 that on purpose, to show the difference between the index state, and the
@@ -360,12 +368,12 @@ As before, if we do `git diff-files -p` in our git-tutorial project,
 we'll still see the same difference we saw last time: the index file
 hasn't changed by the act of committing anything. However, now that we
 have committed something, we can also learn to use a new command:
-'git-diff-index'.
+'git diff-index'.
 
-Unlike 'git-diff-files', which showed the difference between the index
-file and the working tree, 'git-diff-index' shows the differences
+Unlike 'git diff-files', which showed the difference between the index
+file and the working tree, 'git diff-index' shows the differences
 between a committed *tree* and either the index file or the working
-tree. In other words, 'git-diff-index' wants a tree to be diffed
+tree. In other words, 'git diff-index' wants a tree to be diffed
 against, and before we did the commit, we couldn't do that, because we
 didn't have anything to diff against.
 
@@ -375,7 +383,7 @@ But now we can do
 $ git diff-index -p HEAD
 ----------------
 
-(where `-p` has the same meaning as it did in 'git-diff-files'), and it
+(where `-p` has the same meaning as it did in 'git diff-files'), and it
 will show us the same difference, but for a totally different reason.
 Now we're comparing the working tree not against the index file,
 but against the tree we just wrote. It just so happens that those two
@@ -390,7 +398,7 @@ $ git diff HEAD
 
 which ends up doing the above for you.
 
-In other words, 'git-diff-index' normally compares a tree against the
+In other words, 'git diff-index' normally compares a tree against the
 working tree, but when given the `\--cached` flag, it is told to
 instead compare against just the index cache contents, and ignore the
 current working tree state entirely. Since we just wrote the index
@@ -399,7 +407,7 @@ an empty set of differences, and that's exactly what it does.
 
 [NOTE]
 ================
-'git-diff-index' really always uses the index for its
+'git diff-index' really always uses the index for its
 comparisons, and saying that it compares a tree against the working
 tree is thus not strictly accurate. In particular, the list of
 files to compare (the "meta-data") *always* comes from the index file,
@@ -428,11 +436,11 @@ $ git update-index hello
 (note how we didn't need the `\--add` flag this time, since git knew
 about the file already).
 
-Note what happens to the different 'git-diff-\*' versions here. After
+Note what happens to the different 'git diff-\*' versions here. After
 we've updated `hello` in the index, `git diff-files -p` now shows no
 differences, but `git diff-index -p HEAD` still *does* show that the
 current state is different from the state we committed. In fact, now
-'git-diff-index' shows the same difference whether we use the `--cached`
+'git diff-index' shows the same difference whether we use the `--cached`
 flag or not, since now the index is coherent with the working tree.
 
 Now, since we've updated `hello` in the index, we can commit the new
@@ -460,7 +468,7 @@ You've now made your first real git commit. And if you're interested in
 looking at what `git commit` really does, feel free to investigate:
 it's a few very simple shell scripts to generate the helpful (?) commit
 message headers, and a few one-liners that actually do the
-commit itself ('git-commit').
+commit itself ('git commit').
 
 
 Inspecting Changes
@@ -468,9 +476,9 @@ Inspecting Changes
 
 While creating changes is useful, it's even more useful if you can tell
 later what changed. The most useful command for this is another of the
-'diff' family, namely 'git-diff-tree'.
+'diff' family, namely 'git diff-tree'.
 
-'git-diff-tree' can be given two arbitrary trees, and it will tell you the
+'git diff-tree' can be given two arbitrary trees, and it will tell you the
 differences between them. Perhaps even more commonly, though, you can
 give it just a single commit object, and it will figure out the parent
 of that commit itself, and show the difference directly. Thus, to get
@@ -518,15 +526,15 @@ various diff-\* commands compare things.
                     +-----------+
 ============
 
-More interestingly, you can also give 'git-diff-tree' the `--pretty` flag,
+More interestingly, you can also give 'git diff-tree' the `--pretty` flag,
 which tells it to also show the commit message and author and date of the
 commit, and you can tell it to show a whole series of diffs.
 Alternatively, you can tell it to be "silent", and not show the diffs at
 all, but just show the actual commit message.
 
-In fact, together with the 'git-rev-list' program (which generates a
-list of revisions), 'git-diff-tree' ends up being a veritable fount of
-changes. A trivial (but very useful) script called 'git-whatchanged' is
+In fact, together with the 'git rev-list' program (which generates a
+list of revisions), 'git diff-tree' ends up being a veritable fount of
+changes. A trivial (but very useful) script called 'git whatchanged' is
 included with git which does exactly this, and shows a log of recent
 activities.
 
@@ -553,14 +561,14 @@ When using the above two commands, the initial commit will be shown.
 If this is a problem because it is huge, you can hide it by setting
 the log.showroot configuration variable to false. Having this, you
 can still show it for each command just adding the `\--root` option,
-which is a flag for 'git-diff-tree' accepted by both commands.
+which is a flag for 'git diff-tree' accepted by both commands.
 
 With that, you should now be having some inkling of what git does, and
 can explore on your own.
 
 [NOTE]
 Most likely, you are not directly using the core
-git Plumbing commands, but using Porcelain such as 'git-add', `git-rm'
+git Plumbing commands, but using Porcelain such as 'git add', `git-rm'
 and `git-commit'.
 
 
@@ -595,7 +603,7 @@ pointer to the state you want to tag, but also a small tag name and
 message, along with optionally a PGP signature that says that yes,
 you really did
 that tag. You create these annotated tags with either the `-a` or
-`-s` flag to 'git-tag':
+`-s` flag to 'git tag':
 
 ----------------
 $ git tag -s <tagname>
@@ -642,7 +650,7 @@ and it will be gone. There's no external repository, and there's no
 history outside the project you created.
 
  - if you want to move or duplicate a git repository, you can do so. There
-   is 'git-clone' command, but if all you want to do is just to
+   is 'git clone' command, but if all you want to do is just to
    create a copy of your repository (with all the full history that
    went along with it), you can do so with a regular
    `cp -a git-tutorial new-git-tutorial`.
@@ -666,7 +674,7 @@ When copying a remote repository, you'll want to at a minimum update the
 index cache when you do this, and especially with other peoples'
 repositories you often want to make sure that the index cache is in some
 known state (you don't know *what* they've done and not yet checked in),
-so usually you'll precede the 'git-update-index' with a
+so usually you'll precede the 'git update-index' with a
 
 ----------------
 $ git read-tree --reset HEAD
@@ -674,7 +682,7 @@ $ git update-index --refresh
 ----------------
 
 which will force a total index re-build from the tree pointed to by `HEAD`.
-It resets the index contents to `HEAD`, and then the 'git-update-index'
+It resets the index contents to `HEAD`, and then the 'git update-index'
 makes sure to match up all index entries with the checked-out files.
 If the original repository had uncommitted changes in its
 working tree, `git update-index --refresh` notices them and
@@ -689,8 +697,8 @@ $ git reset
 and in fact a lot of the common git command combinations can be scripted
 with the `git xyz` interfaces.  You can learn things by just looking
 at what the various git scripts do.  For example, `git reset` used to be
-the above two lines implemented in 'git-reset', but some things like
-'git-status' and 'git-commit' are slightly more complex scripts around
+the above two lines implemented in 'git reset', but some things like
+'git status' and 'git commit' are slightly more complex scripts around
 the basic git commands.
 
 Many (most?) public remote repositories will not contain any of
@@ -729,7 +737,7 @@ where the `-u` flag means that you want the checkout to keep the index
 up-to-date (so that you don't have to refresh it afterward), and the
 `-a` flag means "check out all files" (if you have a stale copy or an
 older version of a checked out tree you may also need to add the `-f`
-flag first, to tell 'git-checkout-index' to *force* overwriting of any old
+flag first, to tell 'git checkout-index' to *force* overwriting of any old
 files).
 
 Again, this can all be simplified with
@@ -776,7 +784,7 @@ to it.
 ================================================
 If you make the decision to start your new branch at some
 other point in the history than the current `HEAD`, you can do so by
-just telling 'git-checkout' what the base of the checkout would be.
+just telling 'git checkout' what the base of the checkout would be.
 In other words, if you have an earlier tag or branch, you'd just do
 
 ------------
@@ -819,7 +827,7 @@ $ git branch <branchname> [startingpoint]
 
 which will simply _create_ the branch, but will not do anything further.
 You can then later -- once you decide that you want to actually develop
-on that branch -- switch to that branch with a regular 'git-checkout'
+on that branch -- switch to that branch with a regular 'git checkout'
 with the branchname as the argument.
 
 
@@ -881,7 +889,7 @@ source.
 Anyway, let's exit 'gitk' (`^Q` or the File menu), and decide that we want
 to merge the work we did on the `mybranch` branch into the `master`
 branch (which is currently our `HEAD` too). To do that, there's a nice
-script called 'git-merge', which wants to know which branches you want
+script called 'git merge', which wants to know which branches you want
 to resolve and what the merge is all about:
 
 ------------
@@ -925,7 +933,7 @@ $ git commit -i hello
 
 which will very loudly warn you that you're now committing a merge
 (which is correct, so never mind), and you can write a small merge
-message about your adventures in 'git-merge'-land.
+message about your adventures in 'git merge'-land.
 
 After you're done, start up `gitk \--all` to see graphically what the
 history looks like. Notice that `mybranch` still exists, and you can
@@ -967,21 +975,21 @@ branch head.  Please see linkgit:git-rev-parse[1] if you want to
 see more complex cases.
 
 [NOTE]
-Without the '--more=1' option, 'git-show-branch' would not output the
+Without the '--more=1' option, 'git show-branch' would not output the
 '[master^]' commit, as '[mybranch]' commit is a common ancestor of
 both 'master' and 'mybranch' tips.  Please see linkgit:git-show-branch[1]
 for details.
 
 [NOTE]
 If there were more commits on the 'master' branch after the merge, the
-merge commit itself would not be shown by 'git-show-branch' by
+merge commit itself would not be shown by 'git show-branch' by
 default.  You would need to provide '--sparse' option to make the
 merge commit visible in this case.
 
 Now, let's pretend you are the one who did all the work in
 `mybranch`, and the fruit of your hard work has finally been merged
 to the `master` branch. Let's go back to `mybranch`, and run
-'git-merge' to get the "upstream changes" back to your branch.
+'git merge' to get the "upstream changes" back to your branch.
 
 ------------
 $ git checkout mybranch
@@ -1023,12 +1031,12 @@ Merging external work
 It's usually much more common that you merge with somebody else than
 merging with your own branches, so it's worth pointing out that git
 makes that very easy too, and in fact, it's not that different from
-doing a 'git-merge'. In fact, a remote merge ends up being nothing
+doing a 'git merge'. In fact, a remote merge ends up being nothing
 more than "fetch the work from a remote repository into a temporary tag"
-followed by a 'git-merge'.
+followed by a 'git merge'.
 
 Fetching from a remote repository is done by, unsurprisingly,
-'git-fetch':
+'git fetch':
 
 ----------------
 $ git fetch <remote-repository>
@@ -1095,7 +1103,7 @@ The 'commit walkers' are sometimes also called 'dumb
 transports', because they do not require any git aware smart
 server like git Native transport does.  Any stock HTTP server
 that does not even support directory index would suffice.  But
-you must prepare your repository with 'git-update-server-info'
+you must prepare your repository with 'git update-server-info'
 to help dumb transport downloaders.
 
 Once you fetch from the remote repository, you `merge` that
@@ -1115,7 +1123,7 @@ argument.
 [NOTE]
 You could do without using any branches at all, by
 keeping as many local repositories as you would like to have
-branches, and merging between them with 'git-pull', just like
+branches, and merging between them with 'git pull', just like
 you merge between branches. The advantage of this approach is
 that it lets you keep a set of files for each `branch` checked
 out and you may find it easier to switch back and forth if you
@@ -1132,7 +1140,7 @@ like this:
 $ git config remote.linus.url http://www.kernel.org/pub/scm/git/git.git/
 ------------------------------------------------
 
-and use the "linus" keyword with 'git-pull' instead of the full URL.
+and use the "linus" keyword with 'git pull' instead of the full URL.
 
 Examples.
 
@@ -1168,7 +1176,7 @@ $ git show-branch --more=2 master mybranch
 +* [master^] Some fun.
 ------------
 
-Remember, before running 'git-merge', our `master` head was at
+Remember, before running 'git merge', our `master` head was at
 "Some fun." commit, while our `mybranch` head was at "Some
 work." commit.
 
@@ -1195,7 +1203,7 @@ Now we are ready to experiment with the merge by hand.
 
 `git merge` command, when merging two branches, uses 3-way merge
 algorithm.  First, it finds the common ancestor between them.
-The command it uses is 'git-merge-base':
+The command it uses is 'git merge-base':
 
 ------------
 $ mb=$(git merge-base HEAD mybranch)
@@ -1219,7 +1227,7 @@ this:
 $ git read-tree -m -u $mb HEAD mybranch
 ------------
 
-This is the same 'git-read-tree' command we have already seen,
+This is the same 'git read-tree' command we have already seen,
 but it takes three trees, unlike previous examples.  This reads
 the contents of each tree into different 'stage' in the index
 file (the first tree goes to stage 1, the second to stage 2,
@@ -1260,8 +1268,8 @@ $ git ls-files --unmerged
 
 The next step of merging is to merge these three versions of the
 file, using 3-way merge.  This is done by giving
-'git-merge-one-file' command as one of the arguments to
-'git-merge-index' command:
+'git merge-one-file' command as one of the arguments to
+'git merge-index' command:
 
 ------------
 $ git merge-index git-merge-one-file hello
@@ -1270,7 +1278,7 @@ ERROR: Merge conflict in hello
 fatal: merge program failed
 ------------
 
-'git-merge-one-file' script is called with parameters to
+'git merge-one-file' script is called with parameters to
 describe those three versions, and is responsible to leave the
 merge results in the working tree.
 It is a fairly straightforward shell script, and
@@ -1289,9 +1297,9 @@ $ git ls-files --stage
 ------------
 
 This is the state of the index file and the working file after
-'git-merge' returns control back to you, leaving the conflicting
+'git merge' returns control back to you, leaving the conflicting
 merge for you to resolve.  Notice that the path `hello` is still
-unmerged, and what you see with 'git-diff' at this point is
+unmerged, and what you see with 'git diff' at this point is
 differences since stage 2 (i.e. your version).
 
 
@@ -1328,8 +1336,8 @@ into it later. Obviously, this repository creation needs to be
 done only once.
 
 [NOTE]
-'git-push' uses a pair of commands,
-'git-send-pack' on your local machine, and 'git-receive-pack'
+'git push' uses a pair of commands,
+'git send-pack' on your local machine, and 'git-receive-pack'
 on the remote machine. The communication between the two over
 the network internally uses an SSH connection.
 
@@ -1344,7 +1352,7 @@ $ mkdir my-git.git
 ------------
 
 Then, make that directory into a git repository by running
-'git-init', but this time, since its name is not the usual
+'git init', but this time, since its name is not the usual
 `.git`, we do things slightly differently:
 
 ------------
@@ -1407,7 +1415,7 @@ $ git repack
 
 will do it for you. If you followed the tutorial examples, you
 would have accumulated about 17 objects in `.git/objects/??/`
-directories by now. 'git-repack' tells you how many objects it
+directories by now. 'git repack' tells you how many objects it
 packed, and stores the packed file in `.git/objects/pack`
 directory.
 
@@ -1420,7 +1428,7 @@ them together. The former holds all the data from the objects
 in the pack, and the latter holds the index for random
 access.
 
-If you are paranoid, running 'git-verify-pack' command would
+If you are paranoid, running 'git verify-pack' command would
 detect if you have a corrupt pack, but do not worry too much.
 Our programs are always perfect ;-).
 
@@ -1487,17 +1495,17 @@ If other people are pulling from your repository over dumb
 transport protocols (HTTP), you need to keep this repository
 'dumb transport friendly'.  After `git init`,
 `$GIT_DIR/hooks/post-update.sample` copied from the standard templates
-would contain a call to 'git-update-server-info'
+would contain a call to 'git update-server-info'
 but you need to manually enable the hook with
 `mv post-update.sample post-update`.  This makes sure
-'git-update-server-info' keeps the necessary files up-to-date.
+'git update-server-info' keeps the necessary files up-to-date.
 
 3. Push into the public repository from your primary
    repository.
 
-4. 'git-repack' the public repository. This establishes a big
+4. 'git repack' the public repository. This establishes a big
    pack that contains the initial set of objects as the
-   baseline, and possibly 'git-prune' if the transport
+   baseline, and possibly 'git prune' if the transport
    used for pulling from your repository supports packed
    repositories.
 
@@ -1511,14 +1519,14 @@ You can repack this private repository whenever you feel like.
 6. Push your changes to the public repository, and announce it
    to the public.
 
-7. Every once in a while, 'git-repack' the public repository.
+7. Every once in a while, 'git repack' the public repository.
    Go back to step 5. and continue working.
 
 
 A recommended work cycle for a "subsystem maintainer" who works
 on that project and has an own "public repository" goes like this:
 
-1. Prepare your work repository, by 'git-clone' the public
+1. Prepare your work repository, by 'git clone' the public
    repository of the "project lead". The URL used for the
    initial cloning is stored in the remote.origin.url
    configuration variable.
@@ -1533,7 +1541,7 @@ on that project and has an own "public repository" goes like this:
    point at the repository you are borrowing from.
 
 4. Push into the public repository from your primary
-   repository. Run 'git-repack', and possibly 'git-prune' if the
+   repository. Run 'git repack', and possibly 'git prune' if the
    transport used for pulling from your repository supports
    packed repositories.
 
@@ -1550,7 +1558,7 @@ like.
    "project lead" and possibly your "sub-subsystem
    maintainers" to pull from it.
 
-7. Every once in a while, 'git-repack' the public repository.
+7. Every once in a while, 'git repack' the public repository.
    Go back to step 5. and continue working.
 
 
@@ -1558,7 +1566,7 @@ A recommended work cycle for an "individual developer" who does
 not have a "public" repository is somewhat different. It goes
 like this:
 
-1. Prepare your work repository, by 'git-clone' the public
+1. Prepare your work repository, by 'git clone' the public
    repository of the "project lead" (or a "subsystem
    maintainer", if you work on a subsystem). The URL used for
    the initial cloning is stored in the remote.origin.url
@@ -1656,8 +1664,8 @@ $ git reset --hard master~2
 ------------
 
 You can make sure `git show-branch` matches the state before
-those two 'git-merge' you just did.  Then, instead of running
-two 'git-merge' commands in a row, you would merge these two
+those two 'git merge' you just did.  Then, instead of running
+two 'git merge' commands in a row, you would merge these two
 branch heads (this is known as 'making an Octopus'):
 
 ------------
index 0e49c1c03776af30f0509679f720273061aaa7b3..d861ec452f8f115017830422b4b459861f8bd703 100644 (file)
@@ -47,25 +47,25 @@ them first before running git pull.
 [NOTE]
 ================================
 The 'pull' command knows where to get updates from because of certain
-configuration variables that were set by the first 'git-clone'
+configuration variables that were set by the first 'git clone'
 command; see `git config -l` and the linkgit:git-config[1] man
 page for details.
 ================================
 
 You can update the shared repository with your changes by first committing
-your changes, and then using the 'git-push' command:
+your changes, and then using the 'git push' command:
 
 ------------------------------------------------
 $ git push origin master
 ------------------------------------------------
 
 to "push" those commits to the shared repository.  If someone else has
-updated the repository more recently, 'git-push', like 'cvs commit', will
+updated the repository more recently, 'git push', like 'cvs commit', will
 complain, in which case you must pull any changes before attempting the
 push again.
 
-In the 'git-push' command above we specify the name of the remote branch
-to update (`master`).  If we leave that out, 'git-push' tries to update
+In the 'git push' command above we specify the name of the remote branch
+to update (`master`).  If we leave that out, 'git push' tries to update
 any branches in the remote repository that have the same name as a branch
 in the local repository.  So the last 'push' can be done with either of:
 
index e8041bc08f403d2752c41aaaf482a9700038fbef..dcdea54df3450f82cc898db93474e4be4981187c 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 
-The diff commands 'git-diff-index', 'git-diff-files', and 'git-diff-tree'
+The diff commands 'git diff-index', 'git diff-files', and 'git diff-tree'
 can be told to manipulate differences they find in
 unconventional ways before showing 'diff' output.  The manipulation
 is collectively called "diffcore transformation".  This short note
@@ -23,18 +23,18 @@ that is easier to understand than the conventional kind.
 The chain of operation
 ----------------------
 
-The 'git-diff-{asterisk}' family works by first comparing two sets of
+The 'git diff-{asterisk}' family works by first comparing two sets of
 files:
 
- - 'git-diff-index' compares contents of a "tree" object and the
+ - 'git diff-index' compares contents of a "tree" object and the
    working directory (when '\--cached' flag is not used) or a
    "tree" object and the index file (when '\--cached' flag is
    used);
 
- - 'git-diff-files' compares contents of the index file and the
+ - 'git diff-files' compares contents of the index file and the
    working directory;
 
- - 'git-diff-tree' compares contents of two "tree" objects;
+ - 'git diff-tree' compares contents of two "tree" objects;
 
 In all of these cases, the commands themselves first optionally limit
 the two sets of files by any pathspecs given on their command-lines,
@@ -74,12 +74,12 @@ into another list.  There are currently 5 such transformations:
 - diffcore-pickaxe
 - diffcore-order
 
-These are applied in sequence.  The set of filepairs 'git-diff-{asterisk}'
+These are applied in sequence.  The set of filepairs 'git diff-{asterisk}'
 commands find are used as the input to diffcore-break, and
 the output from diffcore-break is used as the input to the
 next transformation.  The final result is then passed to the
 output routine and generates either diff-raw format (see Output
-format sections of the manual for 'git-diff-{asterisk}' commands) or
+format sections of the manual for 'git diff-{asterisk}' commands) or
 diff-patch format.
 
 
@@ -87,7 +87,7 @@ diffcore-break: For Splitting Up "Complete Rewrites"
 ----------------------------------------------------
 
 The second transformation in the chain is diffcore-break, and is
-controlled by the -B option to the 'git-diff-{asterisk}' commands.  This is
+controlled by the -B option to the 'git diff-{asterisk}' commands.  This is
 used to detect a filepair that represents "complete rewrite" and
 break such filepair into two filepairs that represent delete and
 create.  E.g.  If the input contained this filepair:
@@ -123,7 +123,7 @@ diffcore-rename: For Detection Renames and Copies
 
 This transformation is used to detect renames and copies, and is
 controlled by the -M option (to detect renames) and the -C option
-(to detect copies as well) to the 'git-diff-{asterisk}' commands.  If the
+(to detect copies as well) to the 'git diff-{asterisk}' commands.  If the
 input contained these filepairs:
 
 ------------------------------------------------
@@ -168,11 +168,11 @@ number after the "-M" or "-C" option (e.g. "-M8" to tell it to use
 8/10 = 80%).
 
 Note.  When the "-C" option is used with `\--find-copies-harder`
-option, 'git-diff-{asterisk}' commands feed unmodified filepairs to
+option, 'git diff-{asterisk}' commands feed unmodified filepairs to
 diffcore mechanism as well as modified ones.  This lets the copy
 detector consider unmodified files as copy source candidates at
 the expense of making it slower.  Without `\--find-copies-harder`,
-'git-diff-{asterisk}' commands can detect copies only if the file that was
+'git diff-{asterisk}' commands can detect copies only if the file that was
 copied happened to have been modified in the same changeset.
 
 
@@ -223,7 +223,7 @@ diffcore-pickaxe: For Detecting Addition/Deletion of Specified String
 
 This transformation is used to find filepairs that represent
 changes that touch a specified string, and is controlled by the
--S option and the `\--pickaxe-all` option to the 'git-diff-{asterisk}'
+-S option and the `\--pickaxe-all` option to the 'git diff-{asterisk}'
 commands.
 
 When diffcore-pickaxe is in use, it checks if there are
@@ -246,7 +246,7 @@ diffcore-order: For Sorting the Output Based on Filenames
 
 This is used to reorder the filepairs according to the user's
 (or project's) taste, and is controlled by the -O option to the
-'git-diff-{asterisk}' commands.
+'git diff-{asterisk}' commands.
 
 This takes a text file each of whose lines is a shell glob
 pattern.  Filepairs that match a glob pattern on an earlier line
index 29eeae77ca8716a9b07362f164104e3afee4f192..87e2c035a7bf1bfff8024db6f4d971ddb5b44e57 100644 (file)
@@ -15,7 +15,7 @@ DESCRIPTION
 
 Hooks are little scripts you can place in `$GIT_DIR/hooks`
 directory to trigger action at certain points.  When
-'git-init' is run, a handful of example hooks are copied into the
+'git init' is run, a handful of example hooks are copied into the
 `hooks` directory of the new repository, but by default they are
 all disabled.  To enable a hook, rename it by removing its `.sample`
 suffix.
@@ -32,10 +32,10 @@ HOOKS
 applypatch-msg
 ~~~~~~~~~~~~~~
 
-This hook is invoked by 'git-am' script.  It takes a single
+This hook is invoked by 'git am' script.  It takes a single
 parameter, the name of the file that holds the proposed commit
 log message.  Exiting with non-zero status causes
-'git-am' to abort before applying the patch.
+'git am' to abort before applying the patch.
 
 The hook is allowed to edit the message file in place, and can
 be used to normalize the message into some project standard
@@ -48,7 +48,7 @@ The default 'applypatch-msg' hook, when enabled, runs the
 pre-applypatch
 ~~~~~~~~~~~~~~
 
-This hook is invoked by 'git-am'.  It takes no parameter, and is
+This hook is invoked by 'git am'.  It takes no parameter, and is
 invoked after the patch is applied, but before a commit is made.
 
 If it exits with non-zero status, then the working tree will not be
@@ -63,33 +63,33 @@ The default 'pre-applypatch' hook, when enabled, runs the
 post-applypatch
 ~~~~~~~~~~~~~~~
 
-This hook is invoked by 'git-am'.  It takes no parameter,
+This hook is invoked by 'git am'.  It takes no parameter,
 and is invoked after the patch is applied and a commit is made.
 
 This hook is meant primarily for notification, and cannot affect
-the outcome of 'git-am'.
+the outcome of 'git am'.
 
 pre-commit
 ~~~~~~~~~~
 
-This hook is invoked by 'git-commit', and can be bypassed
+This hook is invoked by 'git commit', and can be bypassed
 with `\--no-verify` option.  It takes no parameter, and is
 invoked before obtaining the proposed commit log message and
 making a commit.  Exiting with non-zero status from this script
-causes the 'git-commit' to abort.
+causes the 'git commit' to abort.
 
 The default 'pre-commit' hook, when enabled, catches introduction
 of lines with trailing whitespaces and aborts the commit when
 such a line is found.
 
-All the 'git-commit' hooks are invoked with the environment
+All the 'git commit' hooks are invoked with the environment
 variable `GIT_EDITOR=:` if the command will not bring up an editor
 to modify the commit message.
 
 prepare-commit-msg
 ~~~~~~~~~~~~~~~~~~
 
-This hook is invoked by 'git-commit' right after preparing the
+This hook is invoked by 'git commit' right after preparing the
 default log message, and before the editor is started.
 
 It takes one to three parameters.  The first is the name of the file
@@ -101,7 +101,7 @@ commit is a merge or a `.git/MERGE_MSG` file exists); `squash`
 (if a `.git/SQUASH_MSG` file exists); or `commit`, followed by
 a commit SHA1 (if a `-c`, `-C` or `\--amend` option was given).
 
-If the exit status is non-zero, 'git-commit' will abort.
+If the exit status is non-zero, 'git commit' will abort.
 
 The purpose of the hook is to edit the message file in place, and
 it is not suppressed by the `\--no-verify` option.  A non-zero exit
@@ -114,10 +114,10 @@ out the `Conflicts:` part of a merge's commit message.
 commit-msg
 ~~~~~~~~~~
 
-This hook is invoked by 'git-commit', and can be bypassed
+This hook is invoked by 'git commit', and can be bypassed
 with `\--no-verify` option.  It takes a single parameter, the
 name of the file that holds the proposed commit log message.
-Exiting with non-zero status causes the 'git-commit' to
+Exiting with non-zero status causes the 'git commit' to
 abort.
 
 The hook is allowed to edit the message file in place, and can
@@ -131,30 +131,30 @@ The default 'commit-msg' hook, when enabled, detects duplicate
 post-commit
 ~~~~~~~~~~~
 
-This hook is invoked by 'git-commit'.  It takes no
+This hook is invoked by 'git commit'.  It takes no
 parameter, and is invoked after a commit is made.
 
 This hook is meant primarily for notification, and cannot affect
-the outcome of 'git-commit'.
+the outcome of 'git commit'.
 
 pre-rebase
 ~~~~~~~~~~
 
-This hook is called by 'git-rebase' and can be used to prevent a branch
+This hook is called by 'git rebase' and can be used to prevent a branch
 from getting rebased.
 
 
 post-checkout
 ~~~~~~~~~~~~~
 
-This hook is invoked when a 'git-checkout' is run after having updated the
+This hook is invoked when a 'git checkout' is run after having updated the
 worktree.  The hook is given three parameters: the ref of the previous HEAD,
 the ref of the new HEAD (which may or may not have changed), and a flag
 indicating whether the checkout was a branch checkout (changing branches,
 flag=1) or a file checkout (retrieving a file from the index, flag=0).
-This hook cannot affect the outcome of 'git-checkout'.
+This hook cannot affect the outcome of 'git checkout'.
 
-It is also run after 'git-clone', unless the --no-checkout (-n) option is
+It is also run after 'git clone', unless the --no-checkout (-n) option is
 used. The first parameter given to the hook is the null-ref, the second the
 ref of the new HEAD and the flag is always 1.
 
@@ -165,10 +165,10 @@ properties.
 post-merge
 ~~~~~~~~~~
 
-This hook is invoked by 'git-merge', which happens when a 'git-pull'
+This hook is invoked by 'git merge', which happens when a 'git pull'
 is done on a local repository.  The hook takes a single parameter, a status
 flag specifying whether or not the merge being done was a squash merge.
-This hook cannot affect the outcome of 'git-merge' and is not executed,
+This hook cannot affect the outcome of 'git merge' and is not executed,
 if the merge failed due to conflicts.
 
 This hook can be used in conjunction with a corresponding pre-commit hook to
@@ -181,7 +181,7 @@ pre-receive
 ~~~~~~~~~~~
 
 This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git-push' is done on a local repository.
+which happens when a 'git push' is done on a local repository.
 Just before starting to update refs on the remote repository, the
 pre-receive hook is invoked.  Its exit status determines the success
 or failure of the update.
@@ -202,7 +202,7 @@ updated. If the hook exits with zero, updating of individual refs can
 still be prevented by the <<update,'update'>> hook.
 
 Both standard output and standard error output are forwarded to
-'git-send-pack' on the other end, so you can simply `echo` messages
+'git send-pack' on the other end, so you can simply `echo` messages
 for the user.
 
 [[update]]
@@ -210,7 +210,7 @@ update
 ~~~~~~
 
 This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git-push' is done on a local repository.
+which happens when a 'git push' is done on a local repository.
 Just before updating the ref on the remote repository, the update hook
 is invoked.  Its exit status determines the success or failure of
 the ref update.
@@ -241,7 +241,7 @@ implement access control which is finer grained than the one
 based on filesystem group.
 
 Both standard output and standard error output are forwarded to
-'git-send-pack' on the other end, so you can simply `echo` messages
+'git send-pack' on the other end, so you can simply `echo` messages
 for the user.
 
 The default 'update' hook, when enabled--and with
@@ -253,7 +253,7 @@ post-receive
 ~~~~~~~~~~~~
 
 This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git-push' is done on a local repository.
+which happens when a 'git push' is done on a local repository.
 It executes on the remote repository once after all the refs have
 been updated.
 
@@ -270,7 +270,7 @@ both old and new values of all the refs in addition to their
 names.
 
 Both standard output and standard error output are forwarded to
-'git-send-pack' on the other end, so you can simply `echo` messages
+'git send-pack' on the other end, so you can simply `echo` messages
 for the user.
 
 The default 'post-receive' hook is empty, but there is
@@ -283,7 +283,7 @@ post-update
 ~~~~~~~~~~~
 
 This hook is invoked by 'git-receive-pack' on the remote repository,
-which happens when a 'git-push' is done on a local repository.
+which happens when a 'git push' is done on a local repository.
 It executes on the remote repository once after all the refs have
 been updated.
 
@@ -301,20 +301,20 @@ updated values of the refs. You might consider it instead if you need
 them.
 
 When enabled, the default 'post-update' hook runs
-'git-update-server-info' to keep the information used by dumb
+'git update-server-info' to keep the information used by dumb
 transports (e.g., HTTP) up-to-date.  If you are publishing
 a git repository that is accessible via HTTP, you should
 probably enable this hook.
 
 Both standard output and standard error output are forwarded to
-'git-send-pack' on the other end, so you can simply `echo` messages
+'git send-pack' on the other end, so you can simply `echo` messages
 for the user.
 
 pre-auto-gc
 ~~~~~~~~~~~
 
-This hook is invoked by 'git-gc --auto'. It takes no parameter, and
-exiting with non-zero status from this script causes the 'git-gc --auto'
+This hook is invoked by 'git gc --auto'. It takes no parameter, and
+exiting with non-zero status from this script causes the 'git gc --auto'
 to abort.
 
 GIT
index 7df3cef46f3cc2a0cb0e0d7197b431b01b3af4fd..98c459dc82be46be706b1d7545d7ec95efd8790c 100644 (file)
@@ -56,10 +56,10 @@ the user's editor of choice) generally go into a file specified by
 `core.excludesfile` in the user's `~/.gitconfig`.
 
 The underlying git plumbing tools, such as
-'git-ls-files' and 'git-read-tree', read
+'git ls-files' and 'git read-tree', read
 `gitignore` patterns specified by command-line options, or from
 files specified by command-line options.  Higher-level git
-tools, such as 'git-status' and 'git-add',
+tools, such as 'git status' and 'git add',
 use patterns from the sources specified above.
 
 Patterns have the following format:
index cf465cb47e1f3fbb7452568591874cf865a16c2c..99baa24a2d5c8c703d3ad5ce7442bb8363359cd6 100644 (file)
@@ -22,7 +22,7 @@ git repository.
 OPTIONS
 -------
 To control which revisions to show, the command takes options applicable to
-the 'git-rev-list' command (see linkgit:git-rev-list[1]).
+the 'git rev-list' command (see linkgit:git-rev-list[1]).
 This manual page describes only the most
 frequently used options.
 
index 1befca98d49583b42e652b4ce21fccb89b5b5cdb..3cd32d6803874909d671a2d3f48c1d5701ce89cf 100644 (file)
@@ -64,7 +64,7 @@ objects/info/packs::
        are available in this object store.  Whenever a pack is
        added or removed, `git update-server-info` should be run
        to keep this file up-to-date if the repository is
-       published for dumb transports.  'git-repack' does this
+       published for dumb transports.  'git repack' does this
        by default.
 
 objects/info/alternates::
@@ -85,7 +85,7 @@ objects/info/http-alternates::
 
 refs::
        References are stored in subdirectories of this
-       directory.  The 'git-prune' command knows to keep
+       directory.  The 'git prune' command knows to keep
        objects reachable from refs found in this directory and
        its subdirectories.
 
@@ -125,7 +125,7 @@ details.
 
 branches::
        A slightly deprecated way to store shorthands to be used
-       to specify URL to 'git-fetch', 'git-pull' and 'git-push'
+       to specify URL to 'git fetch', 'git pull' and 'git push'
        commands is to store a file in `branches/<name>` and
        give 'name' to these commands in place of 'repository'
        argument.
@@ -133,7 +133,7 @@ branches::
 hooks::
        Hooks are customization scripts used by various git
        commands.  A handful of sample hooks are installed when
-       'git-init' is run, but all of them are disabled by
+       'git init' is run, but all of them are disabled by
        default.  To enable, the `.sample` suffix has to be
        removed from the filename by renaming.
        Read linkgit:githooks[5] for more details about
@@ -151,10 +151,10 @@ info/refs::
        This file helps dumb transports discover what refs are
        available in this repository.  If the repository is
        published for dumb transports, this file should be
-       regenerated by 'git-update-server-info' every time a tag
+       regenerated by 'git update-server-info' every time a tag
        or branch is created or modified.  This is normally done
        from the `hooks/update` hook, which is run by the
-       'git-receive-pack' command when you 'git-push' into the
+       'git-receive-pack' command when you 'git push' into the
        repository.
 
 info/grafts::
@@ -168,14 +168,14 @@ info/grafts::
 info/exclude::
        This file, by convention among Porcelains, stores the
        exclude pattern list. `.gitignore` is the per-directory
-       ignore file.  'git-status', 'git-add', 'git-rm' and
-       'git-clean' look at it but the core git commands do not look
+       ignore file.  'git status', 'git add', 'git rm' and
+       'git clean' look at it but the core git commands do not look
        at it.  See also: linkgit:gitignore[5].
 
 remotes::
        Stores shorthands to be used to give URL and default
        refnames to interact with remote repository to
-       'git-fetch', 'git-pull' and 'git-push' commands.
+       'git fetch', 'git pull' and 'git push' commands.
 
 logs::
        Records of changes made to refs are stored in this
index dc8fc3a18a5e613da910c6bd5a1dda9c85c3f5b1..ecab0c09d01fadd423b8d6bc4ee4bf3dd9e669de 100644 (file)
@@ -260,7 +260,7 @@ index a042389..513feba 100644
 +hello world, again
 ------------------------------------------------
 
-So 'git-diff' is comparing against something other than the head.
+So 'git diff' is comparing against something other than the head.
 The thing that it's comparing against is actually the index file,
 which is stored in .git/index in a binary format, but whose contents
 we can examine with ls-files:
@@ -275,9 +275,9 @@ hello world!
 hello world, again
 ------------------------------------------------
 
-So what our 'git-add' did was store a new blob and then put
+So what our 'git add' did was store a new blob and then put
 a reference to it in the index file.  If we modify the file again,
-we'll see that the new modifications are reflected in the 'git-diff'
+we'll see that the new modifications are reflected in the 'git diff'
 output:
 
 ------------------------------------------------
@@ -292,7 +292,7 @@ index 513feba..ba3da7b 100644
 +again?
 ------------------------------------------------
 
-With the right arguments, 'git-diff' can also show us the difference
+With the right arguments, 'git diff' can also show us the difference
 between the working directory and the last commit, or between the
 index and the last commit:
 
@@ -316,7 +316,7 @@ index a042389..513feba 100644
 +hello world, again
 ------------------------------------------------
 
-At any time, we can create a new commit using 'git-commit' (without
+At any time, we can create a new commit using 'git commit' (without
 the "-a" option), and verify that the state committed only includes the
 changes stored in the index file, not the additional change that is
 still only in our working tree:
@@ -334,11 +334,11 @@ index 513feba..ba3da7b 100644
 +again?
 ------------------------------------------------
 
-So by default 'git-commit' uses the index to create the commit, not
+So by default 'git commit' uses the index to create the commit, not
 the working tree; the "-a" option to commit tells it to first update
 the index with all changes in the working tree.
 
-Finally, it's worth looking at the effect of 'git-add' on the index
+Finally, it's worth looking at the effect of 'git add' on the index
 file:
 
 ------------------------------------------------
@@ -346,7 +346,7 @@ $ echo "goodbye, world" >closing.txt
 $ git add closing.txt
 ------------------------------------------------
 
-The effect of the 'git-add' was to add one entry to the index file:
+The effect of the 'git add' was to add one entry to the index file:
 
 ------------------------------------------------
 $ git ls-files --stage
index cf0689cfeb8b5122f8fa20345d9f5307fee1cd6c..1c1606696e3771f1b881c18689b049b53a8a3539 100644 (file)
@@ -67,7 +67,7 @@ You've now initialized the working directory--you may notice a new
 directory created, named ".git".
 
 Next, tell git to take a snapshot of the contents of all files under the
-current directory (note the '.'), with 'git-add':
+current directory (note the '.'), with 'git add':
 
 ------------------------------------------------
 $ git add .
@@ -75,7 +75,7 @@ $ git add .
 
 This snapshot is now stored in a temporary staging area which git calls
 the "index".  You can permanently store the contents of the index in the
-repository with 'git-commit':
+repository with 'git commit':
 
 ------------------------------------------------
 $ git commit
@@ -94,15 +94,15 @@ $ git add file1 file2 file3
 ------------------------------------------------
 
 You are now ready to commit.  You can see what is about to be committed
-using 'git-diff' with the --cached option:
+using 'git diff' with the --cached option:
 
 ------------------------------------------------
 $ git diff --cached
 ------------------------------------------------
 
-(Without --cached, 'git-diff' will show you any changes that
+(Without --cached, 'git diff' will show you any changes that
 you've made but not yet added to the index.)  You can also get a brief
-summary of the situation with 'git-status':
+summary of the situation with 'git status':
 
 ------------------------------------------------
 $ git status
@@ -126,7 +126,7 @@ $ git commit
 This will again prompt you for a message describing the change, and then
 record a new version of the project.
 
-Alternatively, instead of running 'git-add' beforehand, you can use
+Alternatively, instead of running 'git add' beforehand, you can use
 
 ------------------------------------------------
 $ git commit -a
@@ -147,7 +147,7 @@ Git tracks content not files
 
 Many revision control systems provide an `add` command that tells the
 system to start tracking changes to a new file.  Git's `add` command
-does something simpler and more powerful: 'git-add' is used both for new
+does something simpler and more powerful: 'git add' is used both for new
 and newly modified files, and in both cases it takes a snapshot of the
 given files and stages that content in the index, ready for inclusion in
 the next commit.
@@ -376,7 +376,7 @@ alice$ git remote add bob /home/bob/myrepo
 ------------------------------------------------
 
 With this, Alice can perform the first part of the "pull" operation
-alone using the 'git-fetch' command without merging them with her own
+alone using the 'git fetch' command without merging them with her own
 branch, using:
 
 -------------------------------------
@@ -384,7 +384,7 @@ alice$ git fetch bob
 -------------------------------------
 
 Unlike the longhand form, when Alice fetches from Bob using a
-remote repository shorthand set up with 'git-remote', what was
+remote repository shorthand set up with 'git remote', what was
 fetched is stored in a remote tracking branch, in this case
 `bob/master`.  So after this:
 
@@ -428,7 +428,7 @@ bob$ git config --get remote.origin.url
 /home/alice/project
 -------------------------------------
 
-(The complete configuration created by 'git-clone' is visible using
+(The complete configuration created by 'git clone' is visible using
 `git config -l`, and the linkgit:git-config[1] man page
 explains the meaning of each option.)
 
@@ -458,7 +458,7 @@ Exploring history
 -----------------
 
 Git history is represented as a series of interrelated commits.  We
-have already seen that the 'git-log' command can list those commits.
+have already seen that the 'git log' command can list those commits.
 Note that first line of each git log entry also gives a name for the
 commit:
 
@@ -471,7 +471,7 @@ Date:   Tue May 16 17:18:22 2006 -0700
     merge-base: Clarify the comments on post processing.
 -------------------------------------
 
-We can give this name to 'git-show' to see the details about this
+We can give this name to 'git show' to see the details about this
 commit.
 
 -------------------------------------
@@ -529,13 +529,13 @@ $ git reset --hard HEAD^ # reset your current branch and working
 Be careful with that last command: in addition to losing any changes
 in the working directory, it will also remove all later commits from
 this branch.  If this branch is the only branch containing those
-commits, they will be lost.  Also, don't use 'git-reset' on a
+commits, they will be lost.  Also, don't use 'git reset' on a
 publicly-visible branch that other developers pull from, as it will
 force needless merges on other developers to clean up the history.
-If you need to undo changes that you have pushed, use 'git-revert'
+If you need to undo changes that you have pushed, use 'git revert'
 instead.
 
-The 'git-grep' command can search for strings in any version of your
+The 'git grep' command can search for strings in any version of your
 project, so
 
 -------------------------------------
@@ -544,7 +544,7 @@ $ git grep "hello" v2.5
 
 searches for all occurrences of "hello" in v2.5.
 
-If you leave out the commit name, 'git-grep' will search any of the
+If you leave out the commit name, 'git grep' will search any of the
 files it manages in your current directory.  So
 
 -------------------------------------
@@ -554,7 +554,7 @@ $ git grep "hello"
 is a quick way to search just the files that are tracked by git.
 
 Many git commands also take sets of commits, which can be specified
-in a number of ways.  Here are some examples with 'git-log':
+in a number of ways.  Here are some examples with 'git log':
 
 -------------------------------------
 $ git log v2.5..v2.6            # commits between v2.5 and v2.6
@@ -564,7 +564,7 @@ $ git log v2.5.. Makefile       # commits since v2.5 which modify
                                # Makefile
 -------------------------------------
 
-You can also give 'git-log' a "range" of commits where the first is not
+You can also give 'git log' a "range" of commits where the first is not
 necessarily an ancestor of the second; for example, if the tips of
 the branches "stable" and "master" diverged from a common
 commit some time ago, then
@@ -583,9 +583,9 @@ $ git log master..stable
 will show the list of commits made on the stable branch but not
 the master branch.
 
-The 'git-log' command has a weakness: it must present commits in a
+The 'git log' command has a weakness: it must present commits in a
 list.  When the history has lines of development that diverged and
-then merged back together, the order in which 'git-log' presents
+then merged back together, the order in which 'git log' presents
 those commits is meaningless.
 
 Most projects with multiple contributors (such as the Linux kernel,
@@ -609,7 +609,7 @@ of the file:
 $ git diff v2.5:Makefile HEAD:Makefile.in
 -------------------------------------
 
-You can also use 'git-show' to see any such file:
+You can also use 'git show' to see any such file:
 
 -------------------------------------
 $ git show v2.5:Makefile
index 065441df644b6efc2883bec87fee4941788312ef..1ef55fffcf6815a12a1f9845809592104e208092 100644 (file)
@@ -360,7 +360,7 @@ There are three main tools that can be used for this:
 
 * linkgit:git-pull[1] that does fetch and merge in one go.
 
-Note the last point.  Do 'not' use 'git-pull' unless you actually want
+Note the last point.  Do 'not' use 'git pull' unless you actually want
 to merge the remote branch.
 
 Getting changes out is easy:
@@ -397,7 +397,7 @@ Please pull from
     <url> <branch>
 -------------------------------------
 
-In that case, 'git-pull' can do the fetch and merge in one go, as
+In that case, 'git pull' can do the fetch and merge in one go, as
 follows.
 
 .Push/pull: Merging remote topics
@@ -449,7 +449,7 @@ problem.
 
 If you receive such a patch series (as maintainer, or perhaps as a
 reader of the mailing list it was sent to), save the mails to files,
-create a new topic branch and use 'git-am' to import the commits:
+create a new topic branch and use 'git am' to import the commits:
 
 .format-patch/am: Importing patches
 [caption="Recipe: "]
index 708da6ca31c1bc0a714df9f437d6813b016dacb7..625d3154ea5100c67ecb354f47e83142dfe8c8af 100644 (file)
@@ -21,7 +21,7 @@ project find it more convenient to use legacy encodings, git
 does not forbid it.  However, there are a few things to keep in
 mind.
 
-. 'git-commit' and 'git-commit-tree' issues
+. 'git commit' and 'git commit-tree' issues
   a warning if the commit log message given to it does not look
   like a valid UTF-8 string, unless you explicitly say your
   project uses a legacy encoding.  The way to say this is to
@@ -37,7 +37,7 @@ of `i18n.commitencoding` in its `encoding` header.  This is to
 help other people who look at them later.  Lack of this header
 implies that the commit log message is encoded in UTF-8.
 
-. 'git-log', 'git-show', 'git-blame' and friends look at the
+. 'git log', 'git show', 'git blame' and friends look at the
   `encoding` header of a commit object, and try to re-code the
   log message into UTF-8 unless otherwise specified.  You can
   specify the desired output encoding with
index fec33943058ea3e139aff0b898ef55a677d4067e..3b83dba1a0d8ad1436d15d164783f08593f54357 100644 (file)
@@ -59,8 +59,8 @@ option can be used to override --squash.
        Use the given merge strategy; can be supplied more than
        once to specify them in the order they should be tried.
        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).
+       is used instead ('git merge-recursive' when merging a single
+       head, 'git merge-octopus' otherwise).
 
 --summary::
 --no-summary::
@@ -74,3 +74,8 @@ option can be used to override --squash.
 -v::
 --verbose::
        Be verbose.
+
+-X <option>::
+--strategy-option=<option>::
+       Pass merge strategy specific option through to the merge
+       strategy.
index 42910a3d5e0229f471c5bceb0c36f184d17ebb08..a5bc1dbb95b466d0c6e37f683c886663908375e7 100644 (file)
@@ -1,6 +1,11 @@
 MERGE STRATEGIES
 ----------------
 
+The merge mechanism ('git-merge' and 'git-pull' commands) allows the
+backend 'merge strategies' to be chosen with `-s` option.  Some strategies
+can also take their own options, which can be passed by giving `-X<option>`
+arguments to 'git-merge' and/or 'git-pull'.
+
 resolve::
        This can only resolve two heads (i.e. the current branch
        and another branch you pulled from) using a 3-way merge
@@ -20,6 +25,27 @@ recursive::
        Additionally this can detect and handle merges involving
        renames.  This is the default merge strategy when
        pulling or merging one branch.
++
+The 'recursive' strategy can take the following options:
+
+ours;;
+       This option forces conflicting hunks to be auto-resolved cleanly by
+       favoring 'our' version.  Changes from the other tree that do not
+       conflict with our side are reflected to the merge result.
++
+This should not be confused with the 'ours' merge strategy, which does not
+even look at what the other tree contains at all.  It discards everything
+the other tree did, declaring 'our' history contains all that happened in it.
+
+theirs;;
+       This is opposite of 'ours'.
+
+subtree[=path];;
+       This option is a more advanced form of 'subtree' strategy, where
+       the strategy makes a guess on how two trees must be shifted to
+       match with each other when merging.  Instead, the specified path
+       is prefixed (or stripped from the beginning) to make the shape of
+       two trees to match.
 
 octopus::
        This resolves cases with more than two heads, but refuses to do
@@ -33,7 +59,8 @@ ours::
        merge is always that of the current branch head, effectively
        ignoring all changes from all other branches.  It is meant to
        be used to supersede old development history of side
-       branches.
+       branches.  Note that this is different from the -Xours option to
+       the 'recursive' merge strategy.
 
 subtree::
        This is a modified recursive strategy. When merging trees A and
index 53a9168ba7d8959e65c2442f6a078155d6f24c71..1686a54d22a746036b997d6eb8d5b85ca1d79c5d 100644 (file)
@@ -134,6 +134,7 @@ The placeholders are:
 - '%C(...)': color specification, as described in color.branch.* config option
 - '%m': left, right or boundary mark
 - '%n': newline
+- '%%': a raw '%'
 - '%x00': print a byte from a hex code
 - '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of
   linkgit:git-shortlog[1].
index 0551ebdfafb3f41dd0e31d3c74ce42fff33e38e1..5dd6e5a0c7e7f215d4de783a2751e98e41c69948 100644 (file)
@@ -38,7 +38,7 @@ must know this is the expected usage pattern for a branch.
 [NOTE]
 You never do your own development on branches that appear
 on the right hand side of a <refspec> colon on `Pull:` lines;
-they are to be updated by 'git-fetch'.  If you intend to do
+they are to be updated by 'git fetch'.  If you intend to do
 development derived from a remote branch `B`, have a `Pull:`
 line to track it (i.e. `Pull: B:remote-B`), and have a separate
 branch `my-B` to do your development on top of it.  The latter
@@ -50,13 +50,13 @@ on the remote branch, merge it into your development branch with
 +
 [NOTE]
 There is a difference between listing multiple <refspec>
-directly on 'git-pull' command line and having multiple
+directly on 'git pull' command line and having multiple
 `Pull:` <refspec> lines for a <repository> and running
-'git-pull' command without any explicit <refspec> parameters.
+'git pull' command without any explicit <refspec> parameters.
 <refspec> listed explicitly on the command line are always
 merged into the current branch after fetching.  In other words,
 if you list more than one remote refs, you would be making
-an Octopus.  While 'git-pull' run without any explicit <refspec>
+an Octopus.  While 'git pull' run without any explicit <refspec>
 parameter takes default <refspec>s from `Pull:` lines, it
 merges only the first <refspec> found into the current branch,
 after fetching all the remote refs.  This is because making an
index 1f57aed337e87e8bd2b175ef3fc807288a8256bf..6e9baf8b38be5fba5e3c82e617ab95d579731884 100644 (file)
@@ -228,20 +228,33 @@ endif::git-rev-list[]
        Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the
        command line as '<commit>'.
 
---branches::
+--branches[=pattern]::
 
        Pretend as if all the refs in `$GIT_DIR/refs/heads` are listed
-       on the command line as '<commit>'.
+       on the command line as '<commit>'. If `pattern` is given, limit
+       branches to ones matching given shell glob. If pattern lacks '?',
+       '*', or '[', '/*' at the end is implied.
 
---tags::
+--tags[=pattern]::
 
        Pretend as if all the refs in `$GIT_DIR/refs/tags` are listed
-       on the command line as '<commit>'.
+       on the command line as '<commit>'. If `pattern` is given, limit
+       tags to ones matching given shell glob. If pattern lacks '?', '*',
+       or '[', '/*' at the end is implied.
 
---remotes::
+--remotes[=pattern]::
 
        Pretend as if all the refs in `$GIT_DIR/refs/remotes` are listed
-       on the command line as '<commit>'.
+       on the command line as '<commit>'. If `pattern`is given, limit
+       remote tracking branches to ones matching given shell glob.
+       If pattern lacks '?', '*', or '[', '/*' at the end is implied.
+
+--glob=glob-pattern::
+       Pretend as if all the refs matching shell glob `glob-pattern`
+       are listed on the command line as '<commit>'. Leading 'refs/',
+       is automatically prepended if missing. If pattern lacks '?', '*',
+       or '[', '/*' at the end is implied.
+
 
 ifndef::git-rev-list[]
 --bisect::
index 5bbd18f0206604416c3833b7541a5b55b7e63976..add6f435b59e3df095e507d8a5d4f911cee9d3b2 100644 (file)
@@ -58,6 +58,9 @@ The result of the enumeration is left in these fields::
 Calling sequence
 ----------------
 
+Note: index may be looked at for .gitignore files that are CE_SKIP_WORKTREE
+marked. If you to exclude files, make sure you have loaded index first.
+
 * Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0,
   sizeof(dir))`.
 
index b26c28133c143b23acf28fc1a3a06d16c10c65f8..68bf4cad8b0298349b6cc67e822a4d60e1ff1d24 100644 (file)
@@ -51,7 +51,7 @@ The functions above do the following:
   ENOENT; a diagnostic is printed only if .silent_exec_failure is 0.
 
 . Otherwise, the program is run. If it terminates regularly, its exit
-  code is returned. No diagnistic is printed, even if the exit code is
+  code is returned. No diagnostic is printed, even if the exit code is
   non-zero.
 
 . If the program terminated due to a signal, then the return value is the
index a0e0f850f83fe164dd7c1ca87d001fe485ba2ec2..afe27599511c5f6bab6e4f799fd18e7d83bdd454 100644 (file)
@@ -199,6 +199,10 @@ character if the letter `n` appears after a `%`.  The function returns
 the length of the placeholder recognized and `strbuf_expand()` skips
 over it.
 +
+The format `%%` is automatically expanded to a single `%` as a quoting
+mechanism; callers do not need to handle the `%` placeholder themselves,
+and the callback function will not be invoked for this placeholder.
++
 All other characters (non-percent and not skipped ones) are copied
 verbatim to the strbuf.  If the callback returned zero, meaning that the
 placeholder is unknown, then the percent sign is copied, too.
@@ -214,6 +218,13 @@ which can be used by the programmer of the callback as she sees fit.
        placeholder and replacement string.  The array needs to be
        terminated by an entry with placeholder set to NULL.
 
+`strbuf_addbuf_percentquote`::
+
+       Append the contents of one strbuf to another, quoting any
+       percent signs ("%") into double-percents ("%%") in the
+       destination. This is useful for literal data to be fed to either
+       strbuf_expand or to the *printf family of functions.
+
 `strbuf_addf`::
 
        Add a formatted string to the buffer.
index 7950eeeda4447808c937b6ddcaa9ae1686e2ed5c..9a5cdafa9cb8c5af8a3903ae18297a23adab0fbf 100644 (file)
@@ -149,7 +149,7 @@ 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.
+MUST peel the ref if it's an annotated tag.
 
 ----
   advertised-refs  =  (no-refs / list-of-refs)
@@ -261,7 +261,7 @@ Without either multi_ack or multi_ack_detailed:
 
  * 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.
+   was already sent, it's 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
@@ -271,9 +271,9 @@ 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
+the server should just send all of its 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.
+is ready to receive its packfile data.
 
 However, the 256 limit *only* turns on in the canonical client
 implementation if we have received at least one "ACK %s continue"
@@ -286,7 +286,7 @@ 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.
+Then the server will start sending its packfile data.
 
 ----
   server-response = *ack_multi ack / nak
index 1892d3eeac484c32441136372fbd7c0cf4fdf7ef..fd1a593149a0ed1a12085bb83067576674a04e81 100644 (file)
@@ -60,7 +60,7 @@ doesn't, as in the following diagram:
 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,
+walking down that line (so don't send c-b-a), but it's 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.
@@ -181,7 +181,7 @@ 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
+it is capable of accepting a 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.
index 2a0e7b89441039699a3dc9514f1165cb2112020a..00f7e79c44eec844969467fac431c33071888983 100644 (file)
@@ -52,8 +52,8 @@ following format:
 
 ------------
 
-`Push:` lines are used by 'git-push' and
-`Pull:` lines are used by 'git-pull' and 'git-fetch'.
+`Push:` lines are used by 'git push' and
+`Pull:` lines are used by 'git pull' and 'git fetch'.
 Multiple `Push:` and `Pull:` lines may
 be specified for additional branch mappings.
 
index b169836684a56aae176875effa5cc4e6a619de80..fe6fb722da1a5c288c7ae3757622aac8cac79a73 100644 (file)
@@ -1196,7 +1196,7 @@ the time, you will want to commit your changes before you can merge,
 and if you don't, then linkgit:git-stash[1] can take these changes
 away while you're doing the merge, and reapply them afterwards.
 
-If the changes are independant enough, Git will automatically complete
+If the changes are independent enough, Git will automatically complete
 the merge and commit the result (or reuse an existing commit in case
 of <<fast-forwards,fast-forward>>, see below). On the other hand,
 if there are conflicts--for example, if the same file is
@@ -3640,6 +3640,26 @@ Did you forget to 'git add'?
 Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a'
 -------------------------------------------------
 
+In older git versions it could be easily forgotten to commit new or modified
+files in a submodule, which silently leads to similar problems as not pushing
+the submodule changes. Starting with git 1.7.0 both "git status" and "git diff"
+in the superproject show submodules as modified when they contain new or
+modified files to protect against accidentally committing such a state. "git
+diff" will also add a "-dirty" to the work tree side when generating patch
+output or used with the --submodule option:
+
+-------------------------------------------------
+$ git diff
+diff --git a/sub b/sub
+--- a/sub
++++ b/sub
+@@ -1 +1 @@
+-Subproject commit 3f356705649b5d566d97ff843cf193359229a453
++Subproject commit 3f356705649b5d566d97ff843cf193359229a453-dirty
+$ git diff --submodule
+Submodule sub 3f35670..3f35670-dirty:
+-------------------------------------------------
+
 You also should not rewind branches in a submodule beyond commits that were
 ever recorded in any superproject.
 
index 4e0adcade959a188c0d6e7d44a99b042e8280aec..a7d8c63427c91fa7c033bbf7dc5150e0e292bf25 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.6.6.1
+DEF_VER=v1.6.6.GIT
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index be504c95e1f092edaf524064d49ba30adc57dc9b..61086ab1204a4304cb1d84eeea9d1649878ac9e1 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -38,13 +38,17 @@ Issues of note:
    Interactive Tools package still can install "git", but you can build it
    with --disable-transition option to avoid this.
 
- - You can use git after building but without installing if you
-   wanted to.  Various git commands need to find other git
-   commands and scripts to do their work, so you would need to
-   arrange a few environment variables to tell them that their
-   friends will be found in your built source area instead of at
-   their standard installation area.  Something like this works
-   for me:
+ - You can use git after building but without installing if you want
+   to test drive it.  Simply run git found in bin-wrappers directory
+   in the build directory, or prepend that directory to your $PATH.
+   This however is less efficient than running an installed git, as
+   you always need an extra fork+exec to run any git subcommand.
+
+   It is still possible to use git without installing by setting a few
+   environment variables, which was the way this was done
+   traditionally.  But using git found in bin-wrappers directory in
+   the build directory is far simpler.  As a historical reference, the
+   old way went like this:
 
        GIT_EXEC_PATH=`pwd`
        PATH=`pwd`:$PATH
index fd7f51e8b95060dc2ee7fcf30382ba4c86cad4ef..7bf2fca4070d2d00ac31d8b4dca6dff19b79cc79 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -168,6 +168,8 @@ all::
 #
 # Define NO_PERL if you do not want Perl scripts or libraries at all.
 #
+# Define NO_PYTHON if you do not want Python scripts or libraries at all.
+#
 # Define NO_TCLTK if you do not want Tcl/Tk GUI.
 #
 # The TCL_PATH variable governs the location of the Tcl interpreter
@@ -178,17 +180,10 @@ all::
 # If not set it defaults to the bare 'wish'. If it is set to the empty
 # string then NO_TCLTK will be forced (this is used by configure script).
 #
-# Define THREADED_DELTA_SEARCH if you have pthreads and wish to exploit
-# parallel delta searching when packing objects.
-#
 # Define INTERNAL_QSORT to use Git's implementation of qsort(), which
 # is a simplified version of the merge sort used in glibc. This is
 # recommended if Git triggers O(n^2) behavior in your platform's qsort().
 #
-# Define NO_EXTERNAL_GREP if you don't want "git grep" to ever call
-# your external grep (e.g., if your system lacks grep, if its grep is
-# broken, or spawning external process is slower than built-in grep git has).
-#
 # Define UNRELIABLE_FSTAT if your system's fstat does not return the same
 # information on a not yet closed file that lstat would return for the same
 # file after it was closed.
@@ -220,7 +215,7 @@ all::
 #   DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
 #   DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
 
-GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
 -include GIT-VERSION-FILE
 
@@ -280,29 +275,6 @@ pathsep = :
 # JavaScript minifier invocation that can function as filter
 JSMIN =
 
-# default configuration for gitweb
-GITWEB_CONFIG = gitweb_config.perl
-GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf
-GITWEB_HOME_LINK_STR = projects
-GITWEB_SITENAME =
-GITWEB_PROJECTROOT = /pub/git
-GITWEB_PROJECT_MAXDEPTH = 2007
-GITWEB_EXPORT_OK =
-GITWEB_STRICT_EXPORT =
-GITWEB_BASE_URL =
-GITWEB_LIST =
-GITWEB_HOMETEXT = indextext.html
-GITWEB_CSS = gitweb.css
-GITWEB_LOGO = git-logo.png
-GITWEB_FAVICON = git-favicon.png
-ifdef JSMIN
-GITWEB_JS = gitweb.min.js
-else
-GITWEB_JS = gitweb.js
-endif
-GITWEB_SITE_HEADER =
-GITWEB_SITE_FOOTER =
-
 export prefix bindir sharedir sysconfdir
 
 CC = gcc
@@ -341,6 +313,7 @@ LIB_H =
 LIB_OBJS =
 PROGRAMS =
 SCRIPT_PERL =
+SCRIPT_PYTHON =
 SCRIPT_SH =
 TEST_PROGRAMS =
 
@@ -379,6 +352,7 @@ SCRIPT_PERL += git-svn.perl
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
+         $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
          git-instaweb
 
 # Empty...
@@ -387,19 +361,10 @@ EXTRA_PROGRAMS =
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS += $(EXTRA_PROGRAMS)
 PROGRAMS += git-fast-import$X
-PROGRAMS += git-hash-object$X
 PROGRAMS += git-imap-send$X
-PROGRAMS += git-index-pack$X
-PROGRAMS += git-merge-index$X
-PROGRAMS += git-merge-tree$X
-PROGRAMS += git-mktag$X
-PROGRAMS += git-pack-redundant$X
-PROGRAMS += git-patch-id$X
 PROGRAMS += git-shell$X
 PROGRAMS += git-show-index$X
-PROGRAMS += git-unpack-file$X
 PROGRAMS += git-upload-pack$X
-PROGRAMS += git-var$X
 PROGRAMS += git-http-backend$X
 
 # List built-in command $C whose implementation cmd_$C() is not in
@@ -427,6 +392,15 @@ ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
 # what 'all' will build but not install in gitexecdir
 OTHER_PROGRAMS = git$X
 
+# what test wrappers are needed and 'install' will install, in bindir
+BINDIR_PROGRAMS_NEED_X += git
+BINDIR_PROGRAMS_NEED_X += git-upload-pack
+BINDIR_PROGRAMS_NEED_X += git-receive-pack
+BINDIR_PROGRAMS_NEED_X += git-upload-archive
+BINDIR_PROGRAMS_NEED_X += git-shell
+
+BINDIR_PROGRAMS_NO_X += git-cvsserver
+
 # Set paths to tools early so that they can be used for version tests.
 ifndef SHELL_PATH
        SHELL_PATH = /bin/sh
@@ -434,8 +408,12 @@ endif
 ifndef PERL_PATH
        PERL_PATH = /usr/bin/perl
 endif
+ifndef PYTHON_PATH
+       PYTHON_PATH = /usr/bin/python
+endif
 
 export PERL_PATH
+export PYTHON_PATH
 
 LIB_FILE=libgit.a
 XDIFF_LIB=xdiff/lib.a
@@ -451,6 +429,7 @@ LIB_H += commit.h
 LIB_H += compat/bswap.h
 LIB_H += compat/cygwin.h
 LIB_H += compat/mingw.h
+LIB_H += compat/win32/pthread.h
 LIB_H += csum-file.h
 LIB_H += decorate.h
 LIB_H += delta.h
@@ -483,6 +462,7 @@ LIB_H += reflog-walk.h
 LIB_H += refs.h
 LIB_H += remote.h
 LIB_H += rerere.h
+LIB_H += resolve-undo.h
 LIB_H += revision.h
 LIB_H += run-command.h
 LIB_H += sha1-lookup.h
@@ -544,6 +524,7 @@ LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
 LIB_OBJS += help.o
+LIB_OBJS += hex.o
 LIB_OBJS += ident.o
 LIB_OBJS += levenshtein.o
 LIB_OBJS += list-objects.o
@@ -578,6 +559,7 @@ LIB_OBJS += refs.o
 LIB_OBJS += remote.o
 LIB_OBJS += replace_object.o
 LIB_OBJS += rerere.o
+LIB_OBJS += resolve-undo.o
 LIB_OBJS += revision.o
 LIB_OBJS += run-command.o
 LIB_OBJS += server-info.o
@@ -642,7 +624,9 @@ BUILTIN_OBJS += builtin-for-each-ref.o
 BUILTIN_OBJS += builtin-fsck.o
 BUILTIN_OBJS += builtin-gc.o
 BUILTIN_OBJS += builtin-grep.o
+BUILTIN_OBJS += builtin-hash-object.o
 BUILTIN_OBJS += builtin-help.o
+BUILTIN_OBJS += builtin-index-pack.o
 BUILTIN_OBJS += builtin-init-db.o
 BUILTIN_OBJS += builtin-log.o
 BUILTIN_OBJS += builtin-ls-files.o
@@ -653,13 +637,18 @@ BUILTIN_OBJS += builtin-mailsplit.o
 BUILTIN_OBJS += builtin-merge.o
 BUILTIN_OBJS += builtin-merge-base.o
 BUILTIN_OBJS += builtin-merge-file.o
+BUILTIN_OBJS += builtin-merge-index.o
 BUILTIN_OBJS += builtin-merge-ours.o
 BUILTIN_OBJS += builtin-merge-recursive.o
+BUILTIN_OBJS += builtin-merge-tree.o
+BUILTIN_OBJS += builtin-mktag.o
 BUILTIN_OBJS += builtin-mktree.o
 BUILTIN_OBJS += builtin-mv.o
 BUILTIN_OBJS += builtin-name-rev.o
 BUILTIN_OBJS += builtin-pack-objects.o
+BUILTIN_OBJS += builtin-pack-redundant.o
 BUILTIN_OBJS += builtin-pack-refs.o
+BUILTIN_OBJS += builtin-patch-id.o
 BUILTIN_OBJS += builtin-prune-packed.o
 BUILTIN_OBJS += builtin-prune.o
 BUILTIN_OBJS += builtin-push.o
@@ -682,11 +671,13 @@ BUILTIN_OBJS += builtin-stripspace.o
 BUILTIN_OBJS += builtin-symbolic-ref.o
 BUILTIN_OBJS += builtin-tag.o
 BUILTIN_OBJS += builtin-tar-tree.o
+BUILTIN_OBJS += builtin-unpack-file.o
 BUILTIN_OBJS += builtin-unpack-objects.o
 BUILTIN_OBJS += builtin-update-index.o
 BUILTIN_OBJS += builtin-update-ref.o
 BUILTIN_OBJS += builtin-update-server-info.o
 BUILTIN_OBJS += builtin-upload-archive.o
+BUILTIN_OBJS += builtin-var.o
 BUILTIN_OBJS += builtin-verify-pack.o
 BUILTIN_OBJS += builtin-verify-tag.o
 BUILTIN_OBJS += builtin-write-tree.o
@@ -705,12 +696,10 @@ EXTLIBS =
 ifeq ($(uname_S),Linux)
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
-       THREADED_DELTA_SEARCH = YesPlease
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
-       THREADED_DELTA_SEARCH = YesPlease
 endif
 ifeq ($(uname_S),UnixWare)
        CC = cc
@@ -764,7 +753,6 @@ ifeq ($(uname_S),Darwin)
                NO_STRLCPY = YesPlease
        endif
        NO_MEMMEM = YesPlease
-       THREADED_DELTA_SEARCH = YesPlease
        USE_ST_TIMESPEC = YesPlease
 endif
 ifeq ($(uname_S),SunOS)
@@ -777,8 +765,6 @@ ifeq ($(uname_S),SunOS)
        NO_MKDTEMP = YesPlease
        NO_MKSTEMPS = YesPlease
        NO_REGEX = YesPlease
-       NO_EXTERNAL_GREP = YesPlease
-       THREADED_DELTA_SEARCH = YesPlease
        ifeq ($(uname_R),5.7)
                NEEDS_RESOLV = YesPlease
                NO_IPV6 = YesPlease
@@ -834,7 +820,6 @@ ifeq ($(uname_S),FreeBSD)
        BASIC_LDFLAGS += -L/usr/local/lib
        DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
        USE_ST_TIMESPEC = YesPlease
-       THREADED_DELTA_SEARCH = YesPlease
        ifeq ($(shell expr "$(uname_R)" : '4\.'),2)
                PTHREAD_LIBS = -pthread
                NO_UINTMAX_T = YesPlease
@@ -848,7 +833,6 @@ ifeq ($(uname_S),OpenBSD)
        NEEDS_LIBICONV = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
-       THREADED_DELTA_SEARCH = YesPlease
 endif
 ifeq ($(uname_S),NetBSD)
        ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
@@ -856,7 +840,6 @@ ifeq ($(uname_S),NetBSD)
        endif
        BASIC_CFLAGS += -I/usr/pkg/include
        BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib
-       THREADED_DELTA_SEARCH = YesPlease
        USE_ST_TIMESPEC = YesPlease
        NO_MKSTEMPS = YesPlease
 endif
@@ -871,9 +854,7 @@ ifeq ($(uname_S),AIX)
        INTERNAL_QSORT = UnfortunatelyYes
        NEEDS_LIBICONV=YesPlease
        BASIC_CFLAGS += -D_LARGE_FILES
-       ifneq ($(shell expr "$(uname_V)" : '[1234]'),1)
-               THREADED_DELTA_SEARCH = YesPlease
-       else
+       ifeq ($(shell expr "$(uname_V)" : '[1234]'),1)
                NO_PTHREADS = YesPlease
        endif
 endif
@@ -896,11 +877,9 @@ ifeq ($(uname_S),IRIX)
        # NO_MMAP.  If you suspect that your compiler is not affected by this
        # issue, comment out the NO_MMAP statement.
        NO_MMAP = YesPlease
-       NO_EXTERNAL_GREP = UnfortunatelyYes
        SNPRINTF_RETURNS_BOGUS = YesPlease
        SHELL_PATH = /usr/gnu/bin/bash
        NEEDS_LIBGEN = YesPlease
-       THREADED_DELTA_SEARCH = YesPlease
 endif
 ifeq ($(uname_S),IRIX64)
        NO_SETENV=YesPlease
@@ -916,11 +895,9 @@ ifeq ($(uname_S),IRIX64)
        # NO_MMAP.  If you suspect that your compiler is not affected by this
        # issue, comment out the NO_MMAP statement.
        NO_MMAP = YesPlease
-       NO_EXTERNAL_GREP = UnfortunatelyYes
        SNPRINTF_RETURNS_BOGUS = YesPlease
        SHELL_PATH=/usr/gnu/bin/bash
        NEEDS_LIBGEN = YesPlease
-       THREADED_DELTA_SEARCH = YesPlease
 endif
 ifeq ($(uname_S),HP-UX)
        NO_IPV6=YesPlease
@@ -968,17 +945,18 @@ ifeq ($(uname_S),Windows)
        OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
        NO_REGEX = YesPlease
        NO_CURL = YesPlease
-       NO_PTHREADS = YesPlease
+       NO_PYTHON = YesPlease
        BLK_SHA1 = YesPlease
 
        CC = compat/vcbuild/scripts/clink.pl
        AR = compat/vcbuild/scripts/lib.pl
        CFLAGS =
        BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
-       COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o
-       COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -DSTRIP_EXTENSION=\".exe\"
+       COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o
+       COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
        BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
        EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib
+       PTHREAD_LIBS =
        lib =
 ifndef DEBUG
        BASIC_CFLAGS += -GL -Os -MT
@@ -1018,11 +996,14 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        UNRELIABLE_FSTAT = UnfortunatelyYes
        OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
        NO_REGEX = YesPlease
+       NO_PYTHON = YesPlease
        BLK_SHA1 = YesPlease
-       COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch
+       COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch -Icompat/win32
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
-       COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o
+       COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \
+               compat/win32/pthread.o
        EXTLIBS += -lws2_32
+       PTHREAD_LIBS =
        X = .exe
 ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
        htmldir=doc/git/html/
@@ -1031,10 +1012,8 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
        EXTLIBS += /mingw/lib/libz.a
        NO_R_TO_GCC_LINKER = YesPlease
        INTERNAL_QSORT = YesPlease
-       THREADED_DELTA_SEARCH = YesPlease
 else
        NO_CURL = YesPlease
-       NO_PTHREADS = YesPlease
 endif
 endif
 
@@ -1082,6 +1061,9 @@ endif
 
 ifdef NO_CURL
        BASIC_CFLAGS += -DNO_CURL
+       REMOTE_CURL_PRIMARY =
+       REMOTE_CURL_ALIASES =
+       REMOTE_CURL_NAMES =
 else
        ifdef CURLDIR
                # Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case.
@@ -1090,7 +1072,10 @@ else
        else
                CURL_LIBCURL = -lcurl
        endif
-       PROGRAMS += git-remote-curl$X git-http-fetch$X
+       REMOTE_CURL_PRIMARY = git-remote-http$X
+       REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X
+       REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES)
+       PROGRAMS += $(REMOTE_CURL_NAMES) git-http-fetch$X
        curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
        ifeq "$(curl_check)" "070908"
                ifndef NO_EXPAT
@@ -1310,22 +1295,15 @@ ifdef RUNTIME_PREFIX
 endif
 
 ifdef NO_PTHREADS
-       THREADED_DELTA_SEARCH =
        BASIC_CFLAGS += -DNO_PTHREADS
 else
        EXTLIBS += $(PTHREAD_LIBS)
-endif
-
-ifdef THREADED_DELTA_SEARCH
-       BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH
        LIB_OBJS += thread-utils.o
 endif
+
 ifdef DIR_HAS_BSD_GROUP_SEMANTICS
        COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
 endif
-ifdef NO_EXTERNAL_GREP
-       BASIC_CFLAGS += -DNO_EXTERNAL_GREP
-endif
 ifdef UNRELIABLE_FSTAT
        BASIC_CFLAGS += -DUNRELIABLE_FSTAT
 endif
@@ -1347,6 +1325,10 @@ ifeq ($(PERL_PATH),)
 NO_PERL=NoThanks
 endif
 
+ifeq ($(PYTHON_PATH),)
+NO_PYTHON=NoThanks
+endif
+
 QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
 QUIET_SUBDIR1  =
 
@@ -1394,6 +1376,7 @@ prefix_SQ = $(subst ','\'',$(prefix))
 
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
@@ -1440,6 +1423,9 @@ ifndef NO_TCLTK
 endif
 ifndef NO_PERL
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+endif
+ifndef NO_PYTHON
+       $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
 endif
        $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1)
 
@@ -1451,20 +1437,19 @@ shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell
 strip: $(PROGRAMS) git$X
        $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
 
-git.o: git.c common-cmds.h GIT-CFLAGS
-       $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
-               '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
-               $(ALL_CFLAGS) -o $@ -c $(filter %.c,$^)
+git.o: common-cmds.h
+git.s git.o: ALL_CFLAGS += -DGIT_VERSION='"$(GIT_VERSION)"' \
+       '-DGIT_HTML_PATH="$(htmldir_SQ)"'
 
 git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
                $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
 
-builtin-help.o: builtin-help.c common-cmds.h GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
-               '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
-               '-DGIT_MAN_PATH="$(mandir_SQ)"' \
-               '-DGIT_INFO_PATH="$(infodir_SQ)"' $<
+builtin-help.o: common-cmds.h
+builtin-help.s builtin-help.o: ALL_CFLAGS += \
+       '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
+       '-DGIT_MAN_PATH="$(mandir_SQ)"' \
+       '-DGIT_INFO_PATH="$(infodir_SQ)"'
 
 $(BUILT_INS): git$X
        $(QUIET_BUILT_IN)$(RM) $@ && \
@@ -1510,6 +1495,11 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        chmod +x $@+ && \
        mv $@+ $@
 
+
+.PHONY: gitweb
+gitweb:
+       $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all
+
 ifdef JSMIN
 OTHER_PROGRAMS += gitweb/gitweb.cgi   gitweb/gitweb.min.js
 gitweb/gitweb.cgi: gitweb/gitweb.perl gitweb/gitweb.min.js
@@ -1517,30 +1507,13 @@ else
 OTHER_PROGRAMS += gitweb/gitweb.cgi
 gitweb/gitweb.cgi: gitweb/gitweb.perl
 endif
-       $(QUIET_GEN)$(RM) $@ $@+ && \
-       sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
-           -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
-           -e 's|++GIT_BINDIR++|$(bindir)|g' \
-           -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \
-           -e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \
-           -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \
-           -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \
-           -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \
-           -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \
-           -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \
-           -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \
-           -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \
-           -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \
-           -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \
-           -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \
-           -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
-           -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
-           -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \
-           -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
-           -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
-           $< >$@+ && \
-       chmod +x $@+ && \
-       mv $@+ $@
+       $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
+
+ifdef JSMIN
+gitweb/gitweb.min.js: gitweb/gitweb.js
+       $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
+endif # JSMIN
+
 
 git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/gitweb.js
        $(QUIET_GEN)$(RM) $@ $@+ && \
@@ -1567,10 +1540,34 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh
        mv $@+ $@
 endif # NO_PERL
 
-ifdef JSMIN
-gitweb/gitweb.min.js: gitweb/gitweb.js
-       $(QUIET_GEN)$(JSMIN) <$< >$@
-endif # JSMIN
+ifndef NO_PYTHON
+$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS
+$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
+               --no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \
+               instlibdir` && \
+       sed -e '1{' \
+           -e '        s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
+           -e '}' \
+           -e 's|^import sys.*|&; \\\
+                  import os; \\\
+                  sys.path[0] = os.environ.has_key("GITPYTHONLIB") and \\\
+                                os.environ["GITPYTHONLIB"] or \\\
+                                "@@INSTLIBDIR@@"|' \
+           -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
+           $@.py >$@+ && \
+       chmod +x $@+ && \
+       mv $@+ $@
+else # NO_PYTHON
+$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : unimplemented.sh
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+           -e 's|@@REASON@@|NO_PYTHON=$(NO_PYTHON)|g' \
+           unimplemented.sh >$@+ && \
+       chmod +x $@+ && \
+       mv $@+ $@
+endif # NO_PYTHON
 
 configure: configure.ac
        $(QUIET_GEN)$(RM) $@ $<+ && \
@@ -1587,30 +1584,26 @@ git.o git.spec \
 
 %.o: %.c GIT-CFLAGS
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
-%.s: %.c GIT-CFLAGS
+%.s: %.c GIT-CFLAGS FORCE
        $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<
-%.o: %.S
+%.o: %.S GIT-CFLAGS
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
 
-exec_cmd.o: exec_cmd.c GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
-               '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
-               '-DBINDIR="$(bindir_relative_SQ)"' \
-               '-DPREFIX="$(prefix_SQ)"' \
-               $<
+exec_cmd.s exec_cmd.o: ALL_CFLAGS += \
+       '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
+       '-DBINDIR="$(bindir_relative_SQ)"' \
+       '-DPREFIX="$(prefix_SQ)"'
 
-builtin-init-db.o: builtin-init-db.c GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $<
+builtin-init-db.s builtin-init-db.o: ALL_CFLAGS += \
+       -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"'
 
-config.o: config.c GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $<
+config.s config.o: ALL_CFLAGS += -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"'
 
-http.o: http.c GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $<
+http.s http.o: ALL_CFLAGS += -DGIT_USER_AGENT='"git/$(GIT_VERSION)"'
 
 ifdef NO_EXPAT
-http-walker.o: http-walker.c http.h GIT-CFLAGS
-       $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $<
+http-walker.o: http.h
+http-walker.s http-walker.o: ALL_CFLAGS += -DNO_EXPAT
 endif
 
 git-%$X: %.o $(GITLIBS)
@@ -1631,7 +1624,13 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
-git-remote-curl$X: remote-curl.o http.o http-walker.o $(GITLIBS)
+$(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
+       $(QUIET_LNCP)$(RM) $@ && \
+       ln $< $@ 2>/dev/null || \
+       ln -s $< $@ 2>/dev/null || \
+       cp $< $@
+
+$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
@@ -1682,7 +1681,7 @@ cscope:
 TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
              $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
 
-GIT-CFLAGS: .FORCE-GIT-CFLAGS
+GIT-CFLAGS: FORCE
        @FLAGS='$(TRACK_CFLAGS)'; \
            if test x"$$FLAGS" != x"`cat GIT-CFLAGS 2>/dev/null`" ; then \
                echo 1>&2 "    * new build flags or prefix"; \
@@ -1692,42 +1691,54 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
 # We need to apply sq twice, once to protect from the shell
 # that runs GIT-BUILD-OPTIONS, and then again to protect it
 # and the first level quoting from the shell that runs "echo".
-GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
+GIT-BUILD-OPTIONS: FORCE
        @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
        @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
        @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
        @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
+       @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
 TRACK_VARS = $(subst ','\'',-DTCLTK_PATH='$(TCLTK_PATH_SQ)')
 
-GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
+GIT-GUI-VARS: FORCE
        @VARS='$(TRACK_VARS)'; \
            if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \
                echo 1>&2 "    * new Tcl/Tk interpreter location"; \
                echo "$$VARS" >$@; \
             fi
-
-.PHONY: .FORCE-GIT-GUI-VARS
 endif
 
 ### Testing rules
 
-TEST_PROGRAMS += test-chmtime$X
-TEST_PROGRAMS += test-ctype$X
-TEST_PROGRAMS += test-date$X
-TEST_PROGRAMS += test-delta$X
-TEST_PROGRAMS += test-dump-cache-tree$X
-TEST_PROGRAMS += test-genrandom$X
-TEST_PROGRAMS += test-match-trees$X
-TEST_PROGRAMS += test-parse-options$X
-TEST_PROGRAMS += test-path-utils$X
-TEST_PROGRAMS += test-sha1$X
-TEST_PROGRAMS += test-sigchain$X
-
-all:: $(TEST_PROGRAMS)
+TEST_PROGRAMS_NEED_X += test-chmtime
+TEST_PROGRAMS_NEED_X += test-ctype
+TEST_PROGRAMS_NEED_X += test-date
+TEST_PROGRAMS_NEED_X += test-delta
+TEST_PROGRAMS_NEED_X += test-dump-cache-tree
+TEST_PROGRAMS_NEED_X += test-genrandom
+TEST_PROGRAMS_NEED_X += test-match-trees
+TEST_PROGRAMS_NEED_X += test-parse-options
+TEST_PROGRAMS_NEED_X += test-path-utils
+TEST_PROGRAMS_NEED_X += test-run-command
+TEST_PROGRAMS_NEED_X += test-sha1
+TEST_PROGRAMS_NEED_X += test-sigchain
+TEST_PROGRAMS_NEED_X += test-index-version
+
+TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
+
+test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
+
+all:: $(TEST_PROGRAMS) $(test_bindir_programs)
+
+bin-wrappers/%: wrap-for-bin.sh
+       @mkdir -p bin-wrappers
+       $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+            -e 's|@@BUILD_DIR@@|$(shell pwd)|' \
+            -e 's|@@PROG@@|$(@F)|' < $< > $@ && \
+       chmod +x $@
 
 # GNU make supports exporting all variables by "export" without parameters.
 # However, the environment gets quite big, and some programs have problems
@@ -1788,15 +1799,20 @@ endif
 gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir))
 export gitexec_instdir
 
+install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X)
+
 install: all
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
        $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
-       $(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X git-shell$X git-cvsserver '$(DESTDIR_SQ)$(bindir_SQ)'
+       $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
 ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
 endif
+ifndef NO_PYTHON
+       $(MAKE) -C git_remote_helpers prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
+endif
 ifndef NO_TCLTK
        $(MAKE) -C gitk-git install
        $(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install
@@ -1804,6 +1820,7 @@ endif
 ifneq (,$X)
        $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';)
 endif
+
        bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \
        execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \
        { test "$$bindir/" = "$$execdir/" || \
@@ -1817,6 +1834,12 @@ endif
                ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
                cp "$$execdir/git$X" "$$execdir/$$p" || exit; \
          done; } && \
+       { for p in $(REMOTE_CURL_ALIASES); do \
+               $(RM) "$$execdir/$$p" && \
+               ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
+               ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
+               cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; \
+         done; } && \
        ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
 
 install-doc:
@@ -1903,6 +1926,7 @@ clean:
                $(LIB_FILE) $(XDIFF_LIB)
        $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X
        $(RM) $(TEST_PROGRAMS)
+       $(RM) -r bin-wrappers
        $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope*
        $(RM) -r autom4te.cache
        $(RM) config.log config.mak.autogen config.mak.append config.status config.cache
@@ -1913,6 +1937,9 @@ clean:
 ifndef NO_PERL
        $(RM) gitweb/gitweb.cgi
        $(MAKE) -C perl clean
+endif
+ifndef NO_PYTHON
+       $(MAKE) -C git_remote_helpers clean
 endif
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
@@ -1924,8 +1951,7 @@ endif
 
 .PHONY: all install clean strip
 .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
-.PHONY: .FORCE-GIT-VERSION-FILE TAGS tags cscope .FORCE-GIT-CFLAGS
-.PHONY: .FORCE-GIT-BUILD-OPTIONS
+.PHONY: FORCE TAGS tags cscope
 
 ### Check documentation
 #
index d57124075c1451ae51eb17935669a4cedff4c605..7b9bde663bd5b2b8eec6b7e91c1de275f6afa460 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.6.6.1.txt
\ No newline at end of file
+Documentation/RelNotes-1.7.0.txt
\ No newline at end of file
index cb666acc3cf8fdda777febbefac455f3667759f1..936d98ba2ba2c597f69188c8e7f9f1abcc7169a8 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -3,6 +3,8 @@
 int advice_push_nonfastforward = 1;
 int advice_status_hints = 1;
 int advice_commit_before_merge = 1;
+int advice_resolve_conflict = 1;
+int advice_implicit_identity = 1;
 
 static struct {
        const char *name;
@@ -11,6 +13,8 @@ static struct {
        { "pushnonfastforward", &advice_push_nonfastforward },
        { "statushints", &advice_status_hints },
        { "commitbeforemerge", &advice_commit_before_merge },
+       { "resolveconflict", &advice_resolve_conflict },
+       { "implicitidentity", &advice_implicit_identity },
 };
 
 int git_default_advice_config(const char *var, const char *value)
@@ -27,3 +31,17 @@ int git_default_advice_config(const char *var, const char *value)
 
        return 0;
 }
+
+void NORETURN die_resolve_conflict(const char *me)
+{
+       if (advice_resolve_conflict)
+               /*
+                * Message used both when 'git commit' fails and when
+                * other commands doing a merge do.
+                */
+               die("'%s' is not possible because you have unmerged files.\n"
+                   "Please, fix them up in the work tree, and then use 'git add/rm <file>' as\n"
+                   "appropriate to mark resolution and make a commit, or use 'git commit -a'.", me);
+       else
+               die("'%s' is not possible because you have unmerged files.", me);
+}
index 3de5000d8cb35c1d2c31867b942a54b487bed836..9b7a3ad1ca126cb882e87d400820043043061f78 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -1,10 +1,16 @@
 #ifndef ADVICE_H
 #define ADVICE_H
 
+#include "git-compat-util.h"
+
 extern int advice_push_nonfastforward;
 extern int advice_status_hints;
 extern int advice_commit_before_merge;
+extern int advice_resolve_conflict;
+extern int advice_implicit_identity;
 
 int git_default_advice_config(const char *var, const char *value);
 
+extern void NORETURN die_resolve_conflict(const char *me);
+
 #endif /* ADVICE_H */
index 55b273246e006ad55c51d3e5cb6bed3153ae8cf4..d700af3b62f35091f9c628a5a2c0d8449e2fe439 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -87,8 +87,8 @@ static void setup_archive_check(struct git_attr_check *check)
        static struct git_attr *attr_export_subst;
 
        if (!attr_export_ignore) {
-               attr_export_ignore = git_attr("export-ignore", 13);
-               attr_export_subst = git_attr("export-subst", 12);
+               attr_export_ignore = git_attr("export-ignore");
+               attr_export_subst = git_attr("export-subst");
        }
        check[0].attr = attr_export_ignore;
        check[1].attr = attr_export_subst;
@@ -211,10 +211,33 @@ static const struct archiver *lookup_archiver(const char *name)
        return NULL;
 }
 
+static int reject_entry(const unsigned char *sha1, const char *base,
+                       int baselen, const char *filename, unsigned mode,
+                       int stage, void *context)
+{
+       return -1;
+}
+
+static int path_exists(struct tree *tree, const char *path)
+{
+       const char *pathspec[] = { path, NULL };
+
+       if (read_tree_recursive(tree, "", 0, 0, pathspec, reject_entry, NULL))
+               return 1;
+       return 0;
+}
+
 static void parse_pathspec_arg(const char **pathspec,
                struct archiver_args *ar_args)
 {
-       ar_args->pathspec = get_pathspec("", pathspec);
+       ar_args->pathspec = pathspec = get_pathspec("", pathspec);
+       if (pathspec) {
+               while (*pathspec) {
+                       if (!path_exists(ar_args->tree, *pathspec))
+                               die("path not found: %s", *pathspec);
+                       pathspec++;
+               }
+       }
 }
 
 static void parse_treeish_arg(const char **argv,
diff --git a/attr.c b/attr.c
index 55bdb7cdebea7f7ea551231fe7801f272d128d69..f5346ed32a1b5caf908021805214fd97e033eb27 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -65,7 +65,7 @@ static int invalid_attr_name(const char *name, int namelen)
        return 0;
 }
 
-struct git_attr *git_attr(const char *name, int len)
+static struct git_attr *git_attr_internal(const char *name, int len)
 {
        unsigned hval = hash_name(name, len);
        unsigned pos = hval % HASHSIZE;
@@ -95,6 +95,11 @@ struct git_attr *git_attr(const char *name, int len)
        return a;
 }
 
+struct git_attr *git_attr(const char *name)
+{
+       return git_attr_internal(name, strlen(name));
+}
+
 /*
  * .gitattributes file is one line per record, each of which is
  *
@@ -162,7 +167,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
                else {
                        e->setto = xmemdupz(equals + 1, ep - equals - 1);
                }
-               e->attr = git_attr(cp, len);
+               e->attr = git_attr_internal(cp, len);
        }
        (*num_attr)++;
        return ep + strspn(ep, blank);
@@ -221,7 +226,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
                              sizeof(struct attr_state) * num_attr +
                              (is_macro ? 0 : namelen + 1));
                if (is_macro)
-                       res->u.attr = git_attr(name, namelen);
+                       res->u.attr = git_attr_internal(name, namelen);
                else {
                        res->u.pattern = (char *)&(res->state[num_attr]);
                        memcpy(res->u.pattern, name, namelen);
diff --git a/attr.h b/attr.h
index 69b5767ebc2189a8bf9d98ff88c1885ec8fcdb7d..450f49d648a013ffddc6321b7fd79b3fc1b66f7a 100644 (file)
--- a/attr.h
+++ b/attr.h
@@ -8,7 +8,7 @@ struct git_attr;
  * Given a string, return the gitattribute object that
  * corresponds to it.
  */
-struct git_attr *git_attr(const char *, int);
+struct git_attr *git_attr(const char *);
 
 /* Internal use */
 extern const char git_attr__true[];
index b779a9589aae28ed8e92ca64309fb62936480e9d..6dc27ee7a6090e56d5b0f2072a72553d3b3e3b87 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -593,7 +593,7 @@ struct commit_list *filter_skipped(struct commit_list *list,
  * is increased by one between each call, but that should not matter
  * for this application.
  */
-int get_prn(int count) {
+static int get_prn(int count) {
        count = count * 1103515245 + 12345;
        return ((unsigned)(count/65536) % PRN_MODULO);
 }
@@ -813,11 +813,11 @@ static void handle_skipped_merge_base(const unsigned char *mb)
        char *bad_hex = sha1_to_hex(current_bad_sha1);
        char *good_hex = join_sha1_array_hex(&good_revs, ' ');
 
-       fprintf(stderr, "Warning: the merge base between %s and [%s] "
+       warning("the merge base between %s and [%s] "
                "must be skipped.\n"
                "So we cannot be sure the first bad commit is "
                "between %s and %s.\n"
-               "We continue anyway.\n",
+               "We continue anyway.",
                bad_hex, good_hex, mb_hex, bad_hex);
        free(good_hex);
 }
index 82f8fc1910a42c5c90ad3312d9aa8f35ef2b992f..0862ce56d76e9b08ab913e6a472fac590974340e 100644 (file)
--- a/bisect.h
+++ b/bisect.h
@@ -27,8 +27,6 @@ struct rev_list_info {
        const char *header_prefix;
 };
 
-extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all);
-
 extern int bisect_next_all(const char *prefix);
 
 extern int estimate_bisect_steps(int all);
diff --git a/blob.c b/blob.c
index bd7d078e1ae5fe4ce0a16fda62a2c1743237941b..ae320bd8fa22aaaef8144bd6bc35c20d1e85e4f4 100644 (file)
--- a/blob.c
+++ b/blob.c
@@ -23,24 +23,3 @@ int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size)
        item->object.parsed = 1;
        return 0;
 }
-
-int parse_blob(struct blob *item)
-{
-        enum object_type type;
-        void *buffer;
-        unsigned long size;
-       int ret;
-
-        if (item->object.parsed)
-                return 0;
-        buffer = read_sha1_file(item->object.sha1, &type, &size);
-        if (!buffer)
-                return error("Could not read %s",
-                             sha1_to_hex(item->object.sha1));
-        if (type != OBJ_BLOB)
-                return error("Object %s not a blob",
-                             sha1_to_hex(item->object.sha1));
-       ret = parse_blob_buffer(item, buffer, size);
-       free(buffer);
-       return ret;
-}
diff --git a/blob.h b/blob.h
index ea5d9e9f8b63be2c7048d19ee53feb06b0795c80..59b394eea38494d5dfa525e28ca949e5a03efcf5 100644 (file)
--- a/blob.h
+++ b/blob.h
@@ -13,6 +13,13 @@ struct blob *lookup_blob(const unsigned char *sha1);
 
 int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size);
 
-int parse_blob(struct blob *item);
+/**
+ * Blobs do not contain references to other objects and do not have
+ * structured data that needs parsing. However, code may use the
+ * "parsed" bit in the struct object for a blob to determine whether
+ * its content has been found to actually be available, so
+ * parse_blob_buffer() is used (by object.c) to flag that the object
+ * has been read successfully from the database.
+ **/
 
 #endif /* BLOB_H */
index 05ef3f5c9ce6e1b55a7eb483a197fe9fd6e5437d..9e1f63ed8dbe8b087f99292880059642d9744697 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -49,9 +49,19 @@ static int should_setup_rebase(const char *origin)
 
 void install_branch_config(int flag, const char *local, const char *origin, const char *remote)
 {
+       const char *shortname = remote + 11;
+       int remote_is_branch = !prefixcmp(remote, "refs/heads/");
        struct strbuf key = STRBUF_INIT;
        int rebasing = should_setup_rebase(origin);
 
+       if (remote_is_branch
+           && !strcmp(local, shortname)
+           && !origin) {
+               warning("Not setting branch %s as its own upstream.",
+                       local);
+               return;
+       }
+
        strbuf_addf(&key, "branch.%s.remote", local);
        git_config_set(key.buf, origin ? origin : ".");
 
@@ -71,8 +81,8 @@ void install_branch_config(int flag, const char *local, const char *origin, cons
                strbuf_addstr(&key, origin ? "remote" : "local");
 
                /* Are we tracking a proper "branch"? */
-               if (!prefixcmp(remote, "refs/heads/")) {
-                       strbuf_addf(&key, " branch %s", remote + 11);
+               if (remote_is_branch) {
+                       strbuf_addf(&key, " branch %s", shortname);
                        if (origin)
                                strbuf_addf(&key, " from %s", origin);
                }
@@ -108,6 +118,7 @@ static int setup_tracking(const char *new_ref, const char *orig_ref,
                switch (track) {
                case BRANCH_TRACK_ALWAYS:
                case BRANCH_TRACK_EXPLICIT:
+               case BRANCH_TRACK_OVERRIDE:
                        break;
                default:
                        return 1;
@@ -128,18 +139,25 @@ void create_branch(const char *head,
                   const char *name, const char *start_name,
                   int force, int reflog, enum branch_track track)
 {
-       struct ref_lock *lock;
+       struct ref_lock *lock = NULL;
        struct commit *commit;
        unsigned char sha1[20];
        char *real_ref, msg[PATH_MAX + 20];
        struct strbuf ref = STRBUF_INIT;
        int forcing = 0;
+       int dont_change_ref = 0;
+       int explicit_tracking = 0;
+
+       if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
+               explicit_tracking = 1;
 
        if (strbuf_check_branch_ref(&ref, name))
                die("'%s' is not a valid branch name.", name);
 
        if (resolve_ref(ref.buf, sha1, 1, NULL)) {
-               if (!force)
+               if (!force && track == BRANCH_TRACK_OVERRIDE)
+                       dont_change_ref = 1;
+               else if (!force)
                        die("A branch named '%s' already exists.", name);
                else if (!is_bare_repository() && !strcmp(head, name))
                        die("Cannot force update the current branch.");
@@ -153,12 +171,12 @@ void create_branch(const char *head,
        switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
        case 0:
                /* Not branching from any existing branch */
-               if (track == BRANCH_TRACK_EXPLICIT)
+               if (explicit_tracking)
                        die("Cannot setup tracking information; starting point is not a branch.");
                break;
        case 1:
                /* Unique completion -- good, only if it is a real ref */
-               if (track == BRANCH_TRACK_EXPLICIT && !strcmp(real_ref, "HEAD"))
+               if (explicit_tracking && !strcmp(real_ref, "HEAD"))
                        die("Cannot setup tracking information; starting point is not a branch.");
                break;
        default:
@@ -170,9 +188,11 @@ void create_branch(const char *head,
                die("Not a valid branch point: '%s'.", start_name);
        hashcpy(sha1, commit->object.sha1);
 
-       lock = lock_any_ref_for_update(ref.buf, NULL, 0);
-       if (!lock)
-               die_errno("Failed to lock ref for update");
+       if (!dont_change_ref) {
+               lock = lock_any_ref_for_update(ref.buf, NULL, 0);
+               if (!lock)
+                       die_errno("Failed to lock ref for update");
+       }
 
        if (reflog)
                log_all_ref_updates = 1;
@@ -180,15 +200,16 @@ void create_branch(const char *head,
        if (forcing)
                snprintf(msg, sizeof msg, "branch: Reset from %s",
                         start_name);
-       else
+       else if (!dont_change_ref)
                snprintf(msg, sizeof msg, "branch: Created from %s",
                         start_name);
 
        if (real_ref && track)
                setup_tracking(name, real_ref, track);
 
-       if (write_ref_sha1(lock, sha1, msg) < 0)
-               die_errno("Failed to write ref");
+       if (!dont_change_ref)
+               if (write_ref_sha1(lock, sha1, msg) < 0)
+                       die_errno("Failed to write ref");
 
        strbuf_release(&ref);
        free(real_ref);
index cb6e5906fb76f2460f212c54b557e5ae0ce23579..2705f8d057a93f7b4a9351713b89fd9f4e041815 100644 (file)
@@ -11,6 +11,7 @@
 #include "run-command.h"
 #include "parse-options.h"
 #include "diff.h"
+#include "diffcore.h"
 #include "revision.h"
 
 static const char * const builtin_add_usage[] = {
@@ -20,6 +21,81 @@ static const char * const builtin_add_usage[] = {
 static int patch_interactive, add_interactive, edit_interactive;
 static int take_worktree_changes;
 
+struct update_callback_data
+{
+       int flags;
+       int add_errors;
+};
+
+static void update_callback(struct diff_queue_struct *q,
+                           struct diff_options *opt, void *cbdata)
+{
+       int i;
+       struct update_callback_data *data = cbdata;
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               const char *path = p->one->path;
+               switch (p->status) {
+               default:
+                       die("unexpected diff status %c", p->status);
+               case DIFF_STATUS_UNMERGED:
+                       /*
+                        * ADD_CACHE_IGNORE_REMOVAL is unset if "git
+                        * add -u" is calling us, In such a case, a
+                        * missing work tree file needs to be removed
+                        * if there is an unmerged entry at stage #2,
+                        * but such a diff record is followed by
+                        * another with DIFF_STATUS_DELETED (and if
+                        * there is no stage #2, we won't see DELETED
+                        * nor MODIFIED).  We can simply continue
+                        * either way.
+                        */
+                       if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL))
+                               continue;
+                       /*
+                        * Otherwise, it is "git add path" is asking
+                        * to explicitly add it; we fall through.  A
+                        * missing work tree file is an error and is
+                        * caught by add_file_to_index() in such a
+                        * case.
+                        */
+               case DIFF_STATUS_MODIFIED:
+               case DIFF_STATUS_TYPE_CHANGED:
+                       if (add_file_to_index(&the_index, path, data->flags)) {
+                               if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
+                                       die("updating files failed");
+                               data->add_errors++;
+                       }
+                       break;
+               case DIFF_STATUS_DELETED:
+                       if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
+                               break;
+                       if (!(data->flags & ADD_CACHE_PRETEND))
+                               remove_file_from_index(&the_index, path);
+                       if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
+                               printf("remove '%s'\n", path);
+                       break;
+               }
+       }
+}
+
+int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
+{
+       struct update_callback_data data;
+       struct rev_info rev;
+       init_revisions(&rev, prefix);
+       setup_revisions(0, NULL, &rev, NULL);
+       rev.prune_data = pathspec;
+       rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = update_callback;
+       data.flags = flags;
+       data.add_errors = 0;
+       rev.diffopt.format_callback_data = &data;
+       run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
+       return !!data.add_errors;
+}
+
 static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
 {
        int num_unmatched = 0, i;
index 36e2f9dda5c85c346e31f45afa6d28b107679970..2a1004d025fcfdea4d317ef3236ff6bc76e3e65a 100644 (file)
@@ -404,6 +404,9 @@ static char *squash_slash(char *name)
 {
        int i = 0, j = 0;
 
+       if (!name)
+               return NULL;
+
        while (name[i]) {
                if ((name[j++] = name[i++]) == '/')
                        while (name[i] == '/')
@@ -416,7 +419,10 @@ static char *squash_slash(char *name)
 static char *find_name(const char *line, char *def, int p_value, int terminate)
 {
        int len;
-       const char *start = line;
+       const char *start = NULL;
+
+       if (p_value == 0)
+               start = line;
 
        if (*line == '"') {
                struct strbuf name = STRBUF_INIT;
@@ -686,7 +692,7 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name,
                if (isnull)
                        die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
                another = find_name(line, NULL, p_value, TERM_TAB);
-               if (!another || memcmp(another, name, len))
+               if (!another || memcmp(another, name, len + 1))
                        die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
                free(another);
                return orig_name;
@@ -1199,7 +1205,8 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
                                continue;
                        if (!patch->old_name && !patch->new_name) {
                                if (!patch->def_name)
-                                       die("git diff header lacks filename information (line %d)", linenr);
+                                       die("git diff header lacks filename information when removing "
+                                           "%d leading pathname components (line %d)" , p_value, linenr);
                                patch->old_name = patch->new_name = patch->def_name;
                        }
                        patch->is_toplevel_relative = 1;
@@ -2666,7 +2673,7 @@ static int verify_index_match(struct cache_entry *ce, struct stat *st)
                        return -1;
                return 0;
        }
-       return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID);
+       return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
 }
 
 static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
index faf4554d5edbb21dbd043daec669bc684bf056cc..6a887f5a9d785d23f1be76fd86fc17896b2b48d7 100644 (file)
@@ -5,6 +5,7 @@
 #include "cache.h"
 #include "builtin.h"
 #include "archive.h"
+#include "transport.h"
 #include "parse-options.h"
 #include "pkt-line.h"
 #include "sideband.h"
@@ -25,12 +26,16 @@ static void create_output_file(const char *output_file)
 static int run_remote_archiver(int argc, const char **argv,
                               const char *remote, const char *exec)
 {
-       char *url, buf[LARGE_PACKET_MAX];
+       char buf[LARGE_PACKET_MAX];
        int fd[2], i, len, rv;
-       struct child_process *conn;
+       struct transport *transport;
+       struct remote *_remote;
 
-       url = xstrdup(remote);
-       conn = git_connect(fd, url, exec, 0);
+       _remote = remote_get(remote);
+       if (!_remote->url[0])
+               die("git archive: Remote with no URL");
+       transport = transport_get(_remote, _remote->url[0]);
+       transport_connect(transport, "git-upload-archive", exec, fd);
 
        for (i = 1; i < argc; i++)
                packet_write(fd[1], "argument %s\n", argv[i]);
@@ -53,9 +58,7 @@ static int run_remote_archiver(int argc, const char **argv,
 
        /* Now, start reading from fd[0] and spit it out to stdout */
        rv = recv_sideband("archive", fd[0], 1);
-       close(fd[0]);
-       close(fd[1]);
-       rv |= finish_connect(conn);
+       rv |= transport_disconnect(transport);
 
        return !!rv;
 }
index ddc9f2dab7f3b55e4cd0cffbf303a3ad108fd431..a28a13986d11ebfecd11a206d1b7a1fb626865db 100644 (file)
@@ -93,9 +93,60 @@ static const char *branch_get_color(enum color_branch ix)
        return "";
 }
 
+static int branch_merged(int kind, const char *name,
+                        struct commit *rev, struct commit *head_rev)
+{
+       /*
+        * This checks whether the merge bases of branch and HEAD (or
+        * the other branch this branch builds upon) contains the
+        * branch, which means that the branch has already been merged
+        * safely to HEAD (or the other branch).
+        */
+       struct commit *reference_rev = NULL;
+       const char *reference_name = NULL;
+       int merged;
+
+       if (kind == REF_LOCAL_BRANCH) {
+               struct branch *branch = branch_get(name);
+               unsigned char sha1[20];
+
+               if (branch &&
+                   branch->merge &&
+                   branch->merge[0] &&
+                   branch->merge[0]->dst &&
+                   (reference_name =
+                    resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
+                       reference_rev = lookup_commit_reference(sha1);
+       }
+       if (!reference_rev)
+               reference_rev = head_rev;
+
+       merged = in_merge_bases(rev, &reference_rev, 1);
+
+       /*
+        * After the safety valve is fully redefined to "check with
+        * upstream, if any, otherwise with HEAD", we should just
+        * return the result of the in_merge_bases() above without
+        * any of the following code, but during the transition period,
+        * a gentle reminder is in order.
+        */
+       if ((head_rev != reference_rev) &&
+           in_merge_bases(rev, &head_rev, 1) != merged) {
+               if (merged)
+                       warning("deleting branch '%s' that has been merged to\n"
+                               "         '%s', but it is not yet merged to HEAD.",
+                               name, reference_name);
+               else
+                       warning("not deleting branch '%s' that is not yet merged to\n"
+                               "         '%s', even though it is merged to HEAD.",
+                               name, reference_name);
+       }
+       return merged;
+}
+
 static int delete_branches(int argc, const char **argv, int force, int kinds)
 {
-       struct commit *rev, *head_rev = head_rev;
+       struct commit *rev, *head_rev = NULL;
        unsigned char sha1[20];
        char *name = NULL;
        const char *fmt, *remote;
@@ -148,15 +199,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                        continue;
                }
 
-               /* This checks whether the merge bases of branch and
-                * HEAD contains branch -- which means that the HEAD
-                * contains everything in both.
-                */
-
-               if (!force &&
-                   !in_merge_bases(rev, &head_rev, 1)) {
-                       error("The branch '%s' is not an ancestor of "
-                             "your current HEAD.\n"
+               if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) {
+                       error("The branch '%s' is not fully merged.\n"
                              "If you are sure you want to delete it, "
                              "run 'git branch -D %s'.", bname.buf, bname.buf);
                        ret = 1;
@@ -564,6 +608,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT__VERBOSE(&verbose),
                OPT_SET_INT('t', "track",  &track, "set up tracking mode (see git-pull(1))",
                        BRANCH_TRACK_EXPLICIT),
+               OPT_SET_INT( 0, "set-upstream",  &track, "change upstream info",
+                       BRANCH_TRACK_OVERRIDE),
                OPT_BOOLEAN( 0 , "color",  &branch_use_color, "use colored output"),
                OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
                        REF_REMOTE_BRANCH),
index 8bd043009829fb4bb40cd2f730cbffa27c3791cf..3016d29caa610caf4618e9bc1684a532fd3a18a1 100644 (file)
@@ -106,7 +106,7 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
                const char *name;
                struct git_attr *a;
                name = argv[i];
-               a = git_attr(name, strlen(name));
+               a = git_attr(name);
                if (!a)
                        return error("%s: not a valid attribute name", name);
                check[i].attr = a;
index 2708669383e21f038a1404e061b34bc4302e8a93..527781728e0706b906a94ddfb8ee2e8bb06fa05e 100644 (file)
@@ -17,6 +17,7 @@
 #include "blob.h"
 #include "xdiff-interface.h"
 #include "ll-merge.h"
+#include "resolve-undo.h"
 
 static const char * const checkout_usage[] = {
        "git checkout [options] <branch>",
@@ -167,7 +168,7 @@ static int checkout_merged(int pos, struct checkout *state)
        fill_mm(active_cache[pos+2]->sha1, &theirs);
 
        status = ll_merge(&result_buf, path, &ancestor,
-                         &ours, "ours", &theirs, "theirs", 1);
+                         &ours, "ours", &theirs, "theirs", 0);
        free(ancestor.ptr);
        free(ours.ptr);
        free(theirs.ptr);
@@ -234,6 +235,10 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
        if (report_path_error(ps_matched, pathspec, 0))
                return 1;
 
+       /* "checkout -m path" to recreate conflicted state */
+       if (opts->merge)
+               unmerge_cache(pathspec);
+
        /* Any unmerged paths? */
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
@@ -370,6 +375,7 @@ static int merge_working_tree(struct checkout_opts *opts,
        if (read_cache_preload(NULL) < 0)
                return error("corrupt index file");
 
+       resolve_undo_clear();
        if (opts->force) {
                ret = reset_tree(new->commit->tree, opts, 1);
                if (ret)
@@ -696,7 +702,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
         * case 3: git checkout <something> [<paths>]
         *
         *   With no paths, if <something> is a commit, that is to
-        *   switch to the branch or detach HEAD at it.
+        *   switch to the branch or detach HEAD at it.  As a special case,
+        *   if <something> is A...B (missing A or B means HEAD but you can
+        *   omit at most one side), and if there is a unique merge base
+        *   between A and B, A...B names that merge base.
         *
         *   With no paths, if <something> is _not_ a commit, no -t nor -b
         *   was given, and there is a tracking branch whose name is
@@ -722,7 +731,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                if (!strcmp(arg, "-"))
                        arg = "@{-1}";
 
-               if (get_sha1(arg, rev)) {
+               if (get_sha1_mb(arg, rev)) {
                        if (has_dash_dash)          /* case (1) */
                                die("invalid reference: %s", arg);
                        if (!patch_mode &&
@@ -749,8 +758,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                new.name = arg;
                if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
                        setup_branch_path(&new);
-                       if (resolve_ref(new.path, rev, 1, NULL))
-                               new.commit = lookup_commit_reference(rev);
+
+                       if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) &&
+                           resolve_ref(new.path, rev, 1, NULL))
+                               ;
                        else
                                new.path = NULL;
                        parse_commit(new.commit);
index 28cdcd0274dd5891c7ad3dc3804cb0acead883f2..fac64e6cd3717dfffb0f54124de9545f7cdc391a 100644 (file)
@@ -67,19 +67,21 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                die("-x and -X cannot be used together");
 
        if (!show_only && !force)
-               die("clean.requireForce%s set and -n or -f not given; "
-                   "refusing to clean", config_set ? "" : " not");
+               die("clean.requireForce %s to true and neither -n nor -f given; "
+                   "refusing to clean", config_set ? "set" : "defaults");
 
        if (force > 1)
                rm_flags = 0;
 
        dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
 
+       if (read_cache() < 0)
+               die("index file corrupt");
+
        if (!ignored)
                setup_standard_excludes(&dir);
 
        pathspec = get_pathspec(prefix, argv);
-       read_cache();
 
        fill_directory(&dir, pathspec);
 
index caf3025031d83dc860b22c7798dd5f40b6b89ed2..58bacbd552c1e2496034346265a3e5ab219e2672 100644 (file)
@@ -44,10 +44,13 @@ static char *option_origin = NULL;
 static char *option_branch = NULL;
 static char *option_upload_pack = "git-upload-pack";
 static int option_verbose;
+static int option_progress;
 
 static struct option builtin_clone_options[] = {
        OPT__QUIET(&option_quiet),
        OPT__VERBOSE(&option_verbose),
+       OPT_BOOLEAN(0, "progress", &option_progress,
+                       "force progress reporting"),
        OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
                    "don't create a checkout"),
        OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
@@ -362,9 +365,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        const char *repo_name, *repo, *work_tree, *git_dir;
        char *path, *dir;
        int dest_exists;
-       const struct ref *refs, *remote_head, *mapped_refs;
+       const struct ref *refs, *remote_head;
        const struct ref *remote_head_points_at;
        const struct ref *our_head_points_at;
+       struct ref *mapped_refs;
        struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
        struct transport *transport = NULL;
@@ -525,6 +529,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                if (option_quiet)
                        transport->verbose = -1;
                else if (option_verbose)
+                       transport->verbose = 1;
+
+               if (option_progress)
                        transport->progress = 1;
 
                if (option_upload_pack)
index ddcb7a4bbbcd45dfd7437209f3187fff882e6e62..90dac349a3137405baaea63bfb6c798d2f8c92a3 100644 (file)
@@ -9,8 +9,6 @@
 #include "builtin.h"
 #include "utf8.h"
 
-#define BLOCKING (1ul << 14)
-
 /*
  * FIXME! Share the code with "write-tree.c"
  */
index 83b7c353ed6e4ca99a9bfae041c41280d31122a7..55676fd874466c70445e81c69cf397cd01380aaf 100644 (file)
@@ -24,6 +24,7 @@
 #include "string-list.h"
 #include "rerere.h"
 #include "unpack-trees.h"
+#include "quote.h"
 
 static const char * const builtin_commit_usage[] = {
        "git commit [options] [--] <filepattern>...",
@@ -35,7 +36,20 @@ static const char * const builtin_status_usage[] = {
        NULL
 };
 
-static unsigned char head_sha1[20], merge_head_sha1[20];
+static const char implicit_ident_advice[] =
+"Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+"    git config --global user.name Your Name\n"
+"    git config --global user.email you@example.com\n"
+"\n"
+"If the identity used for this commit is wrong, you can fix it with:\n"
+"\n"
+"    git commit --amend --author='Your Name <you@example.com>'\n";
+
+static unsigned char head_sha1[20];
+
 static char *use_message_buffer;
 static const char commit_editmsg[] = "COMMIT_EDITMSG";
 static struct lock_file index_lock; /* real index */
@@ -52,7 +66,7 @@ static char *edit_message, *use_message;
 static char *author_name, *author_email, *author_date;
 static int all, edit_flag, also, interactive, only, amend, signoff;
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
-static char *untracked_files_arg;
+static char *untracked_files_arg, *force_date;
 /*
  * The default commit message cleanup mode will remove the lines
  * beginning with # (shell comments) and leading and trailing
@@ -67,10 +81,17 @@ static enum {
 } cleanup_mode;
 static char *cleanup_arg;
 
-static int use_editor = 1, initial_commit, in_merge;
+static int use_editor = 1, initial_commit, in_merge, include_status = 1;
 static const char *only_include_assumed;
 static struct strbuf message;
 
+static int null_termination;
+static enum {
+       STATUS_FORMAT_LONG,
+       STATUS_FORMAT_SHORT,
+       STATUS_FORMAT_PORCELAIN,
+} status_format = STATUS_FORMAT_LONG;
+
 static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 {
        struct strbuf *buf = opt->value;
@@ -90,6 +111,7 @@ static struct option builtin_commit_options[] = {
        OPT_GROUP("Commit message options"),
        OPT_FILENAME('F', "file", &logfile, "read log from file"),
        OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
+       OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
        OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
        OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
        OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
@@ -98,6 +120,7 @@ static struct option builtin_commit_options[] = {
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
        OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
        OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+       OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
        /* end commit message options */
 
        OPT_GROUP("Commit contents options"),
@@ -107,6 +130,12 @@ static struct option builtin_commit_options[] = {
        OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
        OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
        OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
+       OPT_SET_INT(0, "short", &status_format, "show status concisely",
+                   STATUS_FORMAT_SHORT),
+       OPT_SET_INT(0, "porcelain", &status_format,
+                   "show porcelain output format", STATUS_FORMAT_PORCELAIN),
+       OPT_BOOLEAN('z', "null", &null_termination,
+                   "terminate entries with NUL"),
        OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
        { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
        OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
@@ -168,11 +197,15 @@ static int list_paths(struct string_list *list, const char *with_tree,
 
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
+               struct string_list_item *item;
+
                if (ce->ce_flags & CE_UPDATE)
                        continue;
                if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
                        continue;
-               string_list_insert(ce->name, list);
+               item = string_list_insert(ce->name, list);
+               if (ce_skip_worktree(ce))
+                       item->util = item; /* better a valid pointer than a fake one */
        }
 
        return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
@@ -185,6 +218,10 @@ static void add_remove_files(struct string_list *list)
                struct stat st;
                struct string_list_item *p = &(list->items[i]);
 
+               /* p->util is skip-worktree */
+               if (p->util)
+                       continue;
+
                if (!lstat(p->string, &st)) {
                        if (add_to_cache(p->string, &st, 0))
                                die("updating files failed");
@@ -221,6 +258,16 @@ static void create_base_index(void)
                exit(128); /* We've already reported the error, finish dying */
 }
 
+static void refresh_cache_or_die(int refresh_flags)
+{
+       /*
+        * refresh_flags contains REFRESH_QUIET, so the only errors
+        * are for unmerged entries.
+        */
+       if (refresh_cache(refresh_flags | REFRESH_IN_PORCELAIN))
+               die_resolve_conflict("commit");
+}
+
 static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
 {
        int fd;
@@ -260,7 +307,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
        if (all || (also && pathspec && *pathspec)) {
                int fd = hold_locked_index(&index_lock, 1);
                add_files_to_cache(also ? prefix : NULL, pathspec, 0);
-               refresh_cache(refresh_flags);
+               refresh_cache_or_die(refresh_flags);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
                        die("unable to write new_index file");
@@ -278,13 +325,11 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
         * We still need to refresh the index here.
         */
        if (!pathspec || !*pathspec) {
-               fd = hold_locked_index(&index_lock, !is_status);
-               refresh_cache(refresh_flags);
-               if (0 <= fd) {
-                       if (write_cache(fd, active_cache, active_nr) ||
-                           commit_locked_index(&index_lock))
-                               die("unable to write new_index file");
-               }
+               fd = hold_locked_index(&index_lock, 1);
+               refresh_cache_or_die(refresh_flags);
+               if (write_cache(fd, active_cache, active_nr) ||
+                   commit_locked_index(&index_lock))
+                       die("unable to write new_index file");
                commit_style = COMMIT_AS_IS;
                return get_index_file();
        }
@@ -310,7 +355,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
         */
        commit_style = COMMIT_PARTIAL;
 
-       if (file_exists(git_path("MERGE_HEAD")))
+       if (in_merge)
                die("cannot do a partial commit during a merge.");
 
        memset(&partial, 0, sizeof(partial));
@@ -351,6 +396,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
 static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
                      struct wt_status *s)
 {
+       unsigned char sha1[20];
+
        if (s->relative_paths)
                s->prefix = prefix;
 
@@ -362,8 +409,21 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
        s->index_file = index_file;
        s->fp = fp;
        s->nowarn = nowarn;
+       s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
+
+       wt_status_collect(s);
 
-       wt_status_print(s);
+       switch (status_format) {
+       case STATUS_FORMAT_SHORT:
+               wt_shortstatus_print(s, null_termination);
+               break;
+       case STATUS_FORMAT_PORCELAIN:
+               wt_porcelain_print(s, null_termination);
+               break;
+       case STATUS_FORMAT_LONG:
+               wt_status_print(s);
+               break;
+       }
 
        return s->commitable;
 }
@@ -414,6 +474,9 @@ static void determine_author_info(void)
                email = xstrndup(lb + 2, rb - (lb + 2));
        }
 
+       if (force_date)
+               date = force_date;
+
        author_name = name;
        author_email = email;
        author_date = date;
@@ -551,7 +614,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
        /* This checks if committer ident is explicitly given */
        git_committer_info(0);
-       if (use_editor) {
+       if (use_editor && include_status) {
                char *author_ident;
                const char *committer_ident;
 
@@ -593,7 +656,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                                author_ident);
                free(author_ident);
 
-               if (!user_ident_explicitly_given)
+               if (!user_ident_sufficiently_given())
                        fprintf(fp,
                                "%s"
                                "# Committer: %s\n",
@@ -739,6 +802,21 @@ static const char *find_author_by_nickname(const char *name)
        die("No existing author found with '%s'", name);
 }
 
+
+static void handle_untracked_files_arg(struct wt_status *s)
+{
+       if (!untracked_files_arg)
+               ; /* default already initialized */
+       else if (!strcmp(untracked_files_arg, "no"))
+               s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
+       else if (!strcmp(untracked_files_arg, "normal"))
+               s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
+       else if (!strcmp(untracked_files_arg, "all"))
+               s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+       else
+               die("Invalid untracked files mode '%s'", untracked_files_arg);
+}
+
 static int parse_and_validate_options(int argc, const char *argv[],
                                      const char * const usage[],
                                      const char *prefix,
@@ -765,9 +843,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
        if (get_sha1("HEAD", head_sha1))
                initial_commit = 1;
 
-       if (!get_sha1("MERGE_HEAD", merge_head_sha1))
-               in_merge = 1;
-
        /* Sanity check options */
        if (amend && initial_commit)
                die("You have nothing to amend.");
@@ -847,22 +922,18 @@ static int parse_and_validate_options(int argc, const char *argv[],
        else
                die("Invalid cleanup mode %s", cleanup_arg);
 
-       if (!untracked_files_arg)
-               ; /* default already initialized */
-       else if (!strcmp(untracked_files_arg, "no"))
-               s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
-       else if (!strcmp(untracked_files_arg, "normal"))
-               s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
-       else if (!strcmp(untracked_files_arg, "all"))
-               s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
-       else
-               die("Invalid untracked files mode '%s'", untracked_files_arg);
+       handle_untracked_files_arg(s);
 
        if (all && argc > 0)
                die("Paths with -a does not make sense.");
        else if (interactive && argc > 0)
                die("Paths with --interactive does not make sense.");
 
+       if (null_termination && status_format == STATUS_FORMAT_LONG)
+               status_format = STATUS_FORMAT_PORCELAIN;
+       if (status_format != STATUS_FORMAT_LONG)
+               dry_run = 1;
+
        return argc;
 }
 
@@ -944,26 +1015,75 @@ static int git_status_config(const char *k, const char *v, void *cb)
 int cmd_status(int argc, const char **argv, const char *prefix)
 {
        struct wt_status s;
+       unsigned char sha1[20];
+       static struct option builtin_status_options[] = {
+               OPT__VERBOSE(&verbose),
+               OPT_SET_INT('s', "short", &status_format,
+                           "show status concisely", STATUS_FORMAT_SHORT),
+               OPT_SET_INT(0, "porcelain", &status_format,
+                           "show porcelain output format",
+                           STATUS_FORMAT_PORCELAIN),
+               OPT_BOOLEAN('z', "null", &null_termination,
+                           "terminate entries with NUL"),
+               { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
+                 "mode",
+                 "show untracked files, optional modes: all, normal, no. (Default: all)",
+                 PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+               OPT_END(),
+       };
+
+       if (null_termination && status_format == STATUS_FORMAT_LONG)
+               status_format = STATUS_FORMAT_PORCELAIN;
 
        wt_status_prepare(&s);
        git_config(git_status_config, &s);
+       in_merge = file_exists(git_path("MERGE_HEAD"));
+       argc = parse_options(argc, argv, prefix,
+                            builtin_status_options,
+                            builtin_status_usage, 0);
+       handle_untracked_files_arg(&s);
+
+       if (*argv)
+               s.pathspec = get_pathspec(prefix, argv);
+
+       read_cache();
+       refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
+       s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
+       s.in_merge = in_merge;
+       wt_status_collect(&s);
+
+       if (s.relative_paths)
+               s.prefix = prefix;
        if (s.use_color == -1)
                s.use_color = git_use_color_default;
        if (diff_use_color_default == -1)
                diff_use_color_default = git_use_color_default;
 
-       argc = parse_and_validate_options(argc, argv, builtin_status_usage,
-                                         prefix, &s);
-       return dry_run_commit(argc, argv, prefix, &s);
+       switch (status_format) {
+       case STATUS_FORMAT_SHORT:
+               wt_shortstatus_print(&s, null_termination);
+               break;
+       case STATUS_FORMAT_PORCELAIN:
+               wt_porcelain_print(&s, null_termination);
+               break;
+       case STATUS_FORMAT_LONG:
+               s.verbose = verbose;
+               wt_status_print(&s);
+               break;
+       }
+       return 0;
 }
 
 static void print_summary(const char *prefix, const unsigned char *sha1)
 {
        struct rev_info rev;
        struct commit *commit;
-       static const char *format = "format:%h] %s";
+       struct strbuf format = STRBUF_INIT;
        unsigned char junk_sha1[20];
        const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
+       struct pretty_print_context pctx = {0};
+       struct strbuf author_ident = STRBUF_INIT;
+       struct strbuf committer_ident = STRBUF_INIT;
 
        commit = lookup_commit(sha1);
        if (!commit)
@@ -971,6 +1091,25 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
        if (!commit || parse_commit(commit))
                die("could not parse newly created commit");
 
+       strbuf_addstr(&format, "format:%h] %s");
+
+       format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
+       format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
+       if (strbuf_cmp(&author_ident, &committer_ident)) {
+               strbuf_addstr(&format, "\n Author: ");
+               strbuf_addbuf_percentquote(&format, &author_ident);
+       }
+       if (!user_ident_sufficiently_given()) {
+               strbuf_addstr(&format, "\n Committer: ");
+               strbuf_addbuf_percentquote(&format, &committer_ident);
+               if (advice_implicit_identity) {
+                       strbuf_addch(&format, '\n');
+                       strbuf_addstr(&format, implicit_ident_advice);
+               }
+       }
+       strbuf_release(&author_ident);
+       strbuf_release(&committer_ident);
+
        init_revisions(&rev, prefix);
        setup_revisions(0, NULL, &rev, NULL);
 
@@ -981,7 +1120,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
 
        rev.verbose_header = 1;
        rev.show_root_diff = 1;
-       get_commit_format(format, &rev);
+       get_commit_format(format.buf, &rev);
        rev.always_show_header = 0;
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 100;
@@ -1000,10 +1139,11 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
                struct pretty_print_context ctx = {0};
                struct strbuf buf = STRBUF_INIT;
                ctx.date_mode = DATE_NORMAL;
-               format_commit_message(commit, format + 7, &buf, &ctx);
+               format_commit_message(commit, format.buf + 7, &buf, &ctx);
                printf("%s\n", buf.buf);
                strbuf_release(&buf);
        }
+       strbuf_release(&format);
 }
 
 static int git_commit_config(const char *k, const char *v, void *cb)
@@ -1012,6 +1152,10 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 
        if (!strcmp(k, "commit.template"))
                return git_config_pathname(&template_file, k, v);
+       if (!strcmp(k, "commit.status")) {
+               include_status = git_config_bool(k, v);
+               return 0;
+       }
 
        return git_status_config(k, v, s);
 }
@@ -1030,10 +1174,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
        wt_status_prepare(&s);
        git_config(git_commit_config, &s);
+       in_merge = file_exists(git_path("MERGE_HEAD"));
+       s.in_merge = in_merge;
 
        if (s.use_color == -1)
                s.use_color = git_use_color_default;
-
        argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
                                          prefix, &s);
        if (dry_run) {
@@ -1156,7 +1301,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                     "new_index file. Check that disk is not full or quota is\n"
                     "not exceeded, and then \"git reset HEAD\" to recover.");
 
-       rerere();
+       rerere(0);
        run_hook(get_index_file(), "post-commit", NULL);
        if (!quiet)
                print_summary(prefix, commit_sha1);
index a2d656edb383da47fb3622f1f7c2d1524285ac00..4bc46b15fde00d913577a2980778746f8315bb70 100644 (file)
@@ -45,6 +45,7 @@ static int end_null;
 #define TYPE_BOOL (1<<0)
 #define TYPE_INT (1<<1)
 #define TYPE_BOOL_OR_INT (1<<2)
+#define TYPE_PATH (1<<3)
 
 static struct option builtin_config_options[] = {
        OPT_GROUP("Config file location"),
@@ -69,6 +70,7 @@ static struct option builtin_config_options[] = {
        OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL),
        OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT),
        OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT),
+       OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH),
        OPT_GROUP("Other"),
        OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
        OPT_END(),
@@ -94,6 +96,7 @@ static int show_config(const char *key_, const char *value_, void *cb)
 {
        char value[256];
        const char *vptr = value;
+       int must_free_vptr = 0;
        int dup_error = 0;
 
        if (!use_key_regexp && strcmp(key_, key))
@@ -123,6 +126,9 @@ static int show_config(const char *key_, const char *value_, void *cb)
                        vptr = v ? "true" : "false";
                else
                        sprintf(value, "%d", v);
+       } else if (types == TYPE_PATH) {
+               git_config_pathname(&vptr, key_, value_);
+               must_free_vptr = 1;
        }
        else
                vptr = value_?value_:"";
@@ -133,6 +139,12 @@ static int show_config(const char *key_, const char *value_, void *cb)
        }
        else
                printf("%s%c", vptr, term);
+       if (must_free_vptr)
+               /* If vptr must be freed, it's a pointer to a
+                * dynamically allocated buffer, it's safe to cast to
+                * const.
+               */
+               free((char *)vptr);
 
        return 0;
 }
@@ -215,7 +227,13 @@ static char *normalize_value(const char *key, const char *value)
        if (!value)
                return NULL;
 
-       if (types == 0)
+       if (types == 0 || types == TYPE_PATH)
+               /*
+                * We don't do normalization for TYPE_PATH here: If
+                * the path is like ~/foobar/, we prefer to store
+                * "~/foobar/" in the config file, and to expand the ~
+                * when retrieving the value.
+                */
                normalized = xstrdup(value);
        else {
                normalized = xmalloc(64);
@@ -340,7 +358,7 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix)
                if (!is_absolute_path(given_config_file) && prefix)
                        config_exclusive_filename = prefix_filename(prefix,
                                                                    strlen(prefix),
-                                                                   argv[2]);
+                                                                   given_config_file);
                else
                        config_exclusive_filename = given_config_file;
        }
index 5b7db616dcf5cc3bb178b2c4dbb989322c6b2374..8654fa7a2dbe2c1ca76a493a4322447de5219b99 100644 (file)
@@ -322,7 +322,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
        if (!fp)
                return error("cannot open %s: %s\n", filename, strerror(errno));
 
-       url = transport_anonymize_url(raw_url);
+       if (raw_url)
+               url = transport_anonymize_url(raw_url);
+       else
+               url = xstrdup("foreign");
        for (rm = ref_map; rm; rm = rm->next) {
                struct ref *ref = NULL;
 
@@ -819,7 +822,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
        if (!remote)
                die("Where do you want to fetch from today?");
 
-       transport = transport_get(remote, remote->url[0]);
+       transport = transport_get(remote, NULL);
        if (verbosity >= 2)
                transport->verbose = verbosity <= 3 ? verbosity : 3;
        if (verbosity < 0)
index 093517e390044055039b9f4e58132c73d17b741d..c304638b7845601f184149d3c48cbc1ea1e195f5 100644 (file)
@@ -180,12 +180,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        char buf[80];
 
        struct option builtin_gc_options[] = {
+               OPT__QUIET(&quiet),
                { OPTION_STRING, 0, "prune", &prune_expire, "date",
                        "prune unreferenced objects",
                        PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
                OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
                OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
-               OPT_BOOLEAN('q', "quiet", &quiet, "suppress progress reports"),
                OPT_END()
        };
 
index c7d74fbb7fe26840d4084ad3fef2c8fd4674df6e..26d4deb1cce3e5540411a47fdf35085649669d06 100644 (file)
 #include "grep.h"
 #include "quote.h"
 
-#ifndef NO_EXTERNAL_GREP
-#ifdef __unix__
-#define NO_EXTERNAL_GREP 0
-#else
-#define NO_EXTERNAL_GREP 1
-#endif
+#ifndef NO_PTHREADS
+#include "thread-utils.h"
+#include <pthread.h>
 #endif
 
 static char const * const grep_usage[] = {
@@ -28,6 +25,266 @@ static char const * const grep_usage[] = {
        NULL
 };
 
+static int use_threads = 1;
+
+#ifndef NO_PTHREADS
+#define THREADS 8
+static pthread_t threads[THREADS];
+
+static void *load_sha1(const unsigned char *sha1, unsigned long *size,
+                      const char *name);
+static void *load_file(const char *filename, size_t *sz);
+
+enum work_type {WORK_SHA1, WORK_FILE};
+
+/* We use one producer thread and THREADS consumer
+ * threads. The producer adds struct work_items to 'todo' and the
+ * consumers pick work items from the same array.
+ */
+struct work_item
+{
+       enum work_type type;
+       char *name;
+
+       /* if type == WORK_SHA1, then 'identifier' is a SHA1,
+        * otherwise type == WORK_FILE, and 'identifier' is a NUL
+        * terminated filename.
+        */
+       void *identifier;
+       char done;
+       struct strbuf out;
+};
+
+/* In the range [todo_done, todo_start) in 'todo' we have work_items
+ * that have been or are processed by a consumer thread. We haven't
+ * written the result for these to stdout yet.
+ *
+ * The work_items in [todo_start, todo_end) are waiting to be picked
+ * up by a consumer thread.
+ *
+ * The ranges are modulo TODO_SIZE.
+ */
+#define TODO_SIZE 128
+static struct work_item todo[TODO_SIZE];
+static int todo_start;
+static int todo_end;
+static int todo_done;
+
+/* Has all work items been added? */
+static int all_work_added;
+
+/* This lock protects all the variables above. */
+static pthread_mutex_t grep_mutex;
+
+/* Used to serialize calls to read_sha1_file. */
+static pthread_mutex_t read_sha1_mutex;
+
+#define grep_lock() pthread_mutex_lock(&grep_mutex)
+#define grep_unlock() pthread_mutex_unlock(&grep_mutex)
+#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex)
+#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex)
+
+/* Signalled when a new work_item is added to todo. */
+static pthread_cond_t cond_add;
+
+/* Signalled when the result from one work_item is written to
+ * stdout.
+ */
+static pthread_cond_t cond_write;
+
+/* Signalled when we are finished with everything. */
+static pthread_cond_t cond_result;
+
+static void add_work(enum work_type type, char *name, void *id)
+{
+       grep_lock();
+
+       while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) {
+               pthread_cond_wait(&cond_write, &grep_mutex);
+       }
+
+       todo[todo_end].type = type;
+       todo[todo_end].name = name;
+       todo[todo_end].identifier = id;
+       todo[todo_end].done = 0;
+       strbuf_reset(&todo[todo_end].out);
+       todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
+
+       pthread_cond_signal(&cond_add);
+       grep_unlock();
+}
+
+static struct work_item *get_work(void)
+{
+       struct work_item *ret;
+
+       grep_lock();
+       while (todo_start == todo_end && !all_work_added) {
+               pthread_cond_wait(&cond_add, &grep_mutex);
+       }
+
+       if (todo_start == todo_end && all_work_added) {
+               ret = NULL;
+       } else {
+               ret = &todo[todo_start];
+               todo_start = (todo_start + 1) % ARRAY_SIZE(todo);
+       }
+       grep_unlock();
+       return ret;
+}
+
+static void grep_sha1_async(struct grep_opt *opt, char *name,
+                           const unsigned char *sha1)
+{
+       unsigned char *s;
+       s = xmalloc(20);
+       memcpy(s, sha1, 20);
+       add_work(WORK_SHA1, name, s);
+}
+
+static void grep_file_async(struct grep_opt *opt, char *name,
+                           const char *filename)
+{
+       add_work(WORK_FILE, name, xstrdup(filename));
+}
+
+static void work_done(struct work_item *w)
+{
+       int old_done;
+
+       grep_lock();
+       w->done = 1;
+       old_done = todo_done;
+       for(; todo[todo_done].done && todo_done != todo_start;
+           todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
+               w = &todo[todo_done];
+               write_or_die(1, w->out.buf, w->out.len);
+               free(w->name);
+               free(w->identifier);
+       }
+
+       if (old_done != todo_done)
+               pthread_cond_signal(&cond_write);
+
+       if (all_work_added && todo_done == todo_end)
+               pthread_cond_signal(&cond_result);
+
+       grep_unlock();
+}
+
+static void *run(void *arg)
+{
+       int hit = 0;
+       struct grep_opt *opt = arg;
+
+       while (1) {
+               struct work_item *w = get_work();
+               if (!w)
+                       break;
+
+               opt->output_priv = w;
+               if (w->type == WORK_SHA1) {
+                       unsigned long sz;
+                       void* data = load_sha1(w->identifier, &sz, w->name);
+
+                       if (data) {
+                               hit |= grep_buffer(opt, w->name, data, sz);
+                               free(data);
+                       }
+               } else if (w->type == WORK_FILE) {
+                       size_t sz;
+                       void* data = load_file(w->identifier, &sz);
+                       if (data) {
+                               hit |= grep_buffer(opt, w->name, data, sz);
+                               free(data);
+                       }
+               } else {
+                       assert(0);
+               }
+
+               work_done(w);
+       }
+       free_grep_patterns(arg);
+       free(arg);
+
+       return (void*) (intptr_t) hit;
+}
+
+static void strbuf_out(struct grep_opt *opt, const void *buf, size_t size)
+{
+       struct work_item *w = opt->output_priv;
+       strbuf_add(&w->out, buf, size);
+}
+
+static void start_threads(struct grep_opt *opt)
+{
+       int i;
+
+       pthread_mutex_init(&grep_mutex, NULL);
+       pthread_mutex_init(&read_sha1_mutex, NULL);
+       pthread_cond_init(&cond_add, NULL);
+       pthread_cond_init(&cond_write, NULL);
+       pthread_cond_init(&cond_result, NULL);
+
+       for (i = 0; i < ARRAY_SIZE(todo); i++) {
+               strbuf_init(&todo[i].out, 0);
+       }
+
+       for (i = 0; i < ARRAY_SIZE(threads); i++) {
+               int err;
+               struct grep_opt *o = grep_opt_dup(opt);
+               o->output = strbuf_out;
+               compile_grep_patterns(o);
+               err = pthread_create(&threads[i], NULL, run, o);
+
+               if (err)
+                       die("grep: failed to create thread: %s",
+                           strerror(err));
+       }
+}
+
+static int wait_all(void)
+{
+       int hit = 0;
+       int i;
+
+       grep_lock();
+       all_work_added = 1;
+
+       /* Wait until all work is done. */
+       while (todo_done != todo_end)
+               pthread_cond_wait(&cond_result, &grep_mutex);
+
+       /* Wake up all the consumer threads so they can see that there
+        * is no more work to do.
+        */
+       pthread_cond_broadcast(&cond_add);
+       grep_unlock();
+
+       for (i = 0; i < ARRAY_SIZE(threads); i++) {
+               void *h;
+               pthread_join(threads[i], &h);
+               hit |= (int) (intptr_t) h;
+       }
+
+       pthread_mutex_destroy(&grep_mutex);
+       pthread_mutex_destroy(&read_sha1_mutex);
+       pthread_cond_destroy(&cond_add);
+       pthread_cond_destroy(&cond_write);
+       pthread_cond_destroy(&cond_result);
+
+       return hit;
+}
+#else /* !NO_PTHREADS */
+#define read_sha1_lock()
+#define read_sha1_unlock()
+
+static int wait_all(void)
+{
+       return 0;
+}
+#endif
+
 static int grep_config(const char *var, const char *value, void *cb)
 {
        struct grep_opt *opt = cb;
@@ -42,8 +299,6 @@ static int grep_config(const char *var, const char *value, void *cb)
                opt->color = git_config_colorbool(var, value, -1);
                return 0;
        }
-       if (!strcmp(var, "color.grep.external"))
-               return git_config_string(&(opt->color_external), var, value);
        if (!strcmp(var, "color.grep.match")) {
                if (!value)
                        return config_error_nonbool(var);
@@ -153,37 +408,64 @@ static int pathspec_matches(const char **paths, const char *name, int max_depth)
        return 0;
 }
 
-static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len)
+static void *load_sha1(const unsigned char *sha1, unsigned long *size,
+                      const char *name)
 {
-       unsigned long size;
-       char *data;
        enum object_type type;
-       int hit;
-       struct strbuf pathbuf = STRBUF_INIT;
+       char *data;
+
+       read_sha1_lock();
+       data = read_sha1_file(sha1, &type, size);
+       read_sha1_unlock();
 
-       data = read_sha1_file(sha1, &type, &size);
-       if (!data) {
+       if (!data)
                error("'%s': unable to read %s", name, sha1_to_hex(sha1));
-               return 0;
-       }
+
+       return data;
+}
+
+static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
+                    const char *filename, int tree_name_len)
+{
+       struct strbuf pathbuf = STRBUF_INIT;
+       char *name;
+
        if (opt->relative && opt->prefix_length) {
-               quote_path_relative(name + tree_name_len, -1, &pathbuf, opt->prefix);
-               strbuf_insert(&pathbuf, 0, name, tree_name_len);
-               name = pathbuf.buf;
+               quote_path_relative(filename + tree_name_len, -1, &pathbuf,
+                                   opt->prefix);
+               strbuf_insert(&pathbuf, 0, filename, tree_name_len);
+       } else {
+               strbuf_addstr(&pathbuf, filename);
+       }
+
+       name = strbuf_detach(&pathbuf, NULL);
+
+#ifndef NO_PTHREADS
+       if (use_threads) {
+               grep_sha1_async(opt, name, sha1);
+               return 0;
+       } else
+#endif
+       {
+               int hit;
+               unsigned long sz;
+               void *data = load_sha1(sha1, &sz, name);
+               if (!data)
+                       hit = 0;
+               else
+                       hit = grep_buffer(opt, name, data, sz);
+
+               free(data);
+               free(name);
+               return hit;
        }
-       hit = grep_buffer(opt, name, data, size);
-       strbuf_release(&pathbuf);
-       free(data);
-       return hit;
 }
 
-static int grep_file(struct grep_opt *opt, const char *filename)
+static void *load_file(const char *filename, size_t *sz)
 {
        struct stat st;
-       int i;
        char *data;
-       size_t sz;
-       struct strbuf buf = STRBUF_INIT;
+       int i;
 
        if (lstat(filename, &st) < 0) {
        err_ret:
@@ -193,313 +475,60 @@ static int grep_file(struct grep_opt *opt, const char *filename)
        }
        if (!S_ISREG(st.st_mode))
                return 0;
-       sz = xsize_t(st.st_size);
+       *sz = xsize_t(st.st_size);
        i = open(filename, O_RDONLY);
        if (i < 0)
                goto err_ret;
-       data = xmalloc(sz + 1);
-       if (st.st_size != read_in_full(i, data, sz)) {
+       data = xmalloc(*sz + 1);
+       if (st.st_size != read_in_full(i, data, *sz)) {
                error("'%s': short read %s", filename, strerror(errno));
                close(i);
                free(data);
                return 0;
        }
        close(i);
-       data[sz] = 0;
-       if (opt->relative && opt->prefix_length)
-               filename = quote_path_relative(filename, -1, &buf, opt->prefix);
-       i = grep_buffer(opt, filename, data, sz);
-       strbuf_release(&buf);
-       free(data);
-       return i;
-}
-
-#if !NO_EXTERNAL_GREP
-static int exec_grep(int argc, const char **argv)
-{
-       pid_t pid;
-       int status;
-
-       argv[argc] = NULL;
-       pid = fork();
-       if (pid < 0)
-               return pid;
-       if (!pid) {
-               execvp("grep", (char **) argv);
-               exit(255);
-       }
-       while (waitpid(pid, &status, 0) < 0) {
-               if (errno == EINTR)
-                       continue;
-               return -1;
-       }
-       if (WIFEXITED(status)) {
-               if (!WEXITSTATUS(status))
-                       return 1;
-               return 0;
-       }
-       return -1;
+       data[*sz] = 0;
+       return data;
 }
 
-#define MAXARGS 1000
-#define ARGBUF 4096
-#define push_arg(a) do { \
-       if (nr < MAXARGS) argv[nr++] = (a); \
-       else die("maximum number of args exceeded"); \
-       } while (0)
-
-/*
- * If you send a singleton filename to grep, it does not give
- * the name of the file.  GNU grep has "-H" but we would want
- * that behaviour in a portable way.
- *
- * So we keep two pathnames in argv buffer unsent to grep in
- * the main loop if we need to do more than one grep.
- */
-static int flush_grep(struct grep_opt *opt,
-                     int argc, int arg0, const char **argv, int *kept)
+static int grep_file(struct grep_opt *opt, const char *filename)
 {
-       int status;
-       int count = argc - arg0;
-       const char *kept_0 = NULL;
-
-       if (count <= 2) {
-               /*
-                * Because we keep at least 2 paths in the call from
-                * the main loop (i.e. kept != NULL), and MAXARGS is
-                * far greater than 2, this usually is a call to
-                * conclude the grep.  However, the user could attempt
-                * to overflow the argv buffer by giving too many
-                * options to leave very small number of real
-                * arguments even for the call in the main loop.
-                */
-               if (kept)
-                       die("insanely many options to grep");
-
-               /*
-                * If we have two or more paths, we do not have to do
-                * anything special, but we need to push /dev/null to
-                * get "-H" behaviour of GNU grep portably but when we
-                * are not doing "-l" nor "-L" nor "-c".
-                */
-               if (count == 1 &&
-                   !opt->name_only &&
-                   !opt->unmatch_name_only &&
-                   !opt->count) {
-                       argv[argc++] = "/dev/null";
-                       argv[argc] = NULL;
-               }
-       }
-
-       else if (kept) {
-               /*
-                * Called because we found many paths and haven't finished
-                * iterating over the cache yet.  We keep two paths
-                * for the concluding call.  argv[argc-2] and argv[argc-1]
-                * has the last two paths, so save the first one away,
-                * replace it with NULL while sending the list to grep,
-                * and recover them after we are done.
-                */
-               *kept = 2;
-               kept_0 = argv[argc-2];
-               argv[argc-2] = NULL;
-               argc -= 2;
-       }
+       struct strbuf buf = STRBUF_INIT;
+       char *name;
 
-       if (opt->pre_context || opt->post_context) {
-               /*
-                * grep handles hunk marks between files, but we need to
-                * do that ourselves between multiple calls.
-                */
-               if (opt->show_hunk_mark)
-                       write_or_die(1, "--\n", 3);
+       if (opt->relative && opt->prefix_length)
+               quote_path_relative(filename, -1, &buf, opt->prefix);
+       else
+               strbuf_addstr(&buf, filename);
+       name = strbuf_detach(&buf, NULL);
+
+#ifndef NO_PTHREADS
+       if (use_threads) {
+               grep_file_async(opt, name, filename);
+               return 0;
+       } else
+#endif
+       {
+               int hit;
+               size_t sz;
+               void *data = load_file(filename, &sz);
+               if (!data)
+                       hit = 0;
                else
-                       opt->show_hunk_mark = 1;
-       }
-
-       status = exec_grep(argc, argv);
+                       hit = grep_buffer(opt, name, data, sz);
 
-       if (kept_0) {
-               /*
-                * Then recover them.  Now the last arg is beyond the
-                * terminating NULL which is at argc, and the second
-                * from the last is what we saved away in kept_0
-                */
-               argv[arg0++] = kept_0;
-               argv[arg0] = argv[argc+1];
-       }
-       return status;
-}
-
-static void grep_add_color(struct strbuf *sb, const char *escape_seq)
-{
-       size_t orig_len = sb->len;
-
-       while (*escape_seq) {
-               if (*escape_seq == 'm')
-                       strbuf_addch(sb, ';');
-               else if (*escape_seq != '\033' && *escape_seq  != '[')
-                       strbuf_addch(sb, *escape_seq);
-               escape_seq++;
-       }
-       if (sb->len > orig_len && sb->buf[sb->len - 1] == ';')
-               strbuf_setlen(sb, sb->len - 1);
-}
-
-static int external_grep(struct grep_opt *opt, const char **paths, int cached)
-{
-       int i, nr, argc, hit, len, status;
-       const char *argv[MAXARGS+1];
-       char randarg[ARGBUF];
-       char *argptr = randarg;
-       struct grep_pat *p;
-
-       if (opt->extended || (opt->relative && opt->prefix_length))
-               return -1;
-       len = nr = 0;
-       push_arg("grep");
-       if (opt->fixed)
-               push_arg("-F");
-       if (opt->linenum)
-               push_arg("-n");
-       if (!opt->pathname)
-               push_arg("-h");
-       if (opt->regflags & REG_EXTENDED)
-               push_arg("-E");
-       if (opt->ignore_case)
-               push_arg("-i");
-       if (opt->binary == GREP_BINARY_NOMATCH)
-               push_arg("-I");
-       if (opt->word_regexp)
-               push_arg("-w");
-       if (opt->name_only)
-               push_arg("-l");
-       if (opt->unmatch_name_only)
-               push_arg("-L");
-       if (opt->null_following_name)
-               /* in GNU grep git's "-z" translates to "-Z" */
-               push_arg("-Z");
-       if (opt->count)
-               push_arg("-c");
-       if (opt->post_context || opt->pre_context) {
-               if (opt->post_context != opt->pre_context) {
-                       if (opt->pre_context) {
-                               push_arg("-B");
-                               len += snprintf(argptr, sizeof(randarg)-len,
-                                               "%u", opt->pre_context) + 1;
-                               if (sizeof(randarg) <= len)
-                                       die("maximum length of args exceeded");
-                               push_arg(argptr);
-                               argptr += len;
-                       }
-                       if (opt->post_context) {
-                               push_arg("-A");
-                               len += snprintf(argptr, sizeof(randarg)-len,
-                                               "%u", opt->post_context) + 1;
-                               if (sizeof(randarg) <= len)
-                                       die("maximum length of args exceeded");
-                               push_arg(argptr);
-                               argptr += len;
-                       }
-               }
-               else {
-                       push_arg("-C");
-                       len += snprintf(argptr, sizeof(randarg)-len,
-                                       "%u", opt->post_context) + 1;
-                       if (sizeof(randarg) <= len)
-                               die("maximum length of args exceeded");
-                       push_arg(argptr);
-                       argptr += len;
-               }
-       }
-       for (p = opt->pattern_list; p; p = p->next) {
-               push_arg("-e");
-               push_arg(p->pattern);
-       }
-       if (opt->color) {
-               struct strbuf sb = STRBUF_INIT;
-
-               grep_add_color(&sb, opt->color_match);
-               setenv("GREP_COLOR", sb.buf, 1);
-
-               strbuf_reset(&sb);
-               strbuf_addstr(&sb, "mt=");
-               grep_add_color(&sb, opt->color_match);
-               strbuf_addstr(&sb, ":sl=:cx=:fn=:ln=:bn=:se=");
-               setenv("GREP_COLORS", sb.buf, 1);
-
-               strbuf_release(&sb);
-
-               if (opt->color_external && strlen(opt->color_external) > 0)
-                       push_arg(opt->color_external);
-       } else {
-               unsetenv("GREP_COLOR");
-               unsetenv("GREP_COLORS");
-       }
-       unsetenv("GREP_OPTIONS");
-
-       hit = 0;
-       argc = nr;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               char *name;
-               int kept;
-               if (!S_ISREG(ce->ce_mode))
-                       continue;
-               if (!pathspec_matches(paths, ce->name, opt->max_depth))
-                       continue;
-               name = ce->name;
-               if (name[0] == '-') {
-                       int len = ce_namelen(ce);
-                       name = xmalloc(len + 3);
-                       memcpy(name, "./", 2);
-                       memcpy(name + 2, ce->name, len + 1);
-               }
-               argv[argc++] = name;
-               if (MAXARGS <= argc) {
-                       status = flush_grep(opt, argc, nr, argv, &kept);
-                       if (0 < status)
-                               hit = 1;
-                       argc = nr + kept;
-               }
-               if (ce_stage(ce)) {
-                       do {
-                               i++;
-                       } while (i < active_nr &&
-                                !strcmp(ce->name, active_cache[i]->name));
-                       i--; /* compensate for loop control */
-               }
-       }
-       if (argc > nr) {
-               status = flush_grep(opt, argc, nr, argv, NULL);
-               if (0 < status)
-                       hit = 1;
+               free(data);
+               free(name);
+               return hit;
        }
-       return hit;
 }
-#endif
 
-static int grep_cache(struct grep_opt *opt, const char **paths, int cached,
-                     int external_grep_allowed)
+static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
 {
        int hit = 0;
        int nr;
        read_cache();
 
-#if !NO_EXTERNAL_GREP
-       /*
-        * Use the external "grep" command for the case where
-        * we grep through the checked-out files. It tends to
-        * be a lot more optimized
-        */
-       if (!cached && external_grep_allowed) {
-               hit = external_grep(opt, paths, cached);
-               if (hit >= 0)
-                       return hit;
-               hit = 0;
-       }
-#endif
-
        for (nr = 0; nr < active_nr; nr++) {
                struct cache_entry *ce = active_cache[nr];
                if (!S_ISREG(ce->ce_mode))
@@ -511,7 +540,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached,
                 * are identical, even if worktree file has been modified, so use
                 * cache version instead
                 */
-               if (cached || (ce->ce_flags & CE_VALID)) {
+               if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {
                        if (ce_stage(ce))
                                continue;
                        hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
@@ -525,6 +554,8 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached,
                                 !strcmp(ce->name, active_cache[nr]->name));
                        nr--; /* compensate for loop control */
                }
+               if (hit && opt->status_only)
+                       break;
        }
        free_grep_patterns(opt);
        return hit;
@@ -574,7 +605,10 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
                        void *data;
                        unsigned long size;
 
+                       read_sha1_lock();
                        data = read_sha1_file(entry.sha1, &type, &size);
+                       read_sha1_unlock();
+
                        if (!data)
                                die("unable to read tree (%s)",
                                    sha1_to_hex(entry.sha1));
@@ -582,6 +616,8 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
                        hit |= grep_tree(opt, paths, &sub, tree_name, down);
                        free(data);
                }
+               if (hit && opt->status_only)
+                       break;
        }
        strbuf_release(&pathbuf);
        return hit;
@@ -696,8 +732,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 {
        int hit = 0;
        int cached = 0;
-       int external_grep_allowed = 1;
        int seen_dashdash = 0;
+       int external_grep_allowed__ignored;
        struct grep_opt opt;
        struct object_array list = { 0, 0, NULL };
        const char **paths = NULL;
@@ -776,16 +812,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                { OPTION_CALLBACK, ')', NULL, &opt, NULL, "",
                  PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
                  close_callback },
+               OPT_BOOLEAN('q', "quiet", &opt.status_only,
+                           "indicate hit with exit status without output"),
                OPT_BOOLEAN(0, "all-match", &opt.all_match,
                        "show only matches from files that match all patterns"),
                OPT_GROUP(""),
-#if NO_EXTERNAL_GREP
-               OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed,
-                       "allow calling of grep(1) (ignored by this build)"),
-#else
-               OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed,
-                       "allow calling of grep(1) (default)"),
-#endif
+               OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored,
+                           "allow calling of grep(1) (ignored by this build)"),
                { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage",
                  PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
                OPT_END()
@@ -836,14 +869,23 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                argc--;
        }
 
-       if ((opt.color && !opt.color_external) || opt.funcname)
-               external_grep_allowed = 0;
        if (!opt.pattern_list)
                die("no pattern given.");
        if (!opt.fixed && opt.ignore_case)
                opt.regflags |= REG_ICASE;
        if ((opt.regflags != REG_NEWLINE) && opt.fixed)
                die("cannot mix --fixed-strings and regexp");
+
+#ifndef NO_PTHREADS
+       if (online_cpus() == 1 || !grep_threads_ok(&opt))
+               use_threads = 0;
+
+       if (use_threads)
+               start_threads(&opt);
+#else
+       use_threads = 0;
+#endif
+
        compile_grep_patterns(&opt);
 
        /* Check revs and then paths */
@@ -881,9 +923,14 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        }
 
        if (!list.nr) {
+               int hit;
                if (!cached)
                        setup_work_tree();
-               return !grep_cache(&opt, paths, cached, external_grep_allowed);
+
+               hit = grep_cache(&opt, paths, cached);
+               if (use_threads)
+                       hit |= wait_all();
+               return !hit;
        }
 
        if (cached)
@@ -892,9 +939,15 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        for (i = 0; i < list.nr; i++) {
                struct object *real_obj;
                real_obj = deref_tag(list.objects[i].item, NULL, 0);
-               if (grep_object(&opt, paths, real_obj, list.objects[i].name))
+               if (grep_object(&opt, paths, real_obj, list.objects[i].name)) {
                        hit = 1;
+                       if (opt.status_only)
+                               break;
+               }
        }
+
+       if (use_threads)
+               hit |= wait_all();
        free_grep_patterns(&opt);
        return !hit;
 }
diff --git a/builtin-hash-object.c b/builtin-hash-object.c
new file mode 100644 (file)
index 0000000..6a5f5b5
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ * Copyright (C) Junio C Hamano, 2005
+ */
+#include "cache.h"
+#include "blob.h"
+#include "quote.h"
+#include "parse-options.h"
+#include "exec_cmd.h"
+
+static void hash_fd(int fd, const char *type, int write_object, const char *path)
+{
+       struct stat st;
+       unsigned char sha1[20];
+       if (fstat(fd, &st) < 0 ||
+           index_fd(sha1, fd, &st, write_object, type_from_string(type), path))
+               die(write_object
+                   ? "Unable to add %s to database"
+                   : "Unable to hash %s", path);
+       printf("%s\n", sha1_to_hex(sha1));
+       maybe_flush_or_die(stdout, "hash to stdout");
+}
+
+static void hash_object(const char *path, const char *type, int write_object,
+                       const char *vpath)
+{
+       int fd;
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               die_errno("Cannot open '%s'", path);
+       hash_fd(fd, type, write_object, vpath);
+}
+
+static void hash_stdin_paths(const char *type, int write_objects)
+{
+       struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
+
+       while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+               if (buf.buf[0] == '"') {
+                       strbuf_reset(&nbuf);
+                       if (unquote_c_style(&nbuf, buf.buf, NULL))
+                               die("line is badly quoted");
+                       strbuf_swap(&buf, &nbuf);
+               }
+               hash_object(buf.buf, type, write_objects, buf.buf);
+       }
+       strbuf_release(&buf);
+       strbuf_release(&nbuf);
+}
+
+static const char * const hash_object_usage[] = {
+       "git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...",
+       "git hash-object  --stdin-paths < <list-of-paths>",
+       NULL
+};
+
+static const char *type;
+static int write_object;
+static int hashstdin;
+static int stdin_paths;
+static int no_filters;
+static const char *vpath;
+
+static const struct option hash_object_options[] = {
+       OPT_STRING('t', NULL, &type, "type", "object type"),
+       OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"),
+       OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"),
+       OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"),
+       OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"),
+       OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"),
+       OPT_END()
+};
+
+int cmd_hash_object(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       int prefix_length = -1;
+       const char *errstr = NULL;
+
+       type = blob_type;
+
+       argc = parse_options(argc, argv, NULL, hash_object_options,
+                            hash_object_usage, 0);
+
+       if (write_object) {
+               prefix = setup_git_directory();
+               prefix_length = prefix ? strlen(prefix) : 0;
+               if (vpath && prefix)
+                       vpath = prefix_filename(prefix, prefix_length, vpath);
+       }
+
+       git_config(git_default_config, NULL);
+
+       if (stdin_paths) {
+               if (hashstdin)
+                       errstr = "Can't use --stdin-paths with --stdin";
+               else if (argc)
+                       errstr = "Can't specify files with --stdin-paths";
+               else if (vpath)
+                       errstr = "Can't use --stdin-paths with --path";
+               else if (no_filters)
+                       errstr = "Can't use --stdin-paths with --no-filters";
+       }
+       else {
+               if (hashstdin > 1)
+                       errstr = "Multiple --stdin arguments are not supported";
+               if (vpath && no_filters)
+                       errstr = "Can't use --path with --no-filters";
+       }
+
+       if (errstr) {
+               error("%s", errstr);
+               usage_with_options(hash_object_usage, hash_object_options);
+       }
+
+       if (hashstdin)
+               hash_fd(0, type, write_object, vpath);
+
+       for (i = 0 ; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (0 <= prefix_length)
+                       arg = prefix_filename(prefix, prefix_length, arg);
+               hash_object(arg, type, write_object,
+                           no_filters ? NULL : vpath ? vpath : arg);
+       }
+
+       if (stdin_paths)
+               hash_stdin_paths(type, write_object);
+
+       return 0;
+}
index 09ad4b04f9fa860a32580717e61acc942de23388..3182a2bec466c50a9a1db1e91888bad3335414b1 100644 (file)
@@ -23,13 +23,14 @@ static struct man_viewer_info_list {
 } *man_viewer_info_list;
 
 enum help_format {
+       HELP_FORMAT_NONE,
        HELP_FORMAT_MAN,
        HELP_FORMAT_INFO,
        HELP_FORMAT_WEB,
 };
 
 static int show_all = 0;
-static enum help_format help_format = HELP_FORMAT_MAN;
+static enum help_format help_format = HELP_FORMAT_NONE;
 static struct option builtin_help_options[] = {
        OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
        OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
@@ -415,10 +416,12 @@ int cmd_help(int argc, const char **argv, const char *prefix)
 {
        int nongit;
        const char *alias;
+       enum help_format parsed_help_format;
        load_command_list("git-", &main_cmds, &other_cmds);
 
        argc = parse_options(argc, argv, prefix, builtin_help_options,
                        builtin_help_usage, 0);
+       parsed_help_format = help_format;
 
        if (show_all) {
                printf("usage: %s\n\n", git_usage_string);
@@ -437,6 +440,9 @@ int cmd_help(int argc, const char **argv, const char *prefix)
        setup_git_directory_gently(&nongit);
        git_config(git_help_config, NULL);
 
+       if (parsed_help_format != HELP_FORMAT_NONE)
+               help_format = parsed_help_format;
+
        alias = alias_lookup(argv[0]);
        if (alias && !is_git_command(argv[0])) {
                printf("`git %s' is aliased to `%s'\n", argv[0], alias);
@@ -444,6 +450,7 @@ int cmd_help(int argc, const char **argv, const char *prefix)
        }
 
        switch (help_format) {
+       case HELP_FORMAT_NONE:
        case HELP_FORMAT_MAN:
                show_man_page(argv[0]);
                break;
diff --git a/builtin-index-pack.c b/builtin-index-pack.c
new file mode 100644 (file)
index 0000000..b4cf8c5
--- /dev/null
@@ -0,0 +1,1045 @@
+#include "cache.h"
+#include "delta.h"
+#include "pack.h"
+#include "csum-file.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree.h"
+#include "progress.h"
+#include "fsck.h"
+#include "exec_cmd.h"
+
+static const char index_pack_usage[] =
+"git index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
+
+struct object_entry
+{
+       struct pack_idx_entry idx;
+       unsigned long size;
+       unsigned int hdr_size;
+       enum object_type type;
+       enum object_type real_type;
+};
+
+union delta_base {
+       unsigned char sha1[20];
+       off_t offset;
+};
+
+struct base_data {
+       struct base_data *base;
+       struct base_data *child;
+       struct object_entry *obj;
+       void *data;
+       unsigned long size;
+};
+
+/*
+ * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
+ * to memcmp() only the first 20 bytes.
+ */
+#define UNION_BASE_SZ  20
+
+#define FLAG_LINK (1u<<20)
+#define FLAG_CHECKED (1u<<21)
+
+struct delta_entry
+{
+       union delta_base base;
+       int obj_no;
+};
+
+static struct object_entry *objects;
+static struct delta_entry *deltas;
+static struct base_data *base_cache;
+static size_t base_cache_used;
+static int nr_objects;
+static int nr_deltas;
+static int nr_resolved_deltas;
+
+static int from_stdin;
+static int strict;
+static int verbose;
+
+static struct progress *progress;
+
+/* We always read in 4kB chunks. */
+static unsigned char input_buffer[4096];
+static unsigned int input_offset, input_len;
+static off_t consumed_bytes;
+static git_SHA_CTX input_ctx;
+static uint32_t input_crc32;
+static int input_fd, output_fd, pack_fd;
+
+static int mark_link(struct object *obj, int type, void *data)
+{
+       if (!obj)
+               return -1;
+
+       if (type != OBJ_ANY && obj->type != type)
+               die("object type mismatch at %s", sha1_to_hex(obj->sha1));
+
+       obj->flags |= FLAG_LINK;
+       return 0;
+}
+
+/* The content of each linked object must have been checked
+   or it must be already present in the object database */
+static void check_object(struct object *obj)
+{
+       if (!obj)
+               return;
+
+       if (!(obj->flags & FLAG_LINK))
+               return;
+
+       if (!(obj->flags & FLAG_CHECKED)) {
+               unsigned long size;
+               int type = sha1_object_info(obj->sha1, &size);
+               if (type != obj->type || type <= 0)
+                       die("object of unexpected type");
+               obj->flags |= FLAG_CHECKED;
+               return;
+       }
+}
+
+static void check_objects(void)
+{
+       unsigned i, max;
+
+       max = get_max_object_index();
+       for (i = 0; i < max; i++)
+               check_object(get_indexed_object(i));
+}
+
+
+/* Discard current buffer used content. */
+static void flush(void)
+{
+       if (input_offset) {
+               if (output_fd >= 0)
+                       write_or_die(output_fd, input_buffer, input_offset);
+               git_SHA1_Update(&input_ctx, input_buffer, input_offset);
+               memmove(input_buffer, input_buffer + input_offset, input_len);
+               input_offset = 0;
+       }
+}
+
+/*
+ * Make sure at least "min" bytes are available in the buffer, and
+ * return the pointer to the buffer.
+ */
+static void *fill(int min)
+{
+       if (min <= input_len)
+               return input_buffer + input_offset;
+       if (min > sizeof(input_buffer))
+               die("cannot fill %d bytes", min);
+       flush();
+       do {
+               ssize_t ret = xread(input_fd, input_buffer + input_len,
+                               sizeof(input_buffer) - input_len);
+               if (ret <= 0) {
+                       if (!ret)
+                               die("early EOF");
+                       die_errno("read error on input");
+               }
+               input_len += ret;
+               if (from_stdin)
+                       display_throughput(progress, consumed_bytes + input_len);
+       } while (input_len < min);
+       return input_buffer;
+}
+
+static void use(int bytes)
+{
+       if (bytes > input_len)
+               die("used more bytes than were available");
+       input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes);
+       input_len -= bytes;
+       input_offset += bytes;
+
+       /* make sure off_t is sufficiently large not to wrap */
+       if (consumed_bytes > consumed_bytes + bytes)
+               die("pack too large for current definition of off_t");
+       consumed_bytes += bytes;
+}
+
+static const char *open_pack_file(const char *pack_name)
+{
+       if (from_stdin) {
+               input_fd = 0;
+               if (!pack_name) {
+                       static char tmpfile[PATH_MAX];
+                       output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+                                               "pack/tmp_pack_XXXXXX");
+                       pack_name = xstrdup(tmpfile);
+               } else
+                       output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
+               if (output_fd < 0)
+                       die_errno("unable to create '%s'", pack_name);
+               pack_fd = output_fd;
+       } else {
+               input_fd = open(pack_name, O_RDONLY);
+               if (input_fd < 0)
+                       die_errno("cannot open packfile '%s'", pack_name);
+               output_fd = -1;
+               pack_fd = input_fd;
+       }
+       git_SHA1_Init(&input_ctx);
+       return pack_name;
+}
+
+static void parse_pack_header(void)
+{
+       struct pack_header *hdr = fill(sizeof(struct pack_header));
+
+       /* Header consistency check */
+       if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+               die("pack signature mismatch");
+       if (!pack_version_ok(hdr->hdr_version))
+               die("pack version %"PRIu32" unsupported",
+                       ntohl(hdr->hdr_version));
+
+       nr_objects = ntohl(hdr->hdr_entries);
+       use(sizeof(struct pack_header));
+}
+
+static NORETURN void bad_object(unsigned long offset, const char *format,
+                      ...) __attribute__((format (printf, 2, 3)));
+
+static void bad_object(unsigned long offset, const char *format, ...)
+{
+       va_list params;
+       char buf[1024];
+
+       va_start(params, format);
+       vsnprintf(buf, sizeof(buf), format, params);
+       va_end(params);
+       die("pack has bad object at offset %lu: %s", offset, buf);
+}
+
+static void free_base_data(struct base_data *c)
+{
+       if (c->data) {
+               free(c->data);
+               c->data = NULL;
+               base_cache_used -= c->size;
+       }
+}
+
+static void prune_base_data(struct base_data *retain)
+{
+       struct base_data *b;
+       for (b = base_cache;
+            base_cache_used > delta_base_cache_limit && b;
+            b = b->child) {
+               if (b->data && b != retain)
+                       free_base_data(b);
+       }
+}
+
+static void link_base_data(struct base_data *base, struct base_data *c)
+{
+       if (base)
+               base->child = c;
+       else
+               base_cache = c;
+
+       c->base = base;
+       c->child = NULL;
+       if (c->data)
+               base_cache_used += c->size;
+       prune_base_data(c);
+}
+
+static void unlink_base_data(struct base_data *c)
+{
+       struct base_data *base = c->base;
+       if (base)
+               base->child = NULL;
+       else
+               base_cache = NULL;
+       free_base_data(c);
+}
+
+static void *unpack_entry_data(unsigned long offset, unsigned long size)
+{
+       z_stream stream;
+       void *buf = xmalloc(size);
+
+       memset(&stream, 0, sizeof(stream));
+       stream.next_out = buf;
+       stream.avail_out = size;
+       stream.next_in = fill(1);
+       stream.avail_in = input_len;
+       git_inflate_init(&stream);
+
+       for (;;) {
+               int ret = git_inflate(&stream, 0);
+               use(input_len - stream.avail_in);
+               if (stream.total_out == size && ret == Z_STREAM_END)
+                       break;
+               if (ret != Z_OK)
+                       bad_object(offset, "inflate returned %d", ret);
+               stream.next_in = fill(1);
+               stream.avail_in = input_len;
+       }
+       git_inflate_end(&stream);
+       return buf;
+}
+
+static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
+{
+       unsigned char *p;
+       unsigned long size, c;
+       off_t base_offset;
+       unsigned shift;
+       void *data;
+
+       obj->idx.offset = consumed_bytes;
+       input_crc32 = crc32(0, Z_NULL, 0);
+
+       p = fill(1);
+       c = *p;
+       use(1);
+       obj->type = (c >> 4) & 7;
+       size = (c & 15);
+       shift = 4;
+       while (c & 0x80) {
+               p = fill(1);
+               c = *p;
+               use(1);
+               size += (c & 0x7f) << shift;
+               shift += 7;
+       }
+       obj->size = size;
+
+       switch (obj->type) {
+       case OBJ_REF_DELTA:
+               hashcpy(delta_base->sha1, fill(20));
+               use(20);
+               break;
+       case OBJ_OFS_DELTA:
+               memset(delta_base, 0, sizeof(*delta_base));
+               p = fill(1);
+               c = *p;
+               use(1);
+               base_offset = c & 127;
+               while (c & 128) {
+                       base_offset += 1;
+                       if (!base_offset || MSB(base_offset, 7))
+                               bad_object(obj->idx.offset, "offset value overflow for delta base object");
+                       p = fill(1);
+                       c = *p;
+                       use(1);
+                       base_offset = (base_offset << 7) + (c & 127);
+               }
+               delta_base->offset = obj->idx.offset - base_offset;
+               if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset)
+                       bad_object(obj->idx.offset, "delta base offset is out of bound");
+               break;
+       case OBJ_COMMIT:
+       case OBJ_TREE:
+       case OBJ_BLOB:
+       case OBJ_TAG:
+               break;
+       default:
+               bad_object(obj->idx.offset, "unknown object type %d", obj->type);
+       }
+       obj->hdr_size = consumed_bytes - obj->idx.offset;
+
+       data = unpack_entry_data(obj->idx.offset, obj->size);
+       obj->idx.crc32 = input_crc32;
+       return data;
+}
+
+static void *get_data_from_pack(struct object_entry *obj)
+{
+       off_t from = obj[0].idx.offset + obj[0].hdr_size;
+       unsigned long len = obj[1].idx.offset - from;
+       unsigned long rdy = 0;
+       unsigned char *src, *data;
+       z_stream stream;
+       int st;
+
+       src = xmalloc(len);
+       data = src;
+       do {
+               ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy);
+               if (n < 0)
+                       die_errno("cannot pread pack file");
+               if (!n)
+                       die("premature end of pack file, %lu bytes missing",
+                           len - rdy);
+               rdy += n;
+       } while (rdy < len);
+       data = xmalloc(obj->size);
+       memset(&stream, 0, sizeof(stream));
+       stream.next_out = data;
+       stream.avail_out = obj->size;
+       stream.next_in = src;
+       stream.avail_in = len;
+       git_inflate_init(&stream);
+       while ((st = git_inflate(&stream, Z_FINISH)) == Z_OK);
+       git_inflate_end(&stream);
+       if (st != Z_STREAM_END || stream.total_out != obj->size)
+               die("serious inflate inconsistency");
+       free(src);
+       return data;
+}
+
+static int find_delta(const union delta_base *base)
+{
+       int first = 0, last = nr_deltas;
+
+        while (first < last) {
+                int next = (first + last) / 2;
+                struct delta_entry *delta = &deltas[next];
+                int cmp;
+
+                cmp = memcmp(base, &delta->base, UNION_BASE_SZ);
+                if (!cmp)
+                        return next;
+                if (cmp < 0) {
+                        last = next;
+                        continue;
+                }
+                first = next+1;
+        }
+        return -first-1;
+}
+
+static void find_delta_children(const union delta_base *base,
+                               int *first_index, int *last_index)
+{
+       int first = find_delta(base);
+       int last = first;
+       int end = nr_deltas - 1;
+
+       if (first < 0) {
+               *first_index = 0;
+               *last_index = -1;
+               return;
+       }
+       while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
+               --first;
+       while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
+               ++last;
+       *first_index = first;
+       *last_index = last;
+}
+
+static void sha1_object(const void *data, unsigned long size,
+                       enum object_type type, unsigned char *sha1)
+{
+       hash_sha1_file(data, size, typename(type), sha1);
+       if (has_sha1_file(sha1)) {
+               void *has_data;
+               enum object_type has_type;
+               unsigned long has_size;
+               has_data = read_sha1_file(sha1, &has_type, &has_size);
+               if (!has_data)
+                       die("cannot read existing object %s", sha1_to_hex(sha1));
+               if (size != has_size || type != has_type ||
+                   memcmp(data, has_data, size) != 0)
+                       die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
+               free(has_data);
+       }
+       if (strict) {
+               if (type == OBJ_BLOB) {
+                       struct blob *blob = lookup_blob(sha1);
+                       if (blob)
+                               blob->object.flags |= FLAG_CHECKED;
+                       else
+                               die("invalid blob object %s", sha1_to_hex(sha1));
+               } else {
+                       struct object *obj;
+                       int eaten;
+                       void *buf = (void *) data;
+
+                       /*
+                        * we do not need to free the memory here, as the
+                        * buf is deleted by the caller.
+                        */
+                       obj = parse_object_buffer(sha1, type, size, buf, &eaten);
+                       if (!obj)
+                               die("invalid %s", typename(type));
+                       if (fsck_object(obj, 1, fsck_error_function))
+                               die("Error in object");
+                       if (fsck_walk(obj, mark_link, NULL))
+                               die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1));
+
+                       if (obj->type == OBJ_TREE) {
+                               struct tree *item = (struct tree *) obj;
+                               item->buffer = NULL;
+                       }
+                       if (obj->type == OBJ_COMMIT) {
+                               struct commit *commit = (struct commit *) obj;
+                               commit->buffer = NULL;
+                       }
+                       obj->flags |= FLAG_CHECKED;
+               }
+       }
+}
+
+static void *get_base_data(struct base_data *c)
+{
+       if (!c->data) {
+               struct object_entry *obj = c->obj;
+
+               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+                       void *base = get_base_data(c->base);
+                       void *raw = get_data_from_pack(obj);
+                       c->data = patch_delta(
+                               base, c->base->size,
+                               raw, obj->size,
+                               &c->size);
+                       free(raw);
+                       if (!c->data)
+                               bad_object(obj->idx.offset, "failed to apply delta");
+               } else {
+                       c->data = get_data_from_pack(obj);
+                       c->size = obj->size;
+               }
+
+               base_cache_used += c->size;
+               prune_base_data(c);
+       }
+       return c->data;
+}
+
+static void resolve_delta(struct object_entry *delta_obj,
+                         struct base_data *base, struct base_data *result)
+{
+       void *base_data, *delta_data;
+
+       delta_obj->real_type = base->obj->real_type;
+       delta_data = get_data_from_pack(delta_obj);
+       base_data = get_base_data(base);
+       result->obj = delta_obj;
+       result->data = patch_delta(base_data, base->size,
+                                  delta_data, delta_obj->size, &result->size);
+       free(delta_data);
+       if (!result->data)
+               bad_object(delta_obj->idx.offset, "failed to apply delta");
+       sha1_object(result->data, result->size, delta_obj->real_type,
+                   delta_obj->idx.sha1);
+       nr_resolved_deltas++;
+}
+
+static void find_unresolved_deltas(struct base_data *base,
+                                  struct base_data *prev_base)
+{
+       int i, ref_first, ref_last, ofs_first, ofs_last;
+
+       /*
+        * This is a recursive function. Those brackets should help reducing
+        * stack usage by limiting the scope of the delta_base union.
+        */
+       {
+               union delta_base base_spec;
+
+               hashcpy(base_spec.sha1, base->obj->idx.sha1);
+               find_delta_children(&base_spec, &ref_first, &ref_last);
+
+               memset(&base_spec, 0, sizeof(base_spec));
+               base_spec.offset = base->obj->idx.offset;
+               find_delta_children(&base_spec, &ofs_first, &ofs_last);
+       }
+
+       if (ref_last == -1 && ofs_last == -1) {
+               free(base->data);
+               return;
+       }
+
+       link_base_data(prev_base, base);
+
+       for (i = ref_first; i <= ref_last; i++) {
+               struct object_entry *child = objects + deltas[i].obj_no;
+               if (child->real_type == OBJ_REF_DELTA) {
+                       struct base_data result;
+                       resolve_delta(child, base, &result);
+                       if (i == ref_last && ofs_last == -1)
+                               free_base_data(base);
+                       find_unresolved_deltas(&result, base);
+               }
+       }
+
+       for (i = ofs_first; i <= ofs_last; i++) {
+               struct object_entry *child = objects + deltas[i].obj_no;
+               if (child->real_type == OBJ_OFS_DELTA) {
+                       struct base_data result;
+                       resolve_delta(child, base, &result);
+                       if (i == ofs_last)
+                               free_base_data(base);
+                       find_unresolved_deltas(&result, base);
+               }
+       }
+
+       unlink_base_data(base);
+}
+
+static int compare_delta_entry(const void *a, const void *b)
+{
+       const struct delta_entry *delta_a = a;
+       const struct delta_entry *delta_b = b;
+       return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ);
+}
+
+/* Parse all objects and return the pack content SHA1 hash */
+static void parse_pack_objects(unsigned char *sha1)
+{
+       int i;
+       struct delta_entry *delta = deltas;
+       struct stat st;
+
+       /*
+        * First pass:
+        * - find locations of all objects;
+        * - calculate SHA1 of all non-delta objects;
+        * - remember base (SHA1 or offset) for all deltas.
+        */
+       if (verbose)
+               progress = start_progress(
+                               from_stdin ? "Receiving objects" : "Indexing objects",
+                               nr_objects);
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+               void *data = unpack_raw_entry(obj, &delta->base);
+               obj->real_type = obj->type;
+               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+                       nr_deltas++;
+                       delta->obj_no = i;
+                       delta++;
+               } else
+                       sha1_object(data, obj->size, obj->type, obj->idx.sha1);
+               free(data);
+               display_progress(progress, i+1);
+       }
+       objects[i].idx.offset = consumed_bytes;
+       stop_progress(&progress);
+
+       /* Check pack integrity */
+       flush();
+       git_SHA1_Final(sha1, &input_ctx);
+       if (hashcmp(fill(20), sha1))
+               die("pack is corrupted (SHA1 mismatch)");
+       use(20);
+
+       /* If input_fd is a file, we should have reached its end now. */
+       if (fstat(input_fd, &st))
+               die_errno("cannot fstat packfile");
+       if (S_ISREG(st.st_mode) &&
+                       lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
+               die("pack has junk at the end");
+
+       if (!nr_deltas)
+               return;
+
+       /* Sort deltas by base SHA1/offset for fast searching */
+       qsort(deltas, nr_deltas, sizeof(struct delta_entry),
+             compare_delta_entry);
+
+       /*
+        * Second pass:
+        * - for all non-delta objects, look if it is used as a base for
+        *   deltas;
+        * - if used as a base, uncompress the object and apply all deltas,
+        *   recursively checking if the resulting object is used as a base
+        *   for some more deltas.
+        */
+       if (verbose)
+               progress = start_progress("Resolving deltas", nr_deltas);
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+               struct base_data base_obj;
+
+               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
+                       continue;
+               base_obj.obj = obj;
+               base_obj.data = NULL;
+               find_unresolved_deltas(&base_obj, NULL);
+               display_progress(progress, nr_resolved_deltas);
+       }
+}
+
+static int write_compressed(struct sha1file *f, void *in, unsigned int size)
+{
+       z_stream stream;
+       unsigned long maxsize;
+       void *out;
+
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, zlib_compression_level);
+       maxsize = deflateBound(&stream, size);
+       out = xmalloc(maxsize);
+
+       /* Compress it */
+       stream.next_in = in;
+       stream.avail_in = size;
+       stream.next_out = out;
+       stream.avail_out = maxsize;
+       while (deflate(&stream, Z_FINISH) == Z_OK);
+       deflateEnd(&stream);
+
+       size = stream.total_out;
+       sha1write(f, out, size);
+       free(out);
+       return size;
+}
+
+static struct object_entry *append_obj_to_pack(struct sha1file *f,
+                              const unsigned char *sha1, void *buf,
+                              unsigned long size, enum object_type type)
+{
+       struct object_entry *obj = &objects[nr_objects++];
+       unsigned char header[10];
+       unsigned long s = size;
+       int n = 0;
+       unsigned char c = (type << 4) | (s & 15);
+       s >>= 4;
+       while (s) {
+               header[n++] = c | 0x80;
+               c = s & 0x7f;
+               s >>= 7;
+       }
+       header[n++] = c;
+       crc32_begin(f);
+       sha1write(f, header, n);
+       obj[0].size = size;
+       obj[0].hdr_size = n;
+       obj[0].type = type;
+       obj[0].real_type = type;
+       obj[1].idx.offset = obj[0].idx.offset + n;
+       obj[1].idx.offset += write_compressed(f, buf, size);
+       obj[0].idx.crc32 = crc32_end(f);
+       sha1flush(f);
+       hashcpy(obj->idx.sha1, sha1);
+       return obj;
+}
+
+static int delta_pos_compare(const void *_a, const void *_b)
+{
+       struct delta_entry *a = *(struct delta_entry **)_a;
+       struct delta_entry *b = *(struct delta_entry **)_b;
+       return a->obj_no - b->obj_no;
+}
+
+static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
+{
+       struct delta_entry **sorted_by_pos;
+       int i, n = 0;
+
+       /*
+        * Since many unresolved deltas may well be themselves base objects
+        * for more unresolved deltas, we really want to include the
+        * smallest number of base objects that would cover as much delta
+        * as possible by picking the
+        * trunc deltas first, allowing for other deltas to resolve without
+        * additional base objects.  Since most base objects are to be found
+        * before deltas depending on them, a good heuristic is to start
+        * resolving deltas in the same order as their position in the pack.
+        */
+       sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
+       for (i = 0; i < nr_deltas; i++) {
+               if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
+                       continue;
+               sorted_by_pos[n++] = &deltas[i];
+       }
+       qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
+
+       for (i = 0; i < n; i++) {
+               struct delta_entry *d = sorted_by_pos[i];
+               enum object_type type;
+               struct base_data base_obj;
+
+               if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
+                       continue;
+               base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
+               if (!base_obj.data)
+                       continue;
+
+               if (check_sha1_signature(d->base.sha1, base_obj.data,
+                               base_obj.size, typename(type)))
+                       die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
+               base_obj.obj = append_obj_to_pack(f, d->base.sha1,
+                                       base_obj.data, base_obj.size, type);
+               find_unresolved_deltas(&base_obj, NULL);
+               display_progress(progress, nr_resolved_deltas);
+       }
+       free(sorted_by_pos);
+}
+
+static void final(const char *final_pack_name, const char *curr_pack_name,
+                 const char *final_index_name, const char *curr_index_name,
+                 const char *keep_name, const char *keep_msg,
+                 unsigned char *sha1)
+{
+       const char *report = "pack";
+       char name[PATH_MAX];
+       int err;
+
+       if (!from_stdin) {
+               close(input_fd);
+       } else {
+               fsync_or_die(output_fd, curr_pack_name);
+               err = close(output_fd);
+               if (err)
+                       die_errno("error while closing pack file");
+       }
+
+       if (keep_msg) {
+               int keep_fd, keep_msg_len = strlen(keep_msg);
+
+               if (!keep_name)
+                       keep_fd = odb_pack_keep(name, sizeof(name), sha1);
+               else
+                       keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+
+               if (keep_fd < 0) {
+                       if (errno != EEXIST)
+                               die_errno("cannot write keep file '%s'",
+                                         keep_name);
+               } else {
+                       if (keep_msg_len > 0) {
+                               write_or_die(keep_fd, keep_msg, keep_msg_len);
+                               write_or_die(keep_fd, "\n", 1);
+                       }
+                       if (close(keep_fd) != 0)
+                               die_errno("cannot close written keep file '%s'",
+                                   keep_name);
+                       report = "keep";
+               }
+       }
+
+       if (final_pack_name != curr_pack_name) {
+               if (!final_pack_name) {
+                       snprintf(name, sizeof(name), "%s/pack/pack-%s.pack",
+                                get_object_directory(), sha1_to_hex(sha1));
+                       final_pack_name = name;
+               }
+               if (move_temp_to_file(curr_pack_name, final_pack_name))
+                       die("cannot store pack file");
+       } else if (from_stdin)
+               chmod(final_pack_name, 0444);
+
+       if (final_index_name != curr_index_name) {
+               if (!final_index_name) {
+                       snprintf(name, sizeof(name), "%s/pack/pack-%s.idx",
+                                get_object_directory(), sha1_to_hex(sha1));
+                       final_index_name = name;
+               }
+               if (move_temp_to_file(curr_index_name, final_index_name))
+                       die("cannot store index file");
+       } else
+               chmod(final_index_name, 0444);
+
+       if (!from_stdin) {
+               printf("%s\n", sha1_to_hex(sha1));
+       } else {
+               char buf[48];
+               int len = snprintf(buf, sizeof(buf), "%s\t%s\n",
+                                  report, sha1_to_hex(sha1));
+               write_or_die(1, buf, len);
+
+               /*
+                * Let's just mimic git-unpack-objects here and write
+                * the last part of the input buffer to stdout.
+                */
+               while (input_len) {
+                       err = xwrite(1, input_buffer + input_offset, input_len);
+                       if (err <= 0)
+                               break;
+                       input_len -= err;
+                       input_offset += err;
+               }
+       }
+}
+
+static int git_index_pack_config(const char *k, const char *v, void *cb)
+{
+       if (!strcmp(k, "pack.indexversion")) {
+               pack_idx_default_version = git_config_int(k, v);
+               if (pack_idx_default_version > 2)
+                       die("bad pack.indexversion=%"PRIu32,
+                               pack_idx_default_version);
+               return 0;
+       }
+       return git_default_config(k, v, cb);
+}
+
+int cmd_index_pack(int argc, const char **argv, const char *prefix)
+{
+       int i, fix_thin_pack = 0;
+       const char *curr_pack, *curr_index;
+       const char *index_name = NULL, *pack_name = NULL;
+       const char *keep_name = NULL, *keep_msg = NULL;
+       char *index_name_buf = NULL, *keep_name_buf = NULL;
+       struct pack_idx_entry **idx_objects;
+       unsigned char pack_sha1[20];
+
+       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().
+        * However if the cwd was inside .git/objects/pack/ then we need
+        * to go back there or all the pack name arguments will be wrong.
+        * And in that case we cannot rely on any prefix returned by
+        * setup_git_directory_gently() either.
+        */
+       {
+               char cwd[PATH_MAX+1];
+               int nongit;
+
+               if (!getcwd(cwd, sizeof(cwd)-1))
+                       die("Unable to get current working directory");
+               setup_git_directory_gently(&nongit);
+               git_config(git_index_pack_config, NULL);
+               if (chdir(cwd))
+                       die("Cannot come back to cwd");
+       }
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strcmp(arg, "--stdin")) {
+                               from_stdin = 1;
+                       } else if (!strcmp(arg, "--fix-thin")) {
+                               fix_thin_pack = 1;
+                       } else if (!strcmp(arg, "--strict")) {
+                               strict = 1;
+                       } else if (!strcmp(arg, "--keep")) {
+                               keep_msg = "";
+                       } else if (!prefixcmp(arg, "--keep=")) {
+                               keep_msg = arg + 7;
+                       } else if (!prefixcmp(arg, "--pack_header=")) {
+                               struct pack_header *hdr;
+                               char *c;
+
+                               hdr = (struct pack_header *)input_buffer;
+                               hdr->hdr_signature = htonl(PACK_SIGNATURE);
+                               hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+                               if (*c != ',')
+                                       die("bad %s", arg);
+                               hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+                               if (*c)
+                                       die("bad %s", arg);
+                               input_len = sizeof(*hdr);
+                       } else if (!strcmp(arg, "-v")) {
+                               verbose = 1;
+                       } else if (!strcmp(arg, "-o")) {
+                               if (index_name || (i+1) >= argc)
+                                       usage(index_pack_usage);
+                               index_name = argv[++i];
+                       } else if (!prefixcmp(arg, "--index-version=")) {
+                               char *c;
+                               pack_idx_default_version = strtoul(arg + 16, &c, 10);
+                               if (pack_idx_default_version > 2)
+                                       die("bad %s", arg);
+                               if (*c == ',')
+                                       pack_idx_off32_limit = strtoul(c+1, &c, 0);
+                               if (*c || pack_idx_off32_limit & 0x80000000)
+                                       die("bad %s", arg);
+                       } else
+                               usage(index_pack_usage);
+                       continue;
+               }
+
+               if (pack_name)
+                       usage(index_pack_usage);
+               pack_name = arg;
+       }
+
+       if (!pack_name && !from_stdin)
+               usage(index_pack_usage);
+       if (fix_thin_pack && !from_stdin)
+               die("--fix-thin cannot be used without --stdin");
+       if (!index_name && pack_name) {
+               int len = strlen(pack_name);
+               if (!has_extension(pack_name, ".pack"))
+                       die("packfile name '%s' does not end with '.pack'",
+                           pack_name);
+               index_name_buf = xmalloc(len);
+               memcpy(index_name_buf, pack_name, len - 5);
+               strcpy(index_name_buf + len - 5, ".idx");
+               index_name = index_name_buf;
+       }
+       if (keep_msg && !keep_name && pack_name) {
+               int len = strlen(pack_name);
+               if (!has_extension(pack_name, ".pack"))
+                       die("packfile name '%s' does not end with '.pack'",
+                           pack_name);
+               keep_name_buf = xmalloc(len);
+               memcpy(keep_name_buf, pack_name, len - 5);
+               strcpy(keep_name_buf + len - 5, ".keep");
+               keep_name = keep_name_buf;
+       }
+
+       curr_pack = open_pack_file(pack_name);
+       parse_pack_header();
+       objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
+       deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
+       parse_pack_objects(pack_sha1);
+       if (nr_deltas == nr_resolved_deltas) {
+               stop_progress(&progress);
+               /* Flush remaining pack final 20-byte SHA1. */
+               flush();
+       } else {
+               if (fix_thin_pack) {
+                       struct sha1file *f;
+                       unsigned char read_sha1[20], tail_sha1[20];
+                       char msg[48];
+                       int nr_unresolved = nr_deltas - nr_resolved_deltas;
+                       int nr_objects_initial = nr_objects;
+                       if (nr_unresolved <= 0)
+                               die("confusion beyond insanity");
+                       objects = xrealloc(objects,
+                                          (nr_objects + nr_unresolved + 1)
+                                          * sizeof(*objects));
+                       f = sha1fd(output_fd, curr_pack);
+                       fix_unresolved_deltas(f, nr_unresolved);
+                       sprintf(msg, "completed with %d local objects",
+                               nr_objects - nr_objects_initial);
+                       stop_progress_msg(&progress, msg);
+                       sha1close(f, tail_sha1, 0);
+                       hashcpy(read_sha1, pack_sha1);
+                       fixup_pack_header_footer(output_fd, pack_sha1,
+                                                curr_pack, nr_objects,
+                                                read_sha1, consumed_bytes-20);
+                       if (hashcmp(read_sha1, tail_sha1) != 0)
+                               die("Unexpected tail checksum for %s "
+                                   "(disk corruption?)", curr_pack);
+               }
+               if (nr_deltas != nr_resolved_deltas)
+                       die("pack has %d unresolved deltas",
+                           nr_deltas - nr_resolved_deltas);
+       }
+       free(deltas);
+       if (strict)
+               check_objects();
+
+       idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
+       for (i = 0; i < nr_objects; i++)
+               idx_objects[i] = &objects[i].idx;
+       curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1);
+       free(idx_objects);
+
+       final(pack_name, curr_pack,
+               index_name, curr_index,
+               keep_name, keep_msg,
+               pack_sha1);
+       free(objects);
+       free(index_name_buf);
+       free(keep_name_buf);
+       if (pack_name == NULL)
+               free((void *) curr_pack);
+       if (index_name == NULL)
+               free((void *) curr_index);
+
+       return 0;
+}
index 2cb292fa727222487b5fed7f6f4db2bcc4237f49..8d16832f7e9483f7903009459a72efc39e267c98 100644 (file)
@@ -570,7 +570,7 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev)
 
        get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
 
-       if (!DIFF_OPT_TST(&rev->diffopt, QUIET))
+       if (!DIFF_OPT_TST(&rev->diffopt, QUICK))
                fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
 
        if (freopen(filename.buf, "w", stdout) == NULL)
index c9a03e5427bbf1390aeefb9d48b4e362ca6a8e1b..b065061392718e4250d2c52dc3d53a0c1c7938e9 100644 (file)
@@ -11,6 +11,8 @@
 #include "builtin.h"
 #include "tree.h"
 #include "parse-options.h"
+#include "resolve-undo.h"
+#include "string-list.h"
 
 static int abbrev;
 static int show_deleted;
@@ -18,6 +20,7 @@ static int show_cached;
 static int show_others;
 static int show_stage;
 static int show_unmerged;
+static int show_resolve_undo;
 static int show_modified;
 static int show_killed;
 static int show_valid_bit;
@@ -37,6 +40,8 @@ static const char *tag_removed = "";
 static const char *tag_other = "";
 static const char *tag_killed = "";
 static const char *tag_modified = "";
+static const char *tag_skip_worktree = "";
+static const char *tag_resolve_undo = "";
 
 static void show_dir_entry(const char *tag, struct dir_entry *ent)
 {
@@ -155,6 +160,38 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        write_name_quoted(ce->name + offset, stdout, line_terminator);
 }
 
+static int show_one_ru(struct string_list_item *item, void *cbdata)
+{
+       int offset = prefix_offset;
+       const char *path = item->string;
+       struct resolve_undo_info *ui = item->util;
+       int i, len;
+
+       len = strlen(path);
+       if (len < prefix_len)
+               return 0; /* outside of the prefix */
+       if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched))
+               return 0; /* uninterested */
+       for (i = 0; i < 3; i++) {
+               if (!ui->mode[i])
+                       continue;
+               printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
+                      abbrev
+                      ? find_unique_abbrev(ui->sha1[i], abbrev)
+                      : sha1_to_hex(ui->sha1[i]),
+                      i + 1);
+               write_name_quoted(path + offset, stdout, line_terminator);
+       }
+       return 0;
+}
+
+static void show_ru_info(const char *prefix)
+{
+       if (!the_index.resolve_undo)
+               return;
+       for_each_string_list(show_one_ru, the_index.resolve_undo, NULL);
+}
+
 static void show_files(struct dir_struct *dir, const char *prefix)
 {
        int i;
@@ -178,7 +215,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
                                continue;
                        if (ce->ce_flags & CE_UPDATE)
                                continue;
-                       show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
+                       show_ce_entry(ce_stage(ce) ? tag_unmerged :
+                               (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce);
                }
        }
        if (show_deleted | show_modified) {
@@ -192,6 +230,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
                                continue;
                        if (ce->ce_flags & CE_UPDATE)
                                continue;
+                       if (ce_skip_worktree(ce))
+                               continue;
                        err = lstat(ce->name, &st);
                        if (show_deleted && err)
                                show_ce_entry(tag_removed, ce);
@@ -454,6 +494,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                        DIR_HIDE_EMPTY_DIRECTORIES),
                OPT_BOOLEAN('u', "unmerged", &show_unmerged,
                        "show unmerged files in the output"),
+               OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
+                           "show resolve-undo information"),
                { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
                        "skip files matching pattern",
                        0, option_parse_exclude },
@@ -481,6 +523,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                prefix_offset = strlen(prefix);
        git_config(git_default_config, NULL);
 
+       if (read_cache() < 0)
+               die("index file corrupt");
+
        argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
                        ls_files_usage, 0);
        if (show_tag || show_valid_bit) {
@@ -490,6 +535,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                tag_modified = "C ";
                tag_other = "? ";
                tag_killed = "K ";
+               tag_skip_worktree = "S ";
+               tag_resolve_undo = "U ";
        }
        if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)
                require_work_tree = 1;
@@ -508,7 +555,6 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
        pathspec = get_pathspec(prefix, argv);
 
        /* be nice with submodule paths ending in a slash */
-       read_cache();
        if (pathspec)
                strip_trailing_slash_from_submodules();
 
@@ -529,7 +575,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 
        /* With no flags, we default to showing the cached files */
        if (!(show_stage | show_deleted | show_others | show_unmerged |
-             show_killed | show_modified))
+             show_killed | show_modified | show_resolve_undo))
                show_cached = 1;
 
        if (prefix)
@@ -544,6 +590,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                overlay_tree_on_cache(with_tree, prefix);
        }
        show_files(&dir, prefix);
+       if (show_resolve_undo)
+               show_ru_info(prefix);
 
        if (ps_matched) {
                int bad;
index b5bad0c184fc1ebc49759f211781ecd4031fd027..70f5622d9d49aae4080b38ee4487cc6e403a9d2c 100644 (file)
@@ -89,7 +89,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
        remote = remote_get(dest);
        if (!remote->url_nr)
                die("remote %s has no configured URL", dest);
-       transport = transport_get(remote, remote->url[0]);
+       transport = transport_get(remote, NULL);
        if (uploadpack != NULL)
                transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
 
index afd2ea7a732460c431328dbd4c0141a49d638244..1e70073a7ed022675031706a8e9f8c57ff3aa2a9 100644 (file)
@@ -25,15 +25,20 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
        const char *names[3] = { NULL, NULL, NULL };
        mmfile_t mmfs[3];
        mmbuffer_t result = {NULL, 0};
-       xpparam_t xpp = {XDF_NEED_MINIMAL};
+       xmparam_t xmp = {{XDF_NEED_MINIMAL}};
        int ret = 0, i = 0, to_stdout = 0;
-       int merge_level = XDL_MERGE_ZEALOUS_ALNUM;
-       int merge_style = 0, quiet = 0;
+       int level = XDL_MERGE_ZEALOUS_ALNUM;
+       int style = 0, quiet = 0;
+       int favor = 0;
        int nongit;
 
        struct option options[] = {
                OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"),
-               OPT_SET_INT(0, "diff3", &merge_style, "use a diff3 based merge", XDL_MERGE_DIFF3),
+               OPT_SET_INT(0, "diff3", &style, "use a diff3 based merge", XDL_MERGE_DIFF3),
+               OPT_SET_INT(0, "ours", &favor, "for conflicts, use our version",
+                           XDL_MERGE_FAVOR_OURS),
+               OPT_SET_INT(0, "theirs", &favor, "for conflicts, use their version",
+                           XDL_MERGE_FAVOR_THEIRS),
                OPT__QUIET(&quiet),
                OPT_CALLBACK('L', NULL, names, "name",
                             "set labels for file1/orig_file/file2", &label_cb),
@@ -45,7 +50,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
                /* Read the configuration file */
                git_config(git_xmerge_config, NULL);
                if (0 <= git_xmerge_style)
-                       merge_style = git_xmerge_style;
+                       style = git_xmerge_style;
        }
 
        argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0);
@@ -68,7 +73,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
        }
 
        ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
-                       &xpp, merge_level | merge_style, &result);
+                       &xmp, XDL_MERGE_FLAGS(level, style, favor), &result);
 
        for (i = 0; i < 3; i++)
                free(mmfs[i].ptr);
diff --git a/builtin-merge-index.c b/builtin-merge-index.c
new file mode 100644 (file)
index 0000000..2c4cf5e
--- /dev/null
@@ -0,0 +1,111 @@
+#include "cache.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+
+static const char *pgm;
+static int one_shot, quiet;
+static int err;
+
+static int merge_entry(int pos, const char *path)
+{
+       int found;
+       const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL };
+       char hexbuf[4][60];
+       char ownbuf[4][60];
+
+       if (pos >= active_nr)
+               die("git merge-index: %s not in the cache", path);
+       found = 0;
+       do {
+               struct cache_entry *ce = active_cache[pos];
+               int stage = ce_stage(ce);
+
+               if (strcmp(ce->name, path))
+                       break;
+               found++;
+               strcpy(hexbuf[stage], sha1_to_hex(ce->sha1));
+               sprintf(ownbuf[stage], "%o", ce->ce_mode);
+               arguments[stage] = hexbuf[stage];
+               arguments[stage + 4] = ownbuf[stage];
+       } while (++pos < active_nr);
+       if (!found)
+               die("git merge-index: %s not in the cache", path);
+
+       if (run_command_v_opt(arguments, 0)) {
+               if (one_shot)
+                       err++;
+               else {
+                       if (!quiet)
+                               die("merge program failed");
+                       exit(1);
+               }
+       }
+       return found;
+}
+
+static void merge_file(const char *path)
+{
+       int pos = cache_name_pos(path, strlen(path));
+
+       /*
+        * If it already exists in the cache as stage0, it's
+        * already merged and there is nothing to do.
+        */
+       if (pos < 0)
+               merge_entry(-pos-1, path);
+}
+
+static void merge_all(void)
+{
+       int i;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               i += merge_entry(i, ce->name)-1;
+       }
+}
+
+int cmd_merge_index(int argc, const char **argv, const char *prefix)
+{
+       int i, force_file = 0;
+
+       /* Without this we cannot rely on waitpid() to tell
+        * what happened to our children.
+        */
+       signal(SIGCHLD, SIG_DFL);
+
+       if (argc < 3)
+               usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
+
+       read_cache();
+
+       i = 1;
+       if (!strcmp(argv[i], "-o")) {
+               one_shot = 1;
+               i++;
+       }
+       if (!strcmp(argv[i], "-q")) {
+               quiet = 1;
+               i++;
+       }
+       pgm = argv[i++];
+       for (; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!force_file && *arg == '-') {
+                       if (!strcmp(arg, "--")) {
+                               force_file = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-a")) {
+                               merge_all();
+                               continue;
+                       }
+                       die("git merge-index: unknown option %s", arg);
+               }
+               merge_file(arg);
+       }
+       if (err && !quiet)
+               die("merge program failed");
+       return err;
+}
index 710674c6b2cccec4f1e0baa477a11d78cda72833..d8875d589240e0c78a9e241a7d2bdde1d10ab800 100644 (file)
@@ -25,19 +25,30 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
        struct commit *result;
 
        init_merge_options(&o);
-       if (argv[0]) {
-               int namelen = strlen(argv[0]);
-               if (8 < namelen &&
-                   !strcmp(argv[0] + namelen - 8, "-subtree"))
-                       o.subtree_merge = 1;
-       }
+       if (argv[0] && !suffixcmp(argv[0], "-subtree"))
+               o.subtree_shift = "";
 
        if (argc < 4)
                usagef("%s <base>... -- <head> <remote> ...", argv[0]);
 
        for (i = 1; i < argc; ++i) {
-               if (!strcmp(argv[i], "--"))
-                       break;
+               const char *arg = argv[i];
+
+               if (!prefixcmp(arg, "--")) {
+                       if (!arg[2])
+                               break;
+                       if (!strcmp(arg+2, "ours"))
+                               o.recursive_variant = MERGE_RECURSIVE_OURS;
+                       else if (!strcmp(arg+2, "theirs"))
+                               o.recursive_variant = MERGE_RECURSIVE_THEIRS;
+                       else if (!strcmp(arg+2, "subtree"))
+                               o.subtree_shift = "";
+                       else if (!prefixcmp(arg+2, "subtree="))
+                               o.subtree_shift = arg + 10;
+                       else
+                               die("Unknown option %s", arg);
+                       continue;
+               }
                if (bases_count < ARRAY_SIZE(bases)-1) {
                        unsigned char *sha = xmalloc(20);
                        if (get_sha1(argv[i], sha))
diff --git a/builtin-merge-tree.c b/builtin-merge-tree.c
new file mode 100644 (file)
index 0000000..a4a4f2c
--- /dev/null
@@ -0,0 +1,358 @@
+#include "cache.h"
+#include "tree-walk.h"
+#include "xdiff-interface.h"
+#include "blob.h"
+#include "exec_cmd.h"
+
+static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
+static int resolve_directories = 1;
+
+struct merge_list {
+       struct merge_list *next;
+       struct merge_list *link;        /* other stages for this object */
+
+       unsigned int stage : 2,
+                    flags : 30;
+       unsigned int mode;
+       const char *path;
+       struct blob *blob;
+};
+
+static struct merge_list *merge_result, **merge_result_end = &merge_result;
+
+static void add_merge_entry(struct merge_list *entry)
+{
+       *merge_result_end = entry;
+       merge_result_end = &entry->next;
+}
+
+static void merge_trees(struct tree_desc t[3], const char *base);
+
+static const char *explanation(struct merge_list *entry)
+{
+       switch (entry->stage) {
+       case 0:
+               return "merged";
+       case 3:
+               return "added in remote";
+       case 2:
+               if (entry->link)
+                       return "added in both";
+               return "added in local";
+       }
+
+       /* Existed in base */
+       entry = entry->link;
+       if (!entry)
+               return "removed in both";
+
+       if (entry->link)
+               return "changed in both";
+
+       if (entry->stage == 3)
+               return "removed in local";
+       return "removed in remote";
+}
+
+extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *);
+
+static void *result(struct merge_list *entry, unsigned long *size)
+{
+       enum object_type type;
+       struct blob *base, *our, *their;
+
+       if (!entry->stage)
+               return read_sha1_file(entry->blob->object.sha1, &type, size);
+       base = NULL;
+       if (entry->stage == 1) {
+               base = entry->blob;
+               entry = entry->link;
+       }
+       our = NULL;
+       if (entry && entry->stage == 2) {
+               our = entry->blob;
+               entry = entry->link;
+       }
+       their = NULL;
+       if (entry)
+               their = entry->blob;
+       return merge_file(entry->path, base, our, their, size);
+}
+
+static void *origin(struct merge_list *entry, unsigned long *size)
+{
+       enum object_type type;
+       while (entry) {
+               if (entry->stage == 2)
+                       return read_sha1_file(entry->blob->object.sha1, &type, size);
+               entry = entry->link;
+       }
+       return NULL;
+}
+
+static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+       int i;
+       for (i = 0; i < nbuf; i++)
+               printf("%.*s", (int) mb[i].size, mb[i].ptr);
+       return 0;
+}
+
+static void show_diff(struct merge_list *entry)
+{
+       unsigned long size;
+       mmfile_t src, dst;
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       xdemitcb_t ecb;
+
+       xpp.flags = XDF_NEED_MINIMAL;
+       memset(&xecfg, 0, sizeof(xecfg));
+       xecfg.ctxlen = 3;
+       ecb.outf = show_outf;
+       ecb.priv = NULL;
+
+       src.ptr = origin(entry, &size);
+       if (!src.ptr)
+               size = 0;
+       src.size = size;
+       dst.ptr = result(entry, &size);
+       if (!dst.ptr)
+               size = 0;
+       dst.size = size;
+       xdi_diff(&src, &dst, &xpp, &xecfg, &ecb);
+       free(src.ptr);
+       free(dst.ptr);
+}
+
+static void show_result_list(struct merge_list *entry)
+{
+       printf("%s\n", explanation(entry));
+       do {
+               struct merge_list *link = entry->link;
+               static const char *desc[4] = { "result", "base", "our", "their" };
+               printf("  %-6s %o %s %s\n", desc[entry->stage], entry->mode, sha1_to_hex(entry->blob->object.sha1), entry->path);
+               entry = link;
+       } while (entry);
+}
+
+static void show_result(void)
+{
+       struct merge_list *walk;
+
+       walk = merge_result;
+       while (walk) {
+               show_result_list(walk);
+               show_diff(walk);
+               walk = walk->next;
+       }
+}
+
+/* An empty entry never compares same, not even to another empty entry */
+static int same_entry(struct name_entry *a, struct name_entry *b)
+{
+       return  a->sha1 &&
+               b->sha1 &&
+               !hashcmp(a->sha1, b->sha1) &&
+               a->mode == b->mode;
+}
+
+static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path)
+{
+       struct merge_list *res = xcalloc(1, sizeof(*res));
+
+       res->stage = stage;
+       res->path = path;
+       res->mode = mode;
+       res->blob = lookup_blob(sha1);
+       return res;
+}
+
+static char *traverse_path(const struct traverse_info *info, const struct name_entry *n)
+{
+       char *path = xmalloc(traverse_path_len(info, n) + 1);
+       return make_traverse_path(path, info, n);
+}
+
+static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result)
+{
+       struct merge_list *orig, *final;
+       const char *path;
+
+       /* If it's already branch1, don't bother showing it */
+       if (!branch1)
+               return;
+
+       path = traverse_path(info, result);
+       orig = create_entry(2, branch1->mode, branch1->sha1, path);
+       final = create_entry(0, result->mode, result->sha1, path);
+
+       final->link = orig;
+
+       add_merge_entry(final);
+}
+
+static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3])
+{
+       char *newbase;
+       struct name_entry *p;
+       struct tree_desc t[3];
+       void *buf0, *buf1, *buf2;
+
+       if (!resolve_directories)
+               return 0;
+       p = n;
+       if (!p->mode) {
+               p++;
+               if (!p->mode)
+                       p++;
+       }
+       if (!S_ISDIR(p->mode))
+               return 0;
+       newbase = traverse_path(info, p);
+       buf0 = fill_tree_descriptor(t+0, n[0].sha1);
+       buf1 = fill_tree_descriptor(t+1, n[1].sha1);
+       buf2 = fill_tree_descriptor(t+2, n[2].sha1);
+       merge_trees(t, newbase);
+
+       free(buf0);
+       free(buf1);
+       free(buf2);
+       free(newbase);
+       return 1;
+}
+
+
+static struct merge_list *link_entry(unsigned stage, const struct traverse_info *info, struct name_entry *n, struct merge_list *entry)
+{
+       const char *path;
+       struct merge_list *link;
+
+       if (!n->mode)
+               return entry;
+       if (entry)
+               path = entry->path;
+       else
+               path = traverse_path(info, n);
+       link = create_entry(stage, n->mode, n->sha1, path);
+       link->link = entry;
+       return link;
+}
+
+static void unresolved(const struct traverse_info *info, struct name_entry n[3])
+{
+       struct merge_list *entry = NULL;
+
+       if (unresolved_directory(info, n))
+               return;
+
+       /*
+        * Do them in reverse order so that the resulting link
+        * list has the stages in order - link_entry adds new
+        * links at the front.
+        */
+       entry = link_entry(3, info, n + 2, entry);
+       entry = link_entry(2, info, n + 1, entry);
+       entry = link_entry(1, info, n + 0, entry);
+
+       add_merge_entry(entry);
+}
+
+/*
+ * Merge two trees together (t[1] and t[2]), using a common base (t[0])
+ * as the origin.
+ *
+ * This walks the (sorted) trees in lock-step, checking every possible
+ * name. Note that directories automatically sort differently from other
+ * files (see "base_name_compare"), so you'll never see file/directory
+ * conflicts, because they won't ever compare the same.
+ *
+ * IOW, if a directory changes to a filename, it will automatically be
+ * seen as the directory going away, and the filename being created.
+ *
+ * Think of this as a three-way diff.
+ *
+ * The output will be either:
+ *  - successful merge
+ *      "0 mode sha1 filename"
+ *    NOTE NOTE NOTE! FIXME! We really really need to walk the index
+ *    in parallel with this too!
+ *
+ *  - conflict:
+ *     "1 mode sha1 filename"
+ *     "2 mode sha1 filename"
+ *     "3 mode sha1 filename"
+ *    where not all of the 1/2/3 lines may exist, of course.
+ *
+ * The successful merge rules are the same as for the three-way merge
+ * in git-read-tree.
+ */
+static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info)
+{
+       /* Same in both? */
+       if (same_entry(entry+1, entry+2)) {
+               if (entry[0].sha1) {
+                       resolve(info, NULL, entry+1);
+                       return mask;
+               }
+       }
+
+       if (same_entry(entry+0, entry+1)) {
+               if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) {
+                       resolve(info, entry+1, entry+2);
+                       return mask;
+               }
+       }
+
+       if (same_entry(entry+0, entry+2)) {
+               if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) {
+                       resolve(info, NULL, entry+1);
+                       return mask;
+               }
+       }
+
+       unresolved(info, entry);
+       return mask;
+}
+
+static void merge_trees(struct tree_desc t[3], const char *base)
+{
+       struct traverse_info info;
+
+       setup_traverse_info(&info, base);
+       info.fn = threeway_callback;
+       traverse_trees(3, t, &info);
+}
+
+static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
+{
+       unsigned char sha1[20];
+       void *buf;
+
+       if (get_sha1(rev, sha1))
+               die("unknown rev %s", rev);
+       buf = fill_tree_descriptor(desc, sha1);
+       if (!buf)
+               die("%s is not a tree", rev);
+       return buf;
+}
+
+int cmd_merge_tree(int argc, const char **argv, const char *prefix)
+{
+       struct tree_desc t[3];
+       void *buf1, *buf2, *buf3;
+
+       if (argc != 4)
+               usage(merge_tree_usage);
+
+       buf1 = get_tree_descriptor(t+0, argv[1]);
+       buf2 = get_tree_descriptor(t+1, argv[2]);
+       buf3 = get_tree_descriptor(t+2, argv[3]);
+       merge_trees(t, "");
+       free(buf1);
+       free(buf2);
+       free(buf3);
+
+       show_result();
+       return 0;
+}
index f1c84d759dd44f661fb76741d528e43702dfc901..3aaec7bed76af9efdfe5647be0da64373db2011e 100644 (file)
@@ -24,6 +24,7 @@
 #include "rerere.h"
 #include "help.h"
 #include "merge-recursive.h"
+#include "resolve-undo.h"
 
 #define DEFAULT_TWOHEAD (1<<0)
 #define DEFAULT_OCTOPUS (1<<1)
@@ -50,8 +51,11 @@ static struct commit_list *remoteheads;
 static unsigned char head[20], stash[20];
 static struct strategy **use_strategies;
 static size_t use_strategies_nr, use_strategies_alloc;
+static const char **xopts;
+static size_t xopts_nr, xopts_alloc;
 static const char *branch;
 static int verbosity;
+static int allow_rerere_auto;
 
 static struct strategy all_strategy[] = {
        { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -146,6 +150,17 @@ static int option_parse_strategy(const struct option *opt,
        return 0;
 }
 
+static int option_parse_x(const struct option *opt,
+                         const char *arg, int unset)
+{
+       if (unset)
+               return 0;
+
+       ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
+       xopts[xopts_nr++] = xstrdup(arg);
+       return 0;
+}
+
 static int option_parse_n(const struct option *opt,
                          const char *arg, int unset)
 {
@@ -170,8 +185,11 @@ static struct option builtin_merge_options[] = {
                "allow fast-forward (default)"),
        OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
                "abort if fast-forward is not possible"),
+       OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
        OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
                "merge strategy to use", option_parse_strategy),
+       OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
+               "option for selected merge strategy", option_parse_x),
        OPT_CALLBACK('m', "message", &merge_msg, "message",
                "message to be used for the merge commit (if any)",
                option_parse_message),
@@ -534,7 +552,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                              const char *head_arg)
 {
        const char **args;
-       int i = 0, ret;
+       int i = 0, x = 0, ret;
        struct commit_list *j;
        struct strbuf buf = STRBUF_INIT;
        int index_fd;
@@ -563,7 +581,20 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
 
                init_merge_options(&o);
                if (!strcmp(strategy, "subtree"))
-                       o.subtree_merge = 1;
+                       o.subtree_shift = "";
+
+               for (x = 0; x < xopts_nr; x++) {
+                       if (!strcmp(xopts[x], "ours"))
+                               o.recursive_variant = MERGE_RECURSIVE_OURS;
+                       else if (!strcmp(xopts[x], "theirs"))
+                               o.recursive_variant = MERGE_RECURSIVE_THEIRS;
+                       else if (!strcmp(xopts[x], "subtree"))
+                               o.subtree_shift = "";
+                       else if (!prefixcmp(xopts[x], "subtree="))
+                               o.subtree_shift = xopts[x]+8;
+                       else
+                               die("Unknown option for merge-recursive: -X%s", xopts[x]);
+               }
 
                o.branch1 = head_arg;
                o.branch2 = remoteheads->item->util;
@@ -581,10 +612,16 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                rollback_lock_file(lock);
                return clean ? 0 : 1;
        } else {
-               args = xmalloc((4 + commit_list_count(common) +
+               args = xmalloc((4 + xopts_nr + commit_list_count(common) +
                                        commit_list_count(remoteheads)) * sizeof(char *));
                strbuf_addf(&buf, "merge-%s", strategy);
                args[i++] = buf.buf;
+               for (x = 0; x < xopts_nr; x++) {
+                       char *s = xmalloc(strlen(xopts[x])+2+1);
+                       strcpy(s, "--");
+                       strcpy(s+2, xopts[x]);
+                       args[i++] = s;
+               }
                for (j = common; j; j = j->next)
                        args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
                args[i++] = "--";
@@ -595,6 +632,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                ret = run_command_v_opt(args, RUN_GIT_CMD);
                strbuf_release(&buf);
                i = 1;
+               for (x = 0; x < xopts_nr; x++)
+                       free((void *)args[i++]);
                for (j = common; j; j = j->next)
                        free((void *)args[i++]);
                i += 2;
@@ -604,6 +643,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                discard_cache();
                if (read_cache() < 0)
                        die("failed to read the cache");
+               resolve_undo_clear();
                return ret;
        }
 }
@@ -618,11 +658,10 @@ static void count_diff_files(struct diff_queue_struct *q,
 
 static int count_unmerged_entries(void)
 {
-       const struct index_state *state = &the_index;
        int i, ret = 0;
 
-       for (i = 0; i < state->cache_nr; i++)
-               if (ce_stage(state->cache[i]))
+       for (i = 0; i < active_nr; i++)
+               if (ce_stage(active_cache[i]))
                        ret++;
 
        return ret;
@@ -790,7 +829,7 @@ static int suggest_conflicts(void)
                }
        }
        fclose(fp);
-       rerere();
+       rerere(allow_rerere_auto);
        printf("Automatic merge failed; "
                        "fix conflicts and then commit the result.\n");
        return 1;
@@ -847,12 +886,22 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        const char *best_strategy = NULL, *wt_strategy = NULL;
        struct commit_list **remotes = &remoteheads;
 
-       if (file_exists(git_path("MERGE_HEAD")))
-               die("You have not concluded your merge. (MERGE_HEAD exists)");
-       if (read_cache_unmerged())
-               die("You are in the middle of a conflicted merge."
-                               " (index unmerged)");
+       if (read_cache_unmerged()) {
+               die_resolve_conflict("merge");
+       }
+       if (file_exists(git_path("MERGE_HEAD"))) {
+               /*
+                * There is no unmerged entry, don't advise 'git
+                * add/rm <file>', just 'git commit'.
+                */
+               if (advice_resolve_conflict)
+                       die("You have not concluded your merge (MERGE_HEAD exists).\n"
+                           "Please, commit your changes before you can merge.");
+               else
+                       die("You have not concluded your merge (MERGE_HEAD exists).");
+       }
 
+       resolve_undo_clear();
        /*
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
diff --git a/builtin-mktag.c b/builtin-mktag.c
new file mode 100644 (file)
index 0000000..1cb0f3f
--- /dev/null
@@ -0,0 +1,179 @@
+#include "cache.h"
+#include "tag.h"
+#include "exec_cmd.h"
+
+/*
+ * A signature file has a very simple fixed format: four lines
+ * of "object <sha1>" + "type <typename>" + "tag <tagname>" +
+ * "tagger <committer>", followed by a blank line, a free-form tag
+ * message and a signature block that git itself doesn't care about,
+ * but that can be verified with gpg or similar.
+ *
+ * The first four lines are guaranteed to be at least 83 bytes:
+ * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
+ * shortest possible type-line, "tag .\n" at 6 bytes is the shortest
+ * single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is
+ * the shortest possible tagger-line.
+ */
+
+/*
+ * We refuse to tag something we can't verify. Just because.
+ */
+static int verify_object(const unsigned char *sha1, const char *expected_type)
+{
+       int ret = -1;
+       enum object_type type;
+       unsigned long size;
+       const unsigned char *repl;
+       void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl);
+
+       if (buffer) {
+               if (type == type_from_string(expected_type))
+                       ret = check_sha1_signature(repl, buffer, size, expected_type);
+               free(buffer);
+       }
+       return ret;
+}
+
+#ifdef NO_C99_FORMAT
+#define PD_FMT "%d"
+#else
+#define PD_FMT "%td"
+#endif
+
+static int verify_tag(char *buffer, unsigned long size)
+{
+       int typelen;
+       char type[20];
+       unsigned char sha1[20];
+       const char *object, *type_line, *tag_line, *tagger_line, *lb, *rb;
+       size_t len;
+
+       if (size < 84)
+               return error("wanna fool me ? you obviously got the size wrong !");
+
+       buffer[size] = 0;
+
+       /* Verify object line */
+       object = buffer;
+       if (memcmp(object, "object ", 7))
+               return error("char%d: does not start with \"object \"", 0);
+
+       if (get_sha1_hex(object + 7, sha1))
+               return error("char%d: could not get SHA1 hash", 7);
+
+       /* Verify type line */
+       type_line = object + 48;
+       if (memcmp(type_line - 1, "\ntype ", 6))
+               return error("char%d: could not find \"\\ntype \"", 47);
+
+       /* Verify tag-line */
+       tag_line = strchr(type_line, '\n');
+       if (!tag_line)
+               return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer);
+       tag_line++;
+       if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
+               return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer);
+
+       /* Get the actual type */
+       typelen = tag_line - type_line - strlen("type \n");
+       if (typelen >= sizeof(type))
+               return error("char" PD_FMT ": type too long", type_line+5 - buffer);
+
+       memcpy(type, type_line+5, typelen);
+       type[typelen] = 0;
+
+       /* Verify that the object matches */
+       if (verify_object(sha1, type))
+               return error("char%d: could not verify object %s", 7, sha1_to_hex(sha1));
+
+       /* Verify the tag-name: we don't allow control characters or spaces in it */
+       tag_line += 4;
+       for (;;) {
+               unsigned char c = *tag_line++;
+               if (c == '\n')
+                       break;
+               if (c > ' ')
+                       continue;
+               return error("char" PD_FMT ": could not verify tag name", tag_line - buffer);
+       }
+
+       /* Verify the tagger line */
+       tagger_line = tag_line;
+
+       if (memcmp(tagger_line, "tagger ", 7))
+               return error("char" PD_FMT ": could not find \"tagger \"",
+                       tagger_line - buffer);
+
+       /*
+        * Check for correct form for name and email
+        * i.e. " <" followed by "> " on _this_ line
+        * No angle brackets within the name or email address fields.
+        * No spaces within the email address field.
+        */
+       tagger_line += 7;
+       if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) ||
+               strpbrk(tagger_line, "<>\n") != lb+1 ||
+               strpbrk(lb+2, "><\n ") != rb)
+               return error("char" PD_FMT ": malformed tagger field",
+                       tagger_line - buffer);
+
+       /* Check for author name, at least one character, space is acceptable */
+       if (lb == tagger_line)
+               return error("char" PD_FMT ": missing tagger name",
+                       tagger_line - buffer);
+
+       /* timestamp, 1 or more digits followed by space */
+       tagger_line = rb + 2;
+       if (!(len = strspn(tagger_line, "0123456789")))
+               return error("char" PD_FMT ": missing tag timestamp",
+                       tagger_line - buffer);
+       tagger_line += len;
+       if (*tagger_line != ' ')
+               return error("char" PD_FMT ": malformed tag timestamp",
+                       tagger_line - buffer);
+       tagger_line++;
+
+       /* timezone, 5 digits [+-]hhmm, max. 1400 */
+       if (!((tagger_line[0] == '+' || tagger_line[0] == '-') &&
+             strspn(tagger_line+1, "0123456789") == 4 &&
+             tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400))
+               return error("char" PD_FMT ": malformed tag timezone",
+                       tagger_line - buffer);
+       tagger_line += 6;
+
+       /* Verify the blank line separating the header from the body */
+       if (*tagger_line != '\n')
+               return error("char" PD_FMT ": trailing garbage in tag header",
+                       tagger_line - buffer);
+
+       /* The actual stuff afterwards we don't care about.. */
+       return 0;
+}
+
+#undef PD_FMT
+
+int cmd_mktag(int argc, const char **argv, const char *prefix)
+{
+       struct strbuf buf = STRBUF_INIT;
+       unsigned char result_sha1[20];
+
+       if (argc != 1)
+               usage("git mktag < signaturefile");
+
+       if (strbuf_read(&buf, 0, 4096) < 0) {
+               die_errno("could not read from stdin");
+       }
+
+       /* Verify it for some basic sanity: it needs to start with
+          "object <sha1>\ntype\ntagger " */
+       if (verify_tag(buf.buf, buf.len) < 0)
+               die("invalid tag signature file");
+
+       if (write_sha1_file(buf.buf, buf.len, tag_type, result_sha1) < 0)
+               die("unable to write tag file");
+
+       strbuf_release(&buf);
+       printf("%s\n", sha1_to_hex(result_sha1));
+       return 0;
+}
index f633d81424f5e41cb85ac660dbdc9f4913852673..c07f53b34380200eaba223d2e1f8f977c1f9fd18 100644 (file)
@@ -24,10 +24,13 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
        result[count] = NULL;
        for (i = 0; i < count; i++) {
                int length = strlen(result[i]);
-               if (length > 0 && is_dir_sep(result[i][length - 1]))
-                       result[i] = xmemdupz(result[i], length - 1);
-               if (base_name)
-                       result[i] = basename((char *)result[i]);
+               int to_copy = length;
+               while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
+                       to_copy--;
+               if (to_copy != length || base_name) {
+                       char *it = xmemdupz(result[i], to_copy);
+                       result[i] = base_name ? strdup(basename(it)) : it;
+               }
        }
        return get_pathspec(prefix, result);
 }
@@ -169,9 +172,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                                 * check both source and destination
                                 */
                                if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
-                                       fprintf(stderr, "Warning: %s;"
-                                                       " will overwrite!\n",
-                                                       bad);
+                                       warning("%s; will overwrite!", bad);
                                        bad = NULL;
                                } else
                                        bad = "Cannot overwrite";
index 4429d53a1e82b81f0a82e34bf2d6ae463aeeadee..dcfe62aa02ca69cad39b4dd681a60f052b20aba5 100644 (file)
@@ -17,7 +17,7 @@
 #include "progress.h"
 #include "refs.h"
 
-#ifdef THREADED_DELTA_SEARCH
+#ifndef NO_PTHREADS
 #include "thread-utils.h"
 #include <pthread.h>
 #endif
@@ -77,7 +77,7 @@ static int allow_ofs_delta;
 static const char *base_name;
 static int progress = 1;
 static int window = 10;
-static uint32_t pack_size_limit, pack_size_limit_cfg;
+static unsigned long pack_size_limit, pack_size_limit_cfg;
 static int depth = 50;
 static int delta_search_threads;
 static int pack_to_stdout;
@@ -246,7 +246,7 @@ static unsigned long write_object(struct sha1file *f,
 
        type = entry->type;
 
-       /* write limit if limited packsize and not first object */
+       /* apply size limit if limited packsize and not first object */
        if (!pack_size_limit || !nr_written)
                limit = 0;
        else if (pack_size_limit <= write_offset)
@@ -443,11 +443,15 @@ static int write_one(struct sha1file *f,
 
        /* offset is non zero if object is written already. */
        if (e->idx.offset || e->preferred_base)
-               return 1;
+               return -1;
 
-       /* if we are deltified, write out base object first. */
-       if (e->delta && !write_one(f, e->delta, offset))
-               return 0;
+       /*
+        * If we are deltified, attempt to write out base object first.
+        * If that fails due to the pack size limit then the current
+        * object might still possibly fit undeltified within that limit.
+        */
+       if (e->delta)
+              write_one(f, e->delta, offset);
 
        e->idx.offset = *offset;
        size = write_object(f, e, *offset);
@@ -501,11 +505,9 @@ static void write_pack_file(void)
                sha1write(f, &hdr, sizeof(hdr));
                offset = sizeof(hdr);
                nr_written = 0;
-               for (; i < nr_objects; i++) {
-                       if (!write_one(f, objects + i, &offset))
-                               break;
-                       display_progress(progress_state, written);
-               }
+               for (i = 0; i < nr_objects; i++)
+                       if (write_one(f, objects + i, &offset) == 1)
+                               display_progress(progress_state, written);
 
                /*
                 * Did we write the wrong # entries in the header?
@@ -525,7 +527,8 @@ static void write_pack_file(void)
                if (!pack_to_stdout) {
                        mode_t mode = umask(0);
                        struct stat st;
-                       char *idx_tmp_name, tmpname[PATH_MAX];
+                       const char *idx_tmp_name;
+                       char tmpname[PATH_MAX];
 
                        umask(mode);
                        mode = 0444 & ~mode;
@@ -569,7 +572,7 @@ static void write_pack_file(void)
                        if (rename(idx_tmp_name, tmpname))
                                die_errno("unable to rename temporary index file");
 
-                       free(idx_tmp_name);
+                       free((void *) idx_tmp_name);
                        free(pack_tmp_name);
                        puts(sha1_to_hex(sha1));
                }
@@ -579,26 +582,13 @@ static void write_pack_file(void)
                        written_list[j]->offset = (off_t)-1;
                }
                nr_remaining -= nr_written;
-       } while (nr_remaining && i < nr_objects);
+       } while (nr_remaining);
 
        free(written_list);
        stop_progress(&progress_state);
        if (written != nr_result)
                die("wrote %"PRIu32" objects while expecting %"PRIu32,
                        written, nr_result);
-       /*
-        * We have scanned through [0 ... i).  Since we have written
-        * the correct number of objects,  the remaining [i ... nr_objects)
-        * items must be either already written (due to out-of-order delta base)
-        * or a preferred base.  Count those which are neither and complain if any.
-        */
-       for (j = 0; i < nr_objects; i++) {
-               struct object_entry *e = objects + i;
-               j += !e->idx.offset && !e->preferred_base;
-       }
-       if (j)
-               die("wrote %"PRIu32" objects as expected but %"PRIu32
-                       " unwritten", written, j);
 }
 
 static int locate_object_entry_hash(const unsigned char *sha1)
@@ -673,7 +663,7 @@ static void setup_delta_attr_check(struct git_attr_check *check)
        static struct git_attr *attr_delta;
 
        if (!attr_delta)
-               attr_delta = git_attr("delta", 5);
+               attr_delta = git_attr("delta");
 
        check[0].attr = attr_delta;
 }
@@ -1254,17 +1244,17 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size,
        return 0;
 }
 
-#ifdef THREADED_DELTA_SEARCH
+#ifndef NO_PTHREADS
 
-static pthread_mutex_t read_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t read_mutex;
 #define read_lock()            pthread_mutex_lock(&read_mutex)
 #define read_unlock()          pthread_mutex_unlock(&read_mutex)
 
-static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t cache_mutex;
 #define cache_lock()           pthread_mutex_lock(&cache_mutex)
 #define cache_unlock()         pthread_mutex_unlock(&cache_mutex)
 
-static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t progress_mutex;
 #define progress_lock()                pthread_mutex_lock(&progress_mutex)
 #define progress_unlock()      pthread_mutex_unlock(&progress_mutex)
 
@@ -1379,7 +1369,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
        /*
         * Handle memory allocation outside of the cache
         * accounting lock.  Compiler will optimize the strangeness
-        * away when THREADED_DELTA_SEARCH is not defined.
+        * away when NO_PTHREADS is defined.
         */
        free(trg_entry->delta_data);
        cache_lock();
@@ -1566,7 +1556,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
        free(array);
 }
 
-#ifdef THREADED_DELTA_SEARCH
+#ifndef NO_PTHREADS
 
 /*
  * The main thread waits on the condition that (at least) one of the workers
@@ -1591,7 +1581,26 @@ struct thread_params {
        unsigned *processed;
 };
 
-static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER;
+static pthread_cond_t progress_cond;
+
+/*
+ * Mutex and conditional variable can't be statically-initialized on Windows.
+ */
+static void init_threaded_search(void)
+{
+       pthread_mutex_init(&read_mutex, NULL);
+       pthread_mutex_init(&cache_mutex, NULL);
+       pthread_mutex_init(&progress_mutex, NULL);
+       pthread_cond_init(&progress_cond, NULL);
+}
+
+static void cleanup_threaded_search(void)
+{
+       pthread_cond_destroy(&progress_cond);
+       pthread_mutex_destroy(&read_mutex);
+       pthread_mutex_destroy(&cache_mutex);
+       pthread_mutex_destroy(&progress_mutex);
+}
 
 static void *threaded_find_deltas(void *arg)
 {
@@ -1630,10 +1639,13 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
        struct thread_params *p;
        int i, ret, active_threads = 0;
 
+       init_threaded_search();
+
        if (!delta_search_threads)      /* --threads=0 means autodetect */
                delta_search_threads = online_cpus();
        if (delta_search_threads <= 1) {
                find_deltas(list, &list_size, window, depth, processed);
+               cleanup_threaded_search();
                return;
        }
        if (progress > pack_to_stdout)
@@ -1748,6 +1760,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
                        active_threads--;
                }
        }
+       cleanup_threaded_search();
        free(p);
 }
 
@@ -1875,7 +1888,7 @@ static int git_pack_config(const char *k, const char *v, void *cb)
                if (delta_search_threads < 0)
                        die("invalid number of threads specified (%d)",
                            delta_search_threads);
-#ifndef THREADED_DELTA_SEARCH
+#ifdef NO_PTHREADS
                if (delta_search_threads != 1)
                        warning("no threads support, ignoring %s", k);
 #endif
@@ -2179,10 +2192,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        continue;
                }
                if (!prefixcmp(arg, "--max-pack-size=")) {
-                       char *end;
                        pack_size_limit_cfg = 0;
-                       pack_size_limit = strtoul(arg+16, &end, 0) * 1024 * 1024;
-                       if (!arg[16] || *end)
+                       if (!git_parse_ulong(arg+16, &pack_size_limit))
                                usage(pack_usage);
                        continue;
                }
@@ -2203,7 +2214,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        delta_search_threads = strtoul(arg+10, &end, 0);
                        if (!arg[10] || *end || delta_search_threads < 0)
                                usage(pack_usage);
-#ifndef THREADED_DELTA_SEARCH
+#ifdef NO_PTHREADS
                        if (delta_search_threads != 1)
                                warning("no threads support, "
                                        "ignoring %s", arg);
@@ -2322,9 +2333,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
 
        if (!pack_to_stdout && !pack_size_limit)
                pack_size_limit = pack_size_limit_cfg;
-
        if (pack_to_stdout && pack_size_limit)
                die("--max-pack-size cannot be used to build a pack for transfer.");
+       if (pack_size_limit && pack_size_limit < 1024*1024) {
+               warning("minimum pack size limit is 1 MiB");
+               pack_size_limit = 1024*1024;
+       }
 
        if (!pack_to_stdout && thin)
                die("--thin cannot be used to build an indexable pack.");
diff --git a/builtin-pack-redundant.c b/builtin-pack-redundant.c
new file mode 100644 (file)
index 0000000..41e1615
--- /dev/null
@@ -0,0 +1,696 @@
+/*
+*
+* Copyright 2005, Lukas Sandstrom <lukass@etek.chalmers.se>
+*
+* This file is licensed under the GPL v2.
+*
+*/
+
+#include "cache.h"
+#include "exec_cmd.h"
+
+#define BLKSIZE 512
+
+static const char pack_redundant_usage[] =
+"git pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
+
+static int load_all_packs, verbose, alt_odb;
+
+struct llist_item {
+       struct llist_item *next;
+       const unsigned char *sha1;
+};
+static struct llist {
+       struct llist_item *front;
+       struct llist_item *back;
+       size_t size;
+} *all_objects; /* all objects which must be present in local packfiles */
+
+static struct pack_list {
+       struct pack_list *next;
+       struct packed_git *pack;
+       struct llist *unique_objects;
+       struct llist *all_objects;
+} *local_packs = NULL, *altodb_packs = NULL;
+
+struct pll {
+       struct pll *next;
+       struct pack_list *pl;
+};
+
+static struct llist_item *free_nodes;
+
+static inline void llist_item_put(struct llist_item *item)
+{
+       item->next = free_nodes;
+       free_nodes = item;
+}
+
+static inline struct llist_item *llist_item_get(void)
+{
+       struct llist_item *new;
+       if ( free_nodes ) {
+               new = free_nodes;
+               free_nodes = free_nodes->next;
+       } else {
+               int i = 1;
+               new = xmalloc(sizeof(struct llist_item) * BLKSIZE);
+               for (; i < BLKSIZE; i++)
+                       llist_item_put(&new[i]);
+       }
+       return new;
+}
+
+static void llist_free(struct llist *list)
+{
+       while ((list->back = list->front)) {
+               list->front = list->front->next;
+               llist_item_put(list->back);
+       }
+       free(list);
+}
+
+static inline void llist_init(struct llist **list)
+{
+       *list = xmalloc(sizeof(struct llist));
+       (*list)->front = (*list)->back = NULL;
+       (*list)->size = 0;
+}
+
+static struct llist * llist_copy(struct llist *list)
+{
+       struct llist *ret;
+       struct llist_item *new, *old, *prev;
+
+       llist_init(&ret);
+
+       if ((ret->size = list->size) == 0)
+               return ret;
+
+       new = ret->front = llist_item_get();
+       new->sha1 = list->front->sha1;
+
+       old = list->front->next;
+       while (old) {
+               prev = new;
+               new = llist_item_get();
+               prev->next = new;
+               new->sha1 = old->sha1;
+               old = old->next;
+       }
+       new->next = NULL;
+       ret->back = new;
+
+       return ret;
+}
+
+static inline struct llist_item *llist_insert(struct llist *list,
+                                             struct llist_item *after,
+                                              const unsigned char *sha1)
+{
+       struct llist_item *new = llist_item_get();
+       new->sha1 = sha1;
+       new->next = NULL;
+
+       if (after != NULL) {
+               new->next = after->next;
+               after->next = new;
+               if (after == list->back)
+                       list->back = new;
+       } else {/* insert in front */
+               if (list->size == 0)
+                       list->back = new;
+               else
+                       new->next = list->front;
+               list->front = new;
+       }
+       list->size++;
+       return new;
+}
+
+static inline struct llist_item *llist_insert_back(struct llist *list,
+                                                  const unsigned char *sha1)
+{
+       return llist_insert(list, list->back, sha1);
+}
+
+static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
+                       const unsigned char *sha1, struct llist_item *hint)
+{
+       struct llist_item *prev = NULL, *l;
+
+       l = (hint == NULL) ? list->front : hint;
+       while (l) {
+               int cmp = hashcmp(l->sha1, sha1);
+               if (cmp > 0) { /* we insert before this entry */
+                       return llist_insert(list, prev, sha1);
+               }
+               if (!cmp) { /* already exists */
+                       return l;
+               }
+               prev = l;
+               l = l->next;
+       }
+       /* insert at the end */
+       return llist_insert_back(list, sha1);
+}
+
+/* returns a pointer to an item in front of sha1 */
+static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *sha1, struct llist_item *hint)
+{
+       struct llist_item *prev, *l;
+
+redo_from_start:
+       l = (hint == NULL) ? list->front : hint;
+       prev = NULL;
+       while (l) {
+               int cmp = hashcmp(l->sha1, sha1);
+               if (cmp > 0) /* not in list, since sorted */
+                       return prev;
+               if (!cmp) { /* found */
+                       if (prev == NULL) {
+                               if (hint != NULL && hint != list->front) {
+                                       /* we don't know the previous element */
+                                       hint = NULL;
+                                       goto redo_from_start;
+                               }
+                               list->front = l->next;
+                       } else
+                               prev->next = l->next;
+                       if (l == list->back)
+                               list->back = prev;
+                       llist_item_put(l);
+                       list->size--;
+                       return prev;
+               }
+               prev = l;
+               l = l->next;
+       }
+       return prev;
+}
+
+/* computes A\B */
+static void llist_sorted_difference_inplace(struct llist *A,
+                                    struct llist *B)
+{
+       struct llist_item *hint, *b;
+
+       hint = NULL;
+       b = B->front;
+
+       while (b) {
+               hint = llist_sorted_remove(A, b->sha1, hint);
+               b = b->next;
+       }
+}
+
+static inline struct pack_list * pack_list_insert(struct pack_list **pl,
+                                          struct pack_list *entry)
+{
+       struct pack_list *p = xmalloc(sizeof(struct pack_list));
+       memcpy(p, entry, sizeof(struct pack_list));
+       p->next = *pl;
+       *pl = p;
+       return p;
+}
+
+static inline size_t pack_list_size(struct pack_list *pl)
+{
+       size_t ret = 0;
+       while (pl) {
+               ret++;
+               pl = pl->next;
+       }
+       return ret;
+}
+
+static struct pack_list * pack_list_difference(const struct pack_list *A,
+                                              const struct pack_list *B)
+{
+       struct pack_list *ret;
+       const struct pack_list *pl;
+
+       if (A == NULL)
+               return NULL;
+
+       pl = B;
+       while (pl != NULL) {
+               if (A->pack == pl->pack)
+                       return pack_list_difference(A->next, B);
+               pl = pl->next;
+       }
+       ret = xmalloc(sizeof(struct pack_list));
+       memcpy(ret, A, sizeof(struct pack_list));
+       ret->next = pack_list_difference(A->next, B);
+       return ret;
+}
+
+static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
+{
+       unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
+       const unsigned char *p1_base, *p2_base;
+       struct llist_item *p1_hint = NULL, *p2_hint = NULL;
+
+       p1_base = p1->pack->index_data;
+       p2_base = p2->pack->index_data;
+       p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8);
+       p2_base += 256 * 4 + ((p2->pack->index_version < 2) ? 4 : 8);
+       p1_step = (p1->pack->index_version < 2) ? 24 : 20;
+       p2_step = (p2->pack->index_version < 2) ? 24 : 20;
+
+       while (p1_off < p1->pack->num_objects * p1_step &&
+              p2_off < p2->pack->num_objects * p2_step)
+       {
+               int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
+               /* cmp ~ p1 - p2 */
+               if (cmp == 0) {
+                       p1_hint = llist_sorted_remove(p1->unique_objects,
+                                       p1_base + p1_off, p1_hint);
+                       p2_hint = llist_sorted_remove(p2->unique_objects,
+                                       p1_base + p1_off, p2_hint);
+                       p1_off += p1_step;
+                       p2_off += p2_step;
+                       continue;
+               }
+               if (cmp < 0) { /* p1 has the object, p2 doesn't */
+                       p1_off += p1_step;
+               } else { /* p2 has the object, p1 doesn't */
+                       p2_off += p2_step;
+               }
+       }
+}
+
+static void pll_free(struct pll *l)
+{
+       struct pll *old;
+       struct pack_list *opl;
+
+       while (l) {
+               old = l;
+               while (l->pl) {
+                       opl = l->pl;
+                       l->pl = opl->next;
+                       free(opl);
+               }
+               l = l->next;
+               free(old);
+       }
+}
+
+/* all the permutations have to be free()d at the same time,
+ * since they refer to each other
+ */
+static struct pll * get_permutations(struct pack_list *list, int n)
+{
+       struct pll *subset, *ret = NULL, *new_pll = NULL, *pll;
+
+       if (list == NULL || pack_list_size(list) < n || n == 0)
+               return NULL;
+
+       if (n == 1) {
+               while (list) {
+                       new_pll = xmalloc(sizeof(pll));
+                       new_pll->pl = NULL;
+                       pack_list_insert(&new_pll->pl, list);
+                       new_pll->next = ret;
+                       ret = new_pll;
+                       list = list->next;
+               }
+               return ret;
+       }
+
+       while (list->next) {
+               subset = get_permutations(list->next, n - 1);
+               while (subset) {
+                       new_pll = xmalloc(sizeof(pll));
+                       new_pll->pl = subset->pl;
+                       pack_list_insert(&new_pll->pl, list);
+                       new_pll->next = ret;
+                       ret = new_pll;
+                       subset = subset->next;
+               }
+               list = list->next;
+       }
+       return ret;
+}
+
+static int is_superset(struct pack_list *pl, struct llist *list)
+{
+       struct llist *diff;
+
+       diff = llist_copy(list);
+
+       while (pl) {
+               llist_sorted_difference_inplace(diff, pl->all_objects);
+               if (diff->size == 0) { /* we're done */
+                       llist_free(diff);
+                       return 1;
+               }
+               pl = pl->next;
+       }
+       llist_free(diff);
+       return 0;
+}
+
+static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
+{
+       size_t ret = 0;
+       unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
+       const unsigned char *p1_base, *p2_base;
+
+       p1_base = p1->index_data;
+       p2_base = p2->index_data;
+       p1_base += 256 * 4 + ((p1->index_version < 2) ? 4 : 8);
+       p2_base += 256 * 4 + ((p2->index_version < 2) ? 4 : 8);
+       p1_step = (p1->index_version < 2) ? 24 : 20;
+       p2_step = (p2->index_version < 2) ? 24 : 20;
+
+       while (p1_off < p1->num_objects * p1_step &&
+              p2_off < p2->num_objects * p2_step)
+       {
+               int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
+               /* cmp ~ p1 - p2 */
+               if (cmp == 0) {
+                       ret++;
+                       p1_off += p1_step;
+                       p2_off += p2_step;
+                       continue;
+               }
+               if (cmp < 0) { /* p1 has the object, p2 doesn't */
+                       p1_off += p1_step;
+               } else { /* p2 has the object, p1 doesn't */
+                       p2_off += p2_step;
+               }
+       }
+       return ret;
+}
+
+/* another O(n^2) function ... */
+static size_t get_pack_redundancy(struct pack_list *pl)
+{
+       struct pack_list *subset;
+       size_t ret = 0;
+
+       if (pl == NULL)
+               return 0;
+
+       while ((subset = pl->next)) {
+               while (subset) {
+                       ret += sizeof_union(pl->pack, subset->pack);
+                       subset = subset->next;
+               }
+               pl = pl->next;
+       }
+       return ret;
+}
+
+static inline off_t pack_set_bytecount(struct pack_list *pl)
+{
+       off_t ret = 0;
+       while (pl) {
+               ret += pl->pack->pack_size;
+               ret += pl->pack->index_size;
+               pl = pl->next;
+       }
+       return ret;
+}
+
+static void minimize(struct pack_list **min)
+{
+       struct pack_list *pl, *unique = NULL,
+               *non_unique = NULL, *min_perm = NULL;
+       struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm;
+       struct llist *missing;
+       off_t min_perm_size = 0, perm_size;
+       int n;
+
+       pl = local_packs;
+       while (pl) {
+               if (pl->unique_objects->size)
+                       pack_list_insert(&unique, pl);
+               else
+                       pack_list_insert(&non_unique, pl);
+               pl = pl->next;
+       }
+       /* find out which objects are missing from the set of unique packs */
+       missing = llist_copy(all_objects);
+       pl = unique;
+       while (pl) {
+               llist_sorted_difference_inplace(missing, pl->all_objects);
+               pl = pl->next;
+       }
+
+       /* return if there are no objects missing from the unique set */
+       if (missing->size == 0) {
+               *min = unique;
+               return;
+       }
+
+       /* find the permutations which contain all missing objects */
+       for (n = 1; n <= pack_list_size(non_unique) && !perm_ok; n++) {
+               perm_all = perm = get_permutations(non_unique, n);
+               while (perm) {
+                       if (is_superset(perm->pl, missing)) {
+                               new_perm = xmalloc(sizeof(struct pll));
+                               memcpy(new_perm, perm, sizeof(struct pll));
+                               new_perm->next = perm_ok;
+                               perm_ok = new_perm;
+                       }
+                       perm = perm->next;
+               }
+               if (perm_ok)
+                       break;
+               pll_free(perm_all);
+       }
+       if (perm_ok == NULL)
+               die("Internal error: No complete sets found!");
+
+       /* find the permutation with the smallest size */
+       perm = perm_ok;
+       while (perm) {
+               perm_size = pack_set_bytecount(perm->pl);
+               if (!min_perm_size || min_perm_size > perm_size) {
+                       min_perm_size = perm_size;
+                       min_perm = perm->pl;
+               }
+               perm = perm->next;
+       }
+       *min = min_perm;
+       /* add the unique packs to the list */
+       pl = unique;
+       while (pl) {
+               pack_list_insert(min, pl);
+               pl = pl->next;
+       }
+}
+
+static void load_all_objects(void)
+{
+       struct pack_list *pl = local_packs;
+       struct llist_item *hint, *l;
+
+       llist_init(&all_objects);
+
+       while (pl) {
+               hint = NULL;
+               l = pl->all_objects->front;
+               while (l) {
+                       hint = llist_insert_sorted_unique(all_objects,
+                                                         l->sha1, hint);
+                       l = l->next;
+               }
+               pl = pl->next;
+       }
+       /* remove objects present in remote packs */
+       pl = altodb_packs;
+       while (pl) {
+               llist_sorted_difference_inplace(all_objects, pl->all_objects);
+               pl = pl->next;
+       }
+}
+
+/* this scales like O(n^2) */
+static void cmp_local_packs(void)
+{
+       struct pack_list *subset, *pl = local_packs;
+
+       while ((subset = pl)) {
+               while ((subset = subset->next))
+                       cmp_two_packs(pl, subset);
+               pl = pl->next;
+       }
+}
+
+static void scan_alt_odb_packs(void)
+{
+       struct pack_list *local, *alt;
+
+       alt = altodb_packs;
+       while (alt) {
+               local = local_packs;
+               while (local) {
+                       llist_sorted_difference_inplace(local->unique_objects,
+                                                       alt->all_objects);
+                       local = local->next;
+               }
+               llist_sorted_difference_inplace(all_objects, alt->all_objects);
+               alt = alt->next;
+       }
+}
+
+static struct pack_list * add_pack(struct packed_git *p)
+{
+       struct pack_list l;
+       unsigned long off = 0, step;
+       const unsigned char *base;
+
+       if (!p->pack_local && !(alt_odb || verbose))
+               return NULL;
+
+       l.pack = p;
+       llist_init(&l.all_objects);
+
+       if (open_pack_index(p))
+               return NULL;
+
+       base = p->index_data;
+       base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
+       step = (p->index_version < 2) ? 24 : 20;
+       while (off < p->num_objects * step) {
+               llist_insert_back(l.all_objects, base + off);
+               off += step;
+       }
+       /* this list will be pruned in cmp_two_packs later */
+       l.unique_objects = llist_copy(l.all_objects);
+       if (p->pack_local)
+               return pack_list_insert(&local_packs, &l);
+       else
+               return pack_list_insert(&altodb_packs, &l);
+}
+
+static struct pack_list * add_pack_file(const char *filename)
+{
+       struct packed_git *p = packed_git;
+
+       if (strlen(filename) < 40)
+               die("Bad pack filename: %s", filename);
+
+       while (p) {
+               if (strstr(p->pack_name, filename))
+                       return add_pack(p);
+               p = p->next;
+       }
+       die("Filename %s not found in packed_git", filename);
+}
+
+static void load_all(void)
+{
+       struct packed_git *p = packed_git;
+
+       while (p) {
+               add_pack(p);
+               p = p->next;
+       }
+}
+
+int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       struct pack_list *min, *red, *pl;
+       struct llist *ignore;
+       unsigned char *sha1;
+       char buf[42]; /* 40 byte sha1 + \n + \0 */
+
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage(pack_redundant_usage);
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "--all")) {
+                       load_all_packs = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--verbose")) {
+                       verbose = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--alt-odb")) {
+                       alt_odb = 1;
+                       continue;
+               }
+               if (*arg == '-')
+                       usage(pack_redundant_usage);
+               else
+                       break;
+       }
+
+       prepare_packed_git();
+
+       if (load_all_packs)
+               load_all();
+       else
+               while (*(argv + i) != NULL)
+                       add_pack_file(*(argv + i++));
+
+       if (local_packs == NULL)
+               die("Zero packs found!");
+
+       load_all_objects();
+
+       cmp_local_packs();
+       if (alt_odb)
+               scan_alt_odb_packs();
+
+       /* ignore objects given on stdin */
+       llist_init(&ignore);
+       if (!isatty(0)) {
+               while (fgets(buf, sizeof(buf), stdin)) {
+                       sha1 = xmalloc(20);
+                       if (get_sha1_hex(buf, sha1))
+                               die("Bad sha1 on stdin: %s", buf);
+                       llist_insert_sorted_unique(ignore, sha1, NULL);
+               }
+       }
+       llist_sorted_difference_inplace(all_objects, ignore);
+       pl = local_packs;
+       while (pl) {
+               llist_sorted_difference_inplace(pl->unique_objects, ignore);
+               pl = pl->next;
+       }
+
+       minimize(&min);
+
+       if (verbose) {
+               fprintf(stderr, "There are %lu packs available in alt-odbs.\n",
+                       (unsigned long)pack_list_size(altodb_packs));
+               fprintf(stderr, "The smallest (bytewise) set of packs is:\n");
+               pl = min;
+               while (pl) {
+                       fprintf(stderr, "\t%s\n", pl->pack->pack_name);
+                       pl = pl->next;
+               }
+               fprintf(stderr, "containing %lu duplicate objects "
+                               "with a total size of %lukb.\n",
+                       (unsigned long)get_pack_redundancy(min),
+                       (unsigned long)pack_set_bytecount(min)/1024);
+               fprintf(stderr, "A total of %lu unique objects were considered.\n",
+                       (unsigned long)all_objects->size);
+               fprintf(stderr, "Redundant packs (with indexes):\n");
+       }
+       pl = red = pack_list_difference(local_packs, min);
+       while (pl) {
+               printf("%s\n%s\n",
+                      sha1_pack_index_name(pl->pack->sha1),
+                      pl->pack->pack_name);
+               pl = pl->next;
+       }
+       if (verbose)
+               fprintf(stderr, "%luMB of redundant packs in total.\n",
+                       (unsigned long)pack_set_bytecount(red)/(1024*1024));
+
+       return 0;
+}
diff --git a/builtin-patch-id.c b/builtin-patch-id.c
new file mode 100644 (file)
index 0000000..af0911e
--- /dev/null
@@ -0,0 +1,85 @@
+#include "cache.h"
+#include "exec_cmd.h"
+
+static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
+{
+       unsigned char result[20];
+       char name[50];
+
+       if (!patchlen)
+               return;
+
+       git_SHA1_Final(result, c);
+       memcpy(name, sha1_to_hex(id), 41);
+       printf("%s %s\n", sha1_to_hex(result), name);
+       git_SHA1_Init(c);
+}
+
+static int remove_space(char *line)
+{
+       char *src = line;
+       char *dst = line;
+       unsigned char c;
+
+       while ((c = *src++) != '\0') {
+               if (!isspace(c))
+                       *dst++ = c;
+       }
+       return dst - line;
+}
+
+static void generate_id_list(void)
+{
+       static unsigned char sha1[20];
+       static char line[1000];
+       git_SHA_CTX ctx;
+       int patchlen = 0;
+
+       git_SHA1_Init(&ctx);
+       while (fgets(line, sizeof(line), stdin) != NULL) {
+               unsigned char n[20];
+               char *p = line;
+               int len;
+
+               if (!memcmp(line, "diff-tree ", 10))
+                       p += 10;
+               else if (!memcmp(line, "commit ", 7))
+                       p += 7;
+
+               if (!get_sha1_hex(p, n)) {
+                       flush_current_id(patchlen, sha1, &ctx);
+                       hashcpy(sha1, n);
+                       patchlen = 0;
+                       continue;
+               }
+
+               /* Ignore commit comments */
+               if (!patchlen && memcmp(line, "diff ", 5))
+                       continue;
+
+               /* Ignore git-diff index header */
+               if (!memcmp(line, "index ", 6))
+                       continue;
+
+               /* Ignore line numbers when computing the SHA1 of the patch */
+               if (!memcmp(line, "@@ -", 4))
+                       continue;
+
+               /* Compute the sha without whitespace */
+               len = remove_space(line);
+               patchlen += len;
+               git_SHA1_Update(&ctx, line, len);
+       }
+       flush_current_id(patchlen, sha1, &ctx);
+}
+
+static const char patch_id_usage[] = "git patch-id < patch";
+
+int cmd_patch_id(int argc, const char **argv, const char *prefix)
+{
+       if (argc != 1)
+               usage(patch_id_usage);
+
+       generate_id_list();
+       return 0;
+}
index 356d7c1fd3a6fae9558e93eb4f240242a60da368..5633f0ade49f7c845665b7aab202be18a8cc9d8d 100644 (file)
@@ -15,6 +15,7 @@ static const char * const push_usage[] = {
 };
 
 static int thin;
+static int deleterefs;
 static const char *receivepack;
 
 static const char **refspec;
@@ -39,11 +40,24 @@ static void set_refspecs(const char **refs, int nr)
                        if (nr <= ++i)
                                die("tag shorthand without <tag>");
                        len = strlen(refs[i]) + 11;
-                       tag = xmalloc(len);
-                       strcpy(tag, "refs/tags/");
+                       if (deleterefs) {
+                               tag = xmalloc(len+1);
+                               strcpy(tag, ":refs/tags/");
+                       } else {
+                               tag = xmalloc(len);
+                               strcpy(tag, "refs/tags/");
+                       }
                        strcat(tag, refs[i]);
                        ref = tag;
-               }
+               } else if (deleterefs && !strchr(ref, ':')) {
+                       char *delref;
+                       int len = strlen(ref)+1;
+                       delref = xmalloc(len+1);
+                       strcpy(delref, ":");
+                       strcat(delref, ref);
+                       ref = delref;
+               } else if (deleterefs)
+                       die("--delete only accepts plain target ref names");
                add_refspec(ref);
        }
 }
@@ -87,6 +101,37 @@ static void setup_default_push_refspecs(void)
        }
 }
 
+static int push_with_options(struct transport *transport, int flags)
+{
+       int err;
+       int nonfastforward;
+       if (receivepack)
+               transport_set_option(transport,
+                                    TRANS_OPT_RECEIVEPACK, receivepack);
+       if (thin)
+               transport_set_option(transport, TRANS_OPT_THIN, "yes");
+
+       if (flags & TRANSPORT_PUSH_VERBOSE)
+               fprintf(stderr, "Pushing to %s\n", transport->url);
+       err = transport_push(transport, refspec_nr, refspec, flags,
+                            &nonfastforward);
+       if (err != 0)
+               error("failed to push some refs to '%s'", transport->url);
+
+       err |= transport_disconnect(transport);
+
+       if (!err)
+               return 0;
+
+       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 'Note about\n"
+                      "fast-forwards' section of 'git push --help' for details.\n");
+       }
+
+       return 1;
+}
+
 static int do_push(const char *repo, int flags)
 {
        int i, errs;
@@ -135,33 +180,19 @@ static int do_push(const char *repo, int flags)
                url = remote->url;
                url_nr = remote->url_nr;
        }
-       for (i = 0; i < url_nr; i++) {
-               struct transport *transport =
-                       transport_get(remote, url[i]);
-               int err;
-               int nonfastforward;
-               if (receivepack)
-                       transport_set_option(transport,
-                                            TRANS_OPT_RECEIVEPACK, receivepack);
-               if (thin)
-                       transport_set_option(transport, TRANS_OPT_THIN, "yes");
-
-               if (flags & TRANSPORT_PUSH_VERBOSE)
-                       fprintf(stderr, "Pushing to %s\n", url[i]);
-               err = transport_push(transport, refspec_nr, refspec, flags,
-                                    &nonfastforward);
-               err |= transport_disconnect(transport);
-
-               if (!err)
-                       continue;
-
-               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"
-                              "section of 'git push --help' for details.\n");
+       if (url_nr) {
+               for (i = 0; i < url_nr; i++) {
+                       struct transport *transport =
+                               transport_get(remote, url[i]);
+                       if (push_with_options(transport, flags))
+                               errs++;
                }
-               errs++;
+       } else {
+               struct transport *transport =
+                       transport_get(remote, NULL);
+
+               if (push_with_options(transport, flags))
+                       errs++;
        }
        return !!errs;
 }
@@ -179,6 +210,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL),
                OPT_BIT( 0 , "mirror", &flags, "mirror all refs",
                            (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
+               OPT_BOOLEAN( 0, "delete", &deleterefs, "delete refs"),
                OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"),
                OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
                OPT_BIT( 0,  "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
@@ -186,12 +218,19 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
                OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
                OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
+               OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
+                       TRANSPORT_PUSH_SET_UPSTREAM),
                OPT_END()
        };
 
        git_config(git_default_config, NULL);
        argc = parse_options(argc, argv, prefix, options, push_usage, 0);
 
+       if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
+               die("--delete is incompatible with --all, --mirror and --tags");
+       if (deleterefs && argc < 2)
+               die("--delete doesn't make sense without any refs");
+
        if (tags)
                add_refspec("refs/tags/*");
 
index 2a3a32cbfe83a0e0366d04a99bb49606f647f594..8bdcab11389e8b4facab355f27f0e1926134b86f 100644 (file)
@@ -13,6 +13,7 @@
 #include "dir.h"
 #include "builtin.h"
 #include "parse-options.h"
+#include "resolve-undo.h"
 
 static int nr_trees;
 static struct tree *trees[MAX_UNPACK_TREES];
@@ -31,7 +32,7 @@ static int list_tree(unsigned char *sha1)
 }
 
 static const char * const read_tree_usage[] = {
-       "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]]  [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]",
+       "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]",
        NULL
 };
 
@@ -64,6 +65,34 @@ static int exclude_per_directory_cb(const struct option *opt, const char *arg,
        return 0;
 }
 
+static void debug_stage(const char *label, struct cache_entry *ce,
+                       struct unpack_trees_options *o)
+{
+       printf("%s ", label);
+       if (!ce)
+               printf("(missing)\n");
+       else if (ce == o->df_conflict_entry)
+               printf("(conflict)\n");
+       else
+               printf("%06o #%d %s %.8s\n",
+                      ce->ce_mode, ce_stage(ce), ce->name,
+                      sha1_to_hex(ce->sha1));
+}
+
+static int debug_merge(struct cache_entry **stages, struct unpack_trees_options *o)
+{
+       int i;
+
+       printf("* %d-way merge\n", o->merge_size);
+       debug_stage("index", stages[0], o);
+       for (i = 1; i <= o->merge_size; i++) {
+               char buf[24];
+               sprintf(buf, "ent#%d", i);
+               debug_stage(buf, stages[i], o);
+       }
+       return 0;
+}
+
 static struct lock_file lock_file;
 
 int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
@@ -98,6 +127,10 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                  PARSE_OPT_NONEG, exclude_per_directory_cb },
                OPT_SET_INT('i', NULL, &opts.index_only,
                            "don't check the working tree after merging", 1),
+               OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
+                           "skip applying sparse checkout filter", 1),
+               OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack,
+                           "debug unpack-trees", 1),
                OPT_END()
        };
 
@@ -122,6 +155,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                        die("You need to resolve your current index first");
                stage = opts.merge = 1;
        }
+       resolve_undo_clear();
 
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
@@ -165,6 +199,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                        opts.head_idx = 1;
        }
 
+       if (opts.debug_unpack)
+               opts.fn = debug_merge;
+
        cache_tree_free(&active_cache_tree);
        for (i = 0; i < nr_trees; i++) {
                struct tree *tree = trees[i];
@@ -174,6 +211,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
        if (unpack_trees(nr_trees, t, &opts))
                return 128;
 
+       if (opts.debug_unpack)
+               return 0; /* do not write the index out */
+
        /*
         * When reading only one tree (either the most basic form,
         * "-m ent" or "--reset ent" form), we can obtain a fully
index 78c0e69cdc9fe2d21e83947932cb353c0553452e..4320c93e700a08911e42e3e949656af67b675244 100644 (file)
@@ -204,59 +204,47 @@ static int is_ref_checked_out(const char *ref)
        return !strcmp(head_name, ref);
 }
 
-static char *warn_unconfigured_deny_msg[] = {
-       "Updating the currently checked out branch may cause confusion,",
-       "as the index and work tree do not reflect changes that are in HEAD.",
-       "As a result, you may see the changes you just pushed into it",
-       "reverted when you run 'git diff' over there, and you may want",
-       "to run 'git reset --hard' before starting to work to recover.",
+static char *refuse_unconfigured_deny_msg[] = {
+       "By default, updating the current branch in a non-bare repository",
+       "is denied, because it will make the index and work tree inconsistent",
+       "with what you pushed, and will require 'git reset --hard' to match",
+       "the work tree to HEAD.",
        "",
        "You can set 'receive.denyCurrentBranch' configuration variable to",
-       "'refuse' in the remote repository to forbid pushing into its",
-       "current branch."
+       "'ignore' or 'warn' in the remote repository to allow pushing into",
+       "its current branch; however, this is not recommended unless you",
+       "arranged to update its work tree to match what you pushed in some",
+       "other way.",
        "",
-       "To allow pushing into the current branch, you can set it to 'ignore';",
-       "but this is not recommended unless you arranged to update its work",
-       "tree to match what you pushed in some other way.",
-       "",
-       "To squelch this message, you can set it to 'warn'.",
-       "",
-       "Note that the default will change in a future version of git",
-       "to refuse updating the current branch unless you have the",
-       "configuration variable set to either 'ignore' or 'warn'."
+       "To squelch this message and still keep the default behaviour, set",
+       "'receive.denyCurrentBranch' configuration variable to 'refuse'."
 };
 
-static void warn_unconfigured_deny(void)
+static void refuse_unconfigured_deny(void)
 {
        int i;
-       for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++)
-               warning("%s", warn_unconfigured_deny_msg[i]);
+       for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++)
+               error("%s", refuse_unconfigured_deny_msg[i]);
 }
 
-static char *warn_unconfigured_deny_delete_current_msg[] = {
-       "Deleting the current branch can cause confusion by making the next",
-       "'git clone' not check out any file.",
+static char *refuse_unconfigured_deny_delete_current_msg[] = {
+       "By default, deleting the current branch is denied, because the next",
+       "'git clone' won't result in any file checked out, causing confusion.",
        "",
        "You can set 'receive.denyDeleteCurrent' configuration variable to",
-       "'refuse' in the remote repository to disallow deleting the current",
-       "branch.",
-       "",
-       "You can set it to 'ignore' to allow such a delete without a warning.",
+       "'warn' or 'ignore' in the remote repository to allow deleting the",
+       "current branch, with or without a warning message.",
        "",
-       "To make this warning message less loud, you can set it to 'warn'.",
-       "",
-       "Note that the default will change in a future version of git",
-       "to refuse deleting the current branch unless you have the",
-       "configuration variable set to either 'ignore' or 'warn'."
+       "To squelch this message, you can set it to 'refuse'."
 };
 
-static void warn_unconfigured_deny_delete_current(void)
+static void refuse_unconfigured_deny_delete_current(void)
 {
        int i;
        for (i = 0;
-            i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg);
+            i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg);
             i++)
-               warning("%s", warn_unconfigured_deny_delete_current_msg[i]);
+               error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
 }
 
 static const char *update(struct command *cmd)
@@ -276,14 +264,14 @@ static const char *update(struct command *cmd)
                switch (deny_current_branch) {
                case DENY_IGNORE:
                        break;
-               case DENY_UNCONFIGURED:
                case DENY_WARN:
                        warning("updating the current branch");
-                       if (deny_current_branch == DENY_UNCONFIGURED)
-                               warn_unconfigured_deny();
                        break;
                case DENY_REFUSE:
+               case DENY_UNCONFIGURED:
                        error("refusing to update checked out branch: %s", name);
+                       if (deny_current_branch == DENY_UNCONFIGURED)
+                               refuse_unconfigured_deny();
                        return "branch is currently checked out";
                }
        }
@@ -305,12 +293,12 @@ static const char *update(struct command *cmd)
                        case DENY_IGNORE:
                                break;
                        case DENY_WARN:
-                       case DENY_UNCONFIGURED:
-                               if (deny_delete_current == DENY_UNCONFIGURED)
-                                       warn_unconfigured_deny_delete_current();
                                warning("deleting the current branch");
                                break;
                        case DENY_REFUSE:
+                       case DENY_UNCONFIGURED:
+                               if (deny_delete_current == DENY_UNCONFIGURED)
+                                       refuse_unconfigured_deny_delete_current();
                                error("refusing to delete the current branch: %s", name);
                                return "deletion of the current branch prohibited";
                        }
index c4945b870882834fdaa75fd3d65f664570f6a5da..277765b864202b2b6f20069e0cb3f95eaef4fcaa 100644 (file)
@@ -16,6 +16,9 @@ static const char * const builtin_remote_usage[] = {
        "git remote [-v | --verbose] show [-n] <name>",
        "git remote prune [-n | --dry-run] <name>",
        "git remote [-v | --verbose] update [-p | --prune] [group | remote]",
+       "git remote set-url <name> <newurl> [<oldurl>]",
+       "git remote set-url --add <name> <newurl>",
+       "git remote set-url --delete <name> <url>",
        NULL
 };
 
@@ -54,6 +57,13 @@ static const char * const builtin_remote_update_usage[] = {
        NULL
 };
 
+static const char * const builtin_remote_seturl_usage[] = {
+       "git remote set-url [--push] <name> <newurl> [<oldurl>]",
+       "git remote set-url --add <name> <newurl>",
+       "git remote set-url --delete <name> <url>",
+       NULL
+};
+
 #define GET_REF_STATES (1<<0)
 #define GET_HEAD_NAMES (1<<1)
 #define GET_PUSH_REF_STATES (1<<2)
@@ -1255,6 +1265,92 @@ static int update(int argc, const char **argv)
        return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
 }
 
+static int set_url(int argc, const char **argv)
+{
+       int i, push_mode = 0, add_mode = 0, delete_mode = 0;
+       int matches = 0, negative_matches = 0;
+       const char *remotename = NULL;
+       const char *newurl = NULL;
+       const char *oldurl = NULL;
+       struct remote *remote;
+       regex_t old_regex;
+       const char **urlset;
+       int urlset_nr;
+       struct strbuf name_buf = STRBUF_INIT;
+       struct option options[] = {
+               OPT_BOOLEAN('\0', "push", &push_mode,
+                           "manipulate push URLs"),
+               OPT_BOOLEAN('\0', "add", &add_mode,
+                           "add URL"),
+               OPT_BOOLEAN('\0', "delete", &delete_mode,
+                           "delete URLs"),
+               OPT_END()
+       };
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
+                            PARSE_OPT_KEEP_ARGV0);
+
+       if (add_mode && delete_mode)
+               die("--add --delete doesn't make sense");
+
+       if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3))
+               usage_with_options(builtin_remote_seturl_usage, options);
+
+       remotename = argv[1];
+       newurl = argv[2];
+       if (argc > 3)
+               oldurl = argv[3];
+
+       if (delete_mode)
+               oldurl = newurl;
+
+       if (!remote_is_configured(remotename))
+               die("No such remote '%s'", remotename);
+       remote = remote_get(remotename);
+
+       if (push_mode) {
+               strbuf_addf(&name_buf, "remote.%s.pushurl", remotename);
+               urlset = remote->pushurl;
+               urlset_nr = remote->pushurl_nr;
+       } else {
+               strbuf_addf(&name_buf, "remote.%s.url", remotename);
+               urlset = remote->url;
+               urlset_nr = remote->url_nr;
+       }
+
+       /* Special cases that add new entry. */
+       if ((!oldurl && !delete_mode) || add_mode) {
+               if (add_mode)
+                       git_config_set_multivar(name_buf.buf, newurl,
+                               "^$", 0);
+               else
+                       git_config_set(name_buf.buf, newurl);
+               strbuf_release(&name_buf);
+               return 0;
+       }
+
+       /* Old URL specified. Demand that one matches. */
+       if (regcomp(&old_regex, oldurl, REG_EXTENDED))
+               die("Invalid old URL pattern: %s", oldurl);
+
+       for (i = 0; i < urlset_nr; i++)
+               if (!regexec(&old_regex, urlset[i], 0, NULL, 0))
+                       matches++;
+               else
+                       negative_matches++;
+       if (!delete_mode && !matches)
+               die("No such URL found: %s", oldurl);
+       if (delete_mode && !negative_matches && !push_mode)
+               die("Will not delete all non-push URLs");
+
+       regfree(&old_regex);
+
+       if (!delete_mode)
+               git_config_set_multivar(name_buf.buf, newurl, oldurl, 0);
+       else
+               git_config_set_multivar(name_buf.buf, NULL, oldurl, 1);
+       return 0;
+}
+
 static int get_one_entry(struct remote *remote, void *priv)
 {
        struct string_list *list = priv;
@@ -1334,6 +1430,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
                result = rm(argc, argv);
        else if (!strcmp(argv[0], "set-head"))
                result = set_head(argc, argv);
+       else if (!strcmp(argv[0], "set-url"))
+               result = set_url(argc, argv);
        else if (!strcmp(argv[0], "show"))
                result = show(argc, argv);
        else if (!strcmp(argv[0], "prune"))
index 2be9ffb77b38ae687537f3599c9d11e3fbc026a9..34f9acee910406c7ba0bf9eed76267a0cf8f46c2 100644 (file)
@@ -103,15 +103,29 @@ static int diff_two(const char *file1, const char *label1,
 int cmd_rerere(int argc, const char **argv, const char *prefix)
 {
        struct string_list merge_rr = { NULL, 0, 0, 1 };
-       int i, fd;
-
+       int i, fd, flags = 0;
+
+       if (2 < argc) {
+               if (!strcmp(argv[1], "-h"))
+                       usage(git_rerere_usage);
+               if (!strcmp(argv[1], "--rerere-autoupdate"))
+                       flags = RERERE_AUTOUPDATE;
+               else if (!strcmp(argv[1], "--no-rerere-autoupdate"))
+                       flags = RERERE_NOAUTOUPDATE;
+               if (flags) {
+                       argc--;
+                       argv++;
+               }
+       }
        if (argc < 2)
-               return rerere();
+               return rerere(flags);
 
-       if (!strcmp(argv[1], "-h"))
-               usage(git_rerere_usage);
+       if (!strcmp(argv[1], "forget")) {
+               const char **pathspec = get_pathspec(prefix, argv + 2);
+               return rerere_forget(pathspec);
+       }
 
-       fd = setup_rerere(&merge_rr);
+       fd = setup_rerere(&merge_rr, flags);
        if (fd < 0)
                return 0;
 
index e4418bced23807df174e600a4babbf1d604bac8e..0f5022eed24f980f6fedee49f8602fefa6fe85e4 100644 (file)
@@ -18,6 +18,8 @@
 #include "tree.h"
 #include "branch.h"
 #include "parse-options.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
 
 static const char * const git_reset_usage[] = {
        "git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
@@ -54,27 +56,44 @@ static inline int is_merge(void)
 
 static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
 {
-       int i = 0;
-       const char *args[6];
+       int nr = 1;
+       int newfd;
+       struct tree_desc desc[2];
+       struct unpack_trees_options opts;
+       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 
-       args[i++] = "read-tree";
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = 1;
+       opts.src_index = &the_index;
+       opts.dst_index = &the_index;
+       opts.fn = oneway_merge;
+       opts.merge = 1;
        if (!quiet)
-               args[i++] = "-v";
+               opts.verbose_update = 1;
        switch (reset_type) {
        case MERGE:
-               args[i++] = "-u";
-               args[i++] = "-m";
+               opts.update = 1;
                break;
        case HARD:
-               args[i++] = "-u";
+               opts.update = 1;
                /* fallthrough */
        default:
-               args[i++] = "--reset";
+               opts.reset = 1;
        }
-       args[i++] = sha1_to_hex(sha1);
-       args[i] = NULL;
 
-       return run_command_v_opt(args, RUN_GIT_CMD);
+       newfd = hold_locked_index(lock, 1);
+
+       read_cache_unmerged();
+
+       if (!fill_tree_descriptor(desc + nr - 1, sha1))
+               return error("Failed to find tree of %s.", sha1_to_hex(sha1));
+       if (unpack_trees(nr, desc, &opts))
+               return -1;
+       if (write_cache(newfd, active_cache, active_nr) ||
+           commit_locked_index(lock))
+               return error("Could not write new index file.");
+
+       return 0;
 }
 
 static void print_new_head_line(struct commit *commit)
@@ -202,6 +221,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
        struct commit *commit;
        char *reflog_action, msg[1024];
        const struct option options[] = {
+               OPT__QUIET(&quiet),
                OPT_SET_INT(0, "mixed", &reset_type,
                                                "reset HEAD and index", MIXED),
                OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
@@ -209,8 +229,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                                "reset HEAD, index and working tree", HARD),
                OPT_SET_INT(0, "merge", &reset_type,
                                "reset HEAD, index and working tree", MERGE),
-               OPT_BOOLEAN('q', NULL, &quiet,
-                               "disable showing new HEAD in hard reset and progress message"),
                OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
                OPT_END()
        };
@@ -289,6 +307,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
        if (reset_type == HARD || reset_type == MERGE)
                setup_work_tree();
 
+       if (reset_type == MIXED && is_bare_repository())
+               die("%s reset is not allowed in a bare repository",
+                   reset_type_names[reset_type]);
+
        /* Soft reset does not touch the index file nor the working tree
         * at all, but requires them in a good order.  Other resets reset
         * the index file to the tree object we are switching to. */
index 91b604289dd59101cb6477fa49dd9c342e5b39e5..c924b3a2c76c1f9a7f5531504825ed1b5456d41a 100644 (file)
@@ -253,7 +253,7 @@ static void print_var_int(const char *var, int val)
        printf("%s=%d\n", var, val);
 }
 
-int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
+static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
 {
        int cnt, flags = info->bisect_show_flags;
        char hex[41] = "";
@@ -322,7 +322,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        if (revs.bisect)
                bisect_list = 1;
 
-       quiet = DIFF_OPT_TST(&revs.diffopt, QUIET);
+       quiet = DIFF_OPT_TST(&revs.diffopt, QUICK);
        for (i = 1 ; i < argc; i++) {
                const char *arg = argv[i];
 
index 37d02335212dea9672e2472970f66dc4ac95dfd1..a8c5043dedd785b8fc43c0921edd01e2adac65e1 100644 (file)
@@ -41,6 +41,7 @@ static int is_rev_argument(const char *arg)
                "--all",
                "--bisect",
                "--dense",
+               "--branches=",
                "--branches",
                "--header",
                "--max-age=",
@@ -51,8 +52,11 @@ static int is_rev_argument(const char *arg)
                "--objects-edge",
                "--parents",
                "--pretty",
+               "--remotes=",
                "--remotes",
+               "--glob=",
                "--sparse",
+               "--tags=",
                "--tags",
                "--topo-order",
                "--date-order",
@@ -569,18 +573,43 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                for_each_ref_in("refs/bisect/good", anti_reference, NULL);
                                continue;
                        }
+                       if (!prefixcmp(arg, "--branches=")) {
+                               for_each_glob_ref_in(show_reference, arg + 11,
+                                       "refs/heads/", NULL);
+                               continue;
+                       }
                        if (!strcmp(arg, "--branches")) {
                                for_each_branch_ref(show_reference, NULL);
                                continue;
                        }
+                       if (!prefixcmp(arg, "--tags=")) {
+                               for_each_glob_ref_in(show_reference, arg + 7,
+                                       "refs/tags/", NULL);
+                               continue;
+                       }
                        if (!strcmp(arg, "--tags")) {
                                for_each_tag_ref(show_reference, NULL);
                                continue;
                        }
+                       if (!prefixcmp(arg, "--glob=")) {
+                               for_each_glob_ref(show_reference, arg + 7, NULL);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--remotes=")) {
+                               for_each_glob_ref_in(show_reference, arg + 10,
+                                       "refs/remotes/", NULL);
+                               continue;
+                       }
                        if (!strcmp(arg, "--remotes")) {
                                for_each_remote_ref(show_reference, NULL);
                                continue;
                        }
+                       if (!strcmp(arg, "--show-toplevel")) {
+                               const char *work_tree = get_git_work_tree();
+                               if (work_tree)
+                                       puts(work_tree);
+                               continue;
+                       }
                        if (!strcmp(arg, "--show-prefix")) {
                                if (prefix)
                                        puts(prefix);
index 151aa6a981832954120359f7c953015525b530d8..8ac86f09434d1dad830c924de8cadcef6705e1e5 100644 (file)
@@ -38,6 +38,7 @@ static const char * const cherry_pick_usage[] = {
 static int edit, no_replay, no_commit, mainline, signoff;
 static enum { REVERT, CHERRY_PICK } action;
 static struct commit *commit;
+static int allow_rerere_auto;
 
 static const char *me;
 
@@ -57,6 +58,7 @@ static void parse_args(int argc, const char **argv)
                OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"),
                OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
                OPT_INTEGER('m', "mainline", &mainline, "parent number"),
+               OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
                OPT_END(),
        };
 
@@ -233,6 +235,19 @@ static struct tree *empty_tree(void)
        return tree;
 }
 
+static NORETURN void die_dirty_index(const char *me)
+{
+       if (read_cache_unmerged()) {
+               die_resolve_conflict(me);
+       } else {
+               if (advice_commit_before_merge)
+                       die("Your local changes would be overwritten by %s.\n"
+                           "Please, commit your changes or stash them to proceed.", me);
+               else
+                       die("Your local changes would be overwritten by %s.\n", me);
+       }
+}
+
 static int revert_or_cherry_pick(int argc, const char **argv)
 {
        unsigned char head[20];
@@ -269,7 +284,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                if (get_sha1("HEAD", head))
                        die ("You do not have a valid HEAD");
                if (index_differs_from("HEAD", 0))
-                       die ("Dirty index: cannot %s", me);
+                       die_dirty_index(me);
        }
        discard_cache();
 
@@ -395,7 +410,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                        die ("Error wrapping up %s", defmsg);
                fprintf(stderr, "Automatic %s failed.%s\n",
                        me, help_msg(commit->object.sha1));
-               rerere();
+               rerere(allow_rerere_auto);
                exit(1);
        }
        if (commit_lock_file(&msg_file) < 0)
index 57975dbcfd7c0dbcba03b88a1bf403f1ec5f528c..f3772c84de0a1f1a18123e55ae6e6983739f590b 100644 (file)
@@ -169,9 +169,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 
        if (read_cache() < 0)
                die("index file corrupt");
-       refresh_cache(REFRESH_QUIET);
 
        pathspec = get_pathspec(prefix, argv);
+       refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL);
+
        seen = NULL;
        for (i = 0; pathspec[i] ; i++)
                /* nothing */;
index 8fffdbf20058e9970af4b5e4a14349ecb4ff455c..76c72065de73ea3f0da4665c0a47a64610e2ead2 100644 (file)
@@ -406,50 +406,20 @@ int send_pack(struct send_pack_args *args,
         */
        new_refs = 0;
        for (ref = remote_refs; ref; ref = ref->next) {
-
-               if (ref->peer_ref)
-                       hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-               else if (!args->send_mirror)
+               if (!ref->peer_ref && !args->send_mirror)
                        continue;
 
-               ref->deletion = is_null_sha1(ref->new_sha1);
-               if (ref->deletion && !allow_deleting_refs) {
-                       ref->status = REF_STATUS_REJECT_NODELETE;
-                       continue;
-               }
-               if (!ref->deletion &&
-                   !hashcmp(ref->old_sha1, ref->new_sha1)) {
-                       ref->status = REF_STATUS_UPTODATE;
+               /* Check for statuses set by set_ref_status_for_push() */
+               switch (ref->status) {
+               case REF_STATUS_REJECT_NONFASTFORWARD:
+               case REF_STATUS_UPTODATE:
                        continue;
+               default:
+                       ; /* do nothing */
                }
 
-               /* This part determines what can overwrite what.
-                * The rules are:
-                *
-                * (0) you can always use --force or +A:B notation to
-                *     selectively force individual ref pairs.
-                *
-                * (1) if the old thing does not exist, it is OK.
-                *
-                * (2) if you do not have the old thing, you are not allowed
-                *     to overwrite it; you would not know what you are losing
-                *     otherwise.
-                *
-                * (3) if both new and old are commit-ish, and new is a
-                *     descendant of old, it is OK.
-                *
-                * (4) regardless of all of the above, removing :B is
-                *     always allowed.
-                */
-
-               ref->nonfastforward =
-                   !ref->deletion &&
-                   !is_null_sha1(ref->old_sha1) &&
-                   (!has_sha1_file(ref->old_sha1)
-                     || !ref_newer(ref->new_sha1, ref->old_sha1));
-
-               if (ref->nonfastforward && !ref->force && !args->force_update) {
-                       ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
+               if (ref->deletion && !allow_deleting_refs) {
+                       ref->status = REF_STATUS_REJECT_NODELETE;
                        continue;
                }
 
@@ -673,6 +643,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
        if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
                return -1;
 
+       set_ref_status_for_push(remote_refs, args.send_mirror,
+               args.force_update);
+
        ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
 
        if (helper_status)
index c4790185ebb9979a6772cf7159d16c2a1deeeaf3..4ef1c4f508b0261e725c360e96f2b8cbed50e9ce 100644 (file)
@@ -140,7 +140,7 @@ static int delete_tag(const char *name, const char *ref,
 {
        if (delete_ref(ref, sha1, 0))
                return 1;
-       printf("Deleted tag '%s'\n", name);
+       printf("Deleted tag '%s' (was %s)\n", name, find_unique_abbrev(sha1, DEFAULT_ABBREV));
        return 0;
 }
 
@@ -479,6 +479,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                die("%s: cannot lock the ref", ref);
        if (write_ref_sha1(lock, object, NULL) < 0)
                die("%s: cannot update the ref", ref);
+       if (force && hashcmp(prev, object))
+               printf("Updated tag '%s' (was %s)\n", tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
 
        strbuf_release(&buf);
        return 0;
diff --git a/builtin-unpack-file.c b/builtin-unpack-file.c
new file mode 100644 (file)
index 0000000..608590a
--- /dev/null
@@ -0,0 +1,38 @@
+#include "cache.h"
+#include "blob.h"
+#include "exec_cmd.h"
+
+static char *create_temp_file(unsigned char *sha1)
+{
+       static char path[50];
+       void *buf;
+       enum object_type type;
+       unsigned long size;
+       int fd;
+
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf || type != OBJ_BLOB)
+               die("unable to read blob object %s", sha1_to_hex(sha1));
+
+       strcpy(path, ".merge_file_XXXXXX");
+       fd = xmkstemp(path);
+       if (write_in_full(fd, buf, size) != size)
+               die_errno("unable to write temp-file");
+       close(fd);
+       return path;
+}
+
+int cmd_unpack_file(int argc, const char **argv, const char *prefix)
+{
+       unsigned char sha1[20];
+
+       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]);
+
+       git_config(git_default_config, NULL);
+
+       puts(create_temp_file(sha1));
+       return 0;
+}
index a6b7f2d6361fc1e8ea9713082affc72362f08d76..3ab214d24e537865875d6d1e3acd8cf6b019ff22 100644 (file)
@@ -9,6 +9,7 @@
 #include "tree-walk.h"
 #include "builtin.h"
 #include "refs.h"
+#include "resolve-undo.h"
 
 /*
  * Default to not allowing changes to the list of files. The
@@ -24,8 +25,9 @@ static int info_only;
 static int force_remove;
 static int verbose;
 static int mark_valid_only;
-#define MARK_VALID 1
-#define UNMARK_VALID 2
+static int mark_skip_worktree_only;
+#define MARK_FLAG 1
+#define UNMARK_FLAG 2
 
 __attribute__((format (printf, 1, 2)))
 static void report(const char *fmt, ...)
@@ -41,19 +43,15 @@ static void report(const char *fmt, ...)
        va_end(vp);
 }
 
-static int mark_valid(const char *path)
+static int mark_ce_flags(const char *path, int flag, int mark)
 {
        int namelen = strlen(path);
        int pos = cache_name_pos(path, namelen);
        if (0 <= pos) {
-               switch (mark_valid_only) {
-               case MARK_VALID:
-                       active_cache[pos]->ce_flags |= CE_VALID;
-                       break;
-               case UNMARK_VALID:
-                       active_cache[pos]->ce_flags &= ~CE_VALID;
-                       break;
-               }
+               if (mark)
+                       active_cache[pos]->ce_flags |= flag;
+               else
+                       active_cache[pos]->ce_flags &= ~flag;
                cache_tree_invalidate_path(active_cache_tree, path);
                active_cache_changed = 1;
                return 0;
@@ -176,29 +174,29 @@ static int process_directory(const char *path, int len, struct stat *st)
        return error("%s: is a directory - add files inside instead", path);
 }
 
-/*
- * Process a regular file
- */
-static int process_file(const char *path, int len, struct stat *st)
-{
-       int pos = cache_name_pos(path, len);
-       struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos];
-
-       if (ce && S_ISGITLINK(ce->ce_mode))
-               return error("%s is already a gitlink, not replacing", path);
-
-       return add_one_path(ce, path, len, st);
-}
-
 static int process_path(const char *path)
 {
-       int len;
+       int pos, len;
        struct stat st;
+       struct cache_entry *ce;
 
        len = strlen(path);
        if (has_symlink_leading_path(path, len))
                return error("'%s' is beyond a symbolic link", path);
 
+       pos = cache_name_pos(path, len);
+       ce = pos < 0 ? NULL : active_cache[pos];
+       if (ce && ce_skip_worktree(ce)) {
+               /*
+                * working directory version is assumed "good"
+                * so updating it does not make sense.
+                * On the other hand, removing it from index should work
+                */
+               if (allow_remove && remove_file_from_cache(path))
+                       return error("%s: cannot remove from the index", path);
+               return 0;
+       }
+
        /*
         * First things first: get the stat information, to decide
         * what to do about the pathname!
@@ -209,7 +207,13 @@ static int process_path(const char *path)
        if (S_ISDIR(st.st_mode))
                return process_directory(path, len, &st);
 
-       return process_file(path, len, &st);
+       /*
+        * Process a regular file
+        */
+       if (ce && S_ISGITLINK(ce->ce_mode))
+               return error("%s is already a gitlink, not replacing", path);
+
+       return add_one_path(ce, path, len, &st);
 }
 
 static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
@@ -277,7 +281,12 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
                goto free_return;
        }
        if (mark_valid_only) {
-               if (mark_valid(p))
+               if (mark_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG))
+                       die("Unable to mark file %s", path);
+               goto free_return;
+       }
+       if (mark_skip_worktree_only) {
+               if (mark_ce_flags(p, CE_SKIP_WORKTREE, mark_skip_worktree_only == MARK_FLAG))
                        die("Unable to mark file %s", path);
                goto free_return;
        }
@@ -389,7 +398,7 @@ static void read_index_info(int line_termination)
 }
 
 static const char update_index_usage[] =
-"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
 
 static unsigned char head_sha1[20];
 static unsigned char merge_head_sha1[20];
@@ -432,7 +441,18 @@ static int unresolve_one(const char *path)
 
        /* See if there is such entry in the index. */
        pos = cache_name_pos(path, namelen);
-       if (pos < 0) {
+       if (0 <= pos) {
+               /* already merged */
+               pos = unmerge_cache_entry_at(pos);
+               if (pos < active_nr) {
+                       struct cache_entry *ce = active_cache[pos];
+                       if (ce_stage(ce) &&
+                           ce_namelen(ce) == namelen &&
+                           !memcmp(ce->name, path, namelen))
+                               return 0;
+               }
+               /* no resolve-undo information; fall back */
+       } else {
                /* If there isn't, either it is unmerged, or
                 * resolved as "removed" by mistake.  We do not
                 * want to do anything in the former case.
@@ -648,11 +668,19 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                                continue;
                        }
                        if (!strcmp(path, "--assume-unchanged")) {
-                               mark_valid_only = MARK_VALID;
+                               mark_valid_only = MARK_FLAG;
                                continue;
                        }
                        if (!strcmp(path, "--no-assume-unchanged")) {
-                               mark_valid_only = UNMARK_VALID;
+                               mark_valid_only = UNMARK_FLAG;
+                               continue;
+                       }
+                       if (!strcmp(path, "--no-skip-worktree")) {
+                               mark_skip_worktree_only = UNMARK_FLAG;
+                               continue;
+                       }
+                       if (!strcmp(path, "--skip-worktree")) {
+                               mark_skip_worktree_only = MARK_FLAG;
                                continue;
                        }
                        if (!strcmp(path, "--info-only")) {
@@ -703,6 +731,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                                verbose = 1;
                                continue;
                        }
+                       if (!strcmp(path, "--clear-resolve-undo")) {
+                               resolve_undo_clear();
+                               continue;
+                       }
                        if (!strcmp(path, "-h") || !strcmp(path, "--help"))
                                usage(update_index_usage);
                        die("unknown option %s", path);
diff --git a/builtin-var.c b/builtin-var.c
new file mode 100644 (file)
index 0000000..2280518
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Eric Biederman, 2005
+ */
+#include "cache.h"
+#include "exec_cmd.h"
+
+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);
+};
+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++)
+               if ((val = ptr->read(0)))
+                       printf("%s=%s\n", ptr->name, val);
+}
+
+static const char *read_var(const char *var)
+{
+       struct git_var *ptr;
+       const char *val;
+       val = NULL;
+       for (ptr = git_vars; ptr->read; ptr++) {
+               if (strcmp(var, ptr->name) == 0) {
+                       val = ptr->read(IDENT_ERROR_ON_NO_NAME);
+                       break;
+               }
+       }
+       return val;
+}
+
+static int show_config(const char *var, const char *value, void *cb)
+{
+       if (value)
+               printf("%s=%s\n", var, value);
+       else
+               printf("%s\n", var);
+       return git_default_config(var, value, cb);
+}
+
+int cmd_var(int argc, const char **argv, const char *prefix)
+{
+       const char *val;
+       int nongit;
+       if (argc != 2) {
+               usage(var_usage);
+       }
+
+       setup_git_directory_gently(&nongit);
+       val = NULL;
+
+       if (strcmp(argv[1], "-l") == 0) {
+               git_config(show_config, NULL);
+               list_vars();
+               return 0;
+       }
+       git_config(git_default_config, NULL);
+       val = read_var(argv[1]);
+       if (!val)
+               usage(var_usage);
+
+       printf("%s\n", val);
+
+       return 0;
+}
index c3f83c093f6d18dc21ff4112513abbc84fb7858c..e8202f3f5e57a302634be9268866edaba0fd6eb0 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -55,8 +55,10 @@ extern int cmd_fsck(int argc, const char **argv, const char *prefix);
 extern int cmd_gc(int argc, const char **argv, const char *prefix);
 extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
 extern int cmd_grep(int argc, const char **argv, const char *prefix);
+extern int cmd_hash_object(int argc, const char **argv, const char *prefix);
 extern int cmd_help(int argc, const char **argv, const char *prefix);
 extern int cmd_http_fetch(int argc, const char **argv, const char *prefix);
+extern int cmd_index_pack(int argc, const char **argv, const char *prefix);
 extern int cmd_init_db(int argc, const char **argv, const char *prefix);
 extern int cmd_log(int argc, const char **argv, const char *prefix);
 extern int cmd_log_reflog(int argc, const char **argv, const char *prefix);
@@ -67,13 +69,18 @@ extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
 extern int cmd_merge(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_index(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_mktag(int argc, const char **argv, const char *prefix);
 extern int cmd_mktree(int argc, const char **argv, const char *prefix);
 extern int cmd_mv(int argc, const char **argv, const char *prefix);
 extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
 extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
+extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix);
+extern int cmd_patch_id(int argc, const char **argv, const char *prefix);
 extern int cmd_pickaxe(int argc, const char **argv, const char *prefix);
 extern int cmd_prune(int argc, const char **argv, const char *prefix);
 extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
@@ -98,12 +105,14 @@ extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_unpack_file(int argc, const char **argv, const char *prefix);
 extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_update_index(int argc, const char **argv, const char *prefix);
 extern int cmd_update_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_update_server_info(int argc, const char **argv, const char *prefix);
 extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
 extern int cmd_upload_tar(int argc, const char **argv, const char *prefix);
+extern int cmd_var(int argc, const char **argv, const char *prefix);
 extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_version(int argc, const char **argv, const char *prefix);
 extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index bf468e52352c193b355222b718d9f5125c26052c..d478eff1f323f25a474cf019e0de2254c5ff0360 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -177,15 +177,22 @@ struct cache_entry {
 
 #define CE_HASHED    (0x100000)
 #define CE_UNHASHED  (0x200000)
+#define CE_CONFLICTED (0x800000)
+
+/* Only remove in work directory, not index */
+#define CE_WT_REMOVE (0x400000)
+
+#define CE_UNPACKED  (0x1000000)
 
 /*
  * Extended on-disk flags
  */
 #define CE_INTENT_TO_ADD 0x20000000
+#define CE_SKIP_WORKTREE 0x40000000
 /* CE_EXTENDED2 is for future extension */
 #define CE_EXTENDED2 0x80000000
 
-#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD)
+#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE)
 
 /*
  * Safeguard to avoid saving wrong flags:
@@ -234,6 +241,7 @@ static inline size_t ce_namelen(const struct cache_entry *ce)
                            ondisk_cache_entry_size(ce_namelen(ce)))
 #define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
 #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
+#define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE)
 #define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
 
 #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
@@ -282,6 +290,7 @@ static inline int ce_to_dtype(const struct cache_entry *ce)
 struct index_state {
        struct cache_entry **cache;
        unsigned int cache_nr, cache_alloc, cache_changed;
+       struct string_list *resolve_undo;
        struct cache_tree *cache_tree;
        struct cache_time timestamp;
        void *alloc;
@@ -336,6 +345,9 @@ static inline void remove_name_hash(struct cache_entry *ce)
 #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
 #define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase))
 #define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen))
+#define resolve_undo_clear() resolve_undo_clear_index(&the_index)
+#define unmerge_cache_entry_at(at) unmerge_index_entry_at(&the_index, at)
+#define unmerge_cache(pathspec) unmerge_index(&the_index, pathspec)
 #endif
 
 enum object_type {
@@ -445,7 +457,6 @@ extern int index_name_pos(const struct index_state *, const char *name, int name
 #define ADD_CACHE_JUST_APPEND 8                /* Append only; tree.c::read_tree() */
 #define ADD_CACHE_NEW_ONLY 16          /* Do not replace existing ones */
 extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
-extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
 extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
 extern int remove_index_entry_at(struct index_state *, int pos);
 extern void remove_marked_cache_entries(struct index_state *istate);
@@ -464,7 +475,9 @@ extern int index_name_is_other(const struct index_state *, const char *, int);
 /* do stat comparison even if CE_VALID is true */
 #define CE_MATCH_IGNORE_VALID          01
 /* do not check the contents but report dirty on racily-clean entries */
-#define CE_MATCH_RACY_IS_DIRTY 02
+#define CE_MATCH_RACY_IS_DIRTY         02
+/* do stat comparison even if CE_SKIP_WORKTREE is true */
+#define CE_MATCH_IGNORE_SKIP_WORKTREE  04
 extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 
@@ -473,9 +486,6 @@ extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_obje
 extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
 extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 
-/* "careful lstat()" */
-extern int check_path(const char *path, int len, struct stat *st, int skiplen);
-
 #define REFRESH_REALLY         0x0001  /* ignore_valid */
 #define REFRESH_UNMERGED       0x0002  /* allow unmerged */
 #define REFRESH_QUIET          0x0004  /* be quiet about it */
@@ -529,6 +539,7 @@ extern int auto_crlf;
 extern int read_replace_refs;
 extern int fsync_object_files;
 extern int core_preload_index;
+extern int core_apply_sparse_checkout;
 
 enum safe_crlf {
        SAFE_CRLF_FALSE = 0,
@@ -544,6 +555,7 @@ enum branch_track {
        BRANCH_TRACK_REMOTE,
        BRANCH_TRACK_ALWAYS,
        BRANCH_TRACK_EXPLICIT,
+       BRANCH_TRACK_OVERRIDE,
 };
 
 enum rebase_setup_type {
@@ -618,7 +630,6 @@ static inline void hashclr(unsigned char *hash)
 {
        memset(hash, 0, 20);
 }
-extern int is_empty_blob_sha1(const unsigned char *sha1);
 
 #define EMPTY_TREE_SHA1_HEX \
        "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
@@ -688,7 +699,6 @@ extern int has_sha1_pack(const unsigned char *sha1);
 extern int has_sha1_file(const unsigned char *sha1);
 extern int has_loose_object_nonlocal(const unsigned char *sha1);
 
-extern int has_pack_file(const unsigned char *sha1);
 extern int has_pack_index(const unsigned char *sha1);
 
 extern const signed char hexval_table[256];
@@ -702,7 +712,11 @@ static inline unsigned int hexval(unsigned char c)
 #define DEFAULT_ABBREV 7
 
 extern int get_sha1(const char *str, unsigned char *sha1);
-extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode);
+extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int gently, const char *prefix);
+static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode)
+{
+       return get_sha1_with_mode_1(str, sha1, mode, 1, NULL);
+}
 extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
 extern int read_ref(const char *filename, unsigned char *sha1);
@@ -710,6 +724,7 @@ extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *
 extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
 extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
 extern int interpret_branch_name(const char *str, struct strbuf *);
+extern int get_sha1_mb(const char *str, unsigned char *sha1);
 
 extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
 extern const char *ref_rev_parse_rules[];
@@ -747,7 +762,8 @@ const char *show_date_relative(unsigned long time, int tz,
                               size_t timebuf_size);
 int parse_date(const char *date, char *buf, int bufsize);
 void datestamp(char *buf, int bufsize);
-unsigned long approxidate(const char *);
+#define approxidate(s) approxidate_careful((s), NULL)
+unsigned long approxidate_careful(const char *, int *);
 unsigned long approxidate_relative(const char *date, const struct timeval *now);
 enum date_mode parse_date_format(const char *format);
 
@@ -784,8 +800,6 @@ extern int has_symlink_leading_path(const char *name, int len);
 extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
 extern int has_symlink_or_noent_leading_path(const char *name, int len);
 extern int has_dirs_only_path(const char *name, int len, int prefix_len);
-extern void invalidate_lstat_cache(const char *name, int len);
-extern void clear_lstat_cache(void);
 extern void schedule_dir_for_removal(const char *name, int len);
 extern void remove_scheduled_dirs(void);
 
@@ -925,7 +939,11 @@ extern const char *config_exclusive_filename;
 #define MAX_GITNAME (1000)
 extern char git_default_email[MAX_GITNAME];
 extern char git_default_name[MAX_GITNAME];
+#define IDENT_NAME_GIVEN 01
+#define IDENT_MAIL_GIVEN 02
+#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN)
 extern int user_ident_explicitly_given;
+extern int user_ident_sufficiently_given(void);
 
 extern const char *git_commit_encoding;
 extern const char *git_log_output_encoding;
@@ -993,6 +1011,7 @@ extern int diff_auto_refresh_index;
 
 /* match-trees.c */
 void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
+void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *, const char *);
 
 /*
  * whitespace rules.
index 2c0742b721fe1d4d78a7a63ed1c2ee1cf9c96ab8..3cf51665816abb5e5855c036f102019eded23bd6 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -74,7 +74,6 @@ struct pretty_print_context
        struct reflog_walk_info *reflog_info;
 };
 
-extern int non_ascii(int);
 extern int has_non_ascii(const char *text);
 struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
 extern char *reencode_commit_message(const struct commit *commit,
index 0d73f15fa894cb0ded20f0cdd1f7fec3da1643f1..ab65f77ab99500d99d24a9b7266669f37bb02cb2 100644 (file)
@@ -3,9 +3,7 @@
 #include <conio.h>
 #include "../strbuf.h"
 
-#include <shellapi.h>
-
-static int err_win_to_posix(DWORD winerr)
+int err_win_to_posix(DWORD winerr)
 {
        int error = ENOSYS;
        switch(winerr) {
@@ -142,12 +140,20 @@ int mingw_open (const char *filename, int oflags, ...)
        return fd;
 }
 
-static inline time_t filetime_to_time_t(const FILETIME *ft)
+/*
+ * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC.
+ * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch.
+ */
+static inline long long filetime_to_hnsec(const FILETIME *ft)
 {
        long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
-       winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */
-       winTime /= 10000000;             /* Nano to seconds resolution */
-       return (time_t)winTime;
+       /* Windows to Unix Epoch conversion */
+       return winTime - 116444736000000000LL;
+}
+
+static inline time_t filetime_to_time_t(const FILETIME *ft)
+{
+       return (time_t)(filetime_to_hnsec(ft) / 10000000);
 }
 
 /* We keep the do_lstat code in a separate function to avoid recursion.
@@ -283,64 +289,37 @@ int mkstemp(char *template)
 
 int gettimeofday(struct timeval *tv, void *tz)
 {
-       SYSTEMTIME st;
-       struct tm tm;
-       GetSystemTime(&st);
-       tm.tm_year = st.wYear-1900;
-       tm.tm_mon = st.wMonth-1;
-       tm.tm_mday = st.wDay;
-       tm.tm_hour = st.wHour;
-       tm.tm_min = st.wMinute;
-       tm.tm_sec = st.wSecond;
-       tv->tv_sec = tm_to_time_t(&tm);
-       if (tv->tv_sec < 0)
-               return -1;
-       tv->tv_usec = st.wMilliseconds*1000;
+       FILETIME ft;
+       long long hnsec;
+
+       GetSystemTimeAsFileTime(&ft);
+       hnsec = filetime_to_hnsec(&ft);
+       tv->tv_sec = hnsec / 10000000;
+       tv->tv_usec = (hnsec % 10000000) / 10;
        return 0;
 }
 
 int pipe(int filedes[2])
 {
-       int fd;
-       HANDLE h[2], parent;
+       HANDLE h[2];
 
-       if (_pipe(filedes, 8192, 0) < 0)
-               return -1;
-
-       parent = GetCurrentProcess();
-
-       if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[0]),
-                       parent, &h[0], 0, FALSE, DUPLICATE_SAME_ACCESS)) {
-               close(filedes[0]);
-               close(filedes[1]);
-               return -1;
-       }
-       if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[1]),
-                       parent, &h[1], 0, FALSE, DUPLICATE_SAME_ACCESS)) {
-               close(filedes[0]);
-               close(filedes[1]);
-               CloseHandle(h[0]);
+       /* this creates non-inheritable handles */
+       if (!CreatePipe(&h[0], &h[1], NULL, 8192)) {
+               errno = err_win_to_posix(GetLastError());
                return -1;
        }
-       fd = _open_osfhandle((int)h[0], O_NOINHERIT);
-       if (fd < 0) {
-               close(filedes[0]);
-               close(filedes[1]);
+       filedes[0] = _open_osfhandle((int)h[0], O_NOINHERIT);
+       if (filedes[0] < 0) {
                CloseHandle(h[0]);
                CloseHandle(h[1]);
                return -1;
        }
-       close(filedes[0]);
-       filedes[0] = fd;
-       fd = _open_osfhandle((int)h[1], O_NOINHERIT);
-       if (fd < 0) {
+       filedes[1] = _open_osfhandle((int)h[1], O_NOINHERIT);
+       if (filedes[0] < 0) {
                close(filedes[0]);
-               close(filedes[1]);
                CloseHandle(h[1]);
                return -1;
        }
-       close(filedes[1]);
-       filedes[1] = fd;
        return 0;
 }
 
@@ -638,8 +617,8 @@ static int env_compare(const void *a, const void *b)
        return strcasecmp(*ea, *eb);
 }
 
-static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
-                          int prepend_cmd)
+static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
+                             int prepend_cmd, int fhin, int fhout, int fherr)
 {
        STARTUPINFO si;
        PROCESS_INFORMATION pi;
@@ -675,9 +654,9 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
        memset(&si, 0, sizeof(si));
        si.cb = sizeof(si);
        si.dwFlags = STARTF_USESTDHANDLES;
-       si.hStdInput = (HANDLE) _get_osfhandle(0);
-       si.hStdOutput = (HANDLE) _get_osfhandle(1);
-       si.hStdError = (HANDLE) _get_osfhandle(2);
+       si.hStdInput = (HANDLE) _get_osfhandle(fhin);
+       si.hStdOutput = (HANDLE) _get_osfhandle(fhout);
+       si.hStdError = (HANDLE) _get_osfhandle(fherr);
 
        /* concatenate argv, quoting args as we go */
        strbuf_init(&args, 0);
@@ -732,7 +711,14 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
        return (pid_t)pi.hProcess;
 }
 
-pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env)
+static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
+                          int prepend_cmd)
+{
+       return mingw_spawnve_fd(cmd, argv, env, prepend_cmd, 0, 1, 2);
+}
+
+pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
+                    int fhin, int fhout, int fherr)
 {
        pid_t pid;
        char **path = get_path_split();
@@ -754,13 +740,15 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env)
                                pid = -1;
                        }
                        else {
-                               pid = mingw_spawnve(iprog, argv, env, 1);
+                               pid = mingw_spawnve_fd(iprog, argv, env, 1,
+                                                      fhin, fhout, fherr);
                                free(iprog);
                        }
                        argv[0] = argv0;
                }
                else
-                       pid = mingw_spawnve(prog, argv, env, 0);
+                       pid = mingw_spawnve_fd(prog, argv, env, 0,
+                                              fhin, fhout, fherr);
                free(prog);
        }
        free_path_split(path);
@@ -1338,8 +1326,22 @@ static const char *make_backslash_path(const char *path)
 void mingw_open_html(const char *unixpath)
 {
        const char *htmlpath = make_backslash_path(unixpath);
+       typedef HINSTANCE (WINAPI *T)(HWND, const char *,
+                       const char *, const char *, const char *, INT);
+       T ShellExecute;
+       HMODULE shell32;
+
+       shell32 = LoadLibrary("shell32.dll");
+       if (!shell32)
+               die("cannot load shell32.dll");
+       ShellExecute = (T)GetProcAddress(shell32, "ShellExecuteA");
+       if (!ShellExecute)
+               die("cannot run browser");
+
        printf("Launching default browser to display HTML ...\n");
        ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
+
+       FreeLibrary(shell32);
 }
 
 int link(const char *oldpath, const char *newpath)
index b3d299f5bc16810867b7768725b8d33ee999f6b5..e254fb4e068c3248a1aac33d70e40b620cd91088 100644 (file)
@@ -209,18 +209,21 @@ int mingw_getpagesize(void);
  * mingw_fstat() instead of fstat() on Windows.
  */
 #define off_t off64_t
-#define stat _stati64
 #define lseek _lseeki64
+#ifndef ALREADY_DECLARED_STAT_FUNCS
+#define stat _stati64
 int mingw_lstat(const char *file_name, struct stat *buf);
 int mingw_fstat(int fd, struct stat *buf);
 #define fstat mingw_fstat
 #define lstat mingw_lstat
 #define _stati64(x,y) mingw_lstat(x,y)
+#endif
 
 int mingw_utime(const char *file_name, const struct utimbuf *times);
 #define utime mingw_utime
 
-pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env);
+pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
+                    int fhin, int fhout, int fherr);
 void mingw_execvp(const char *cmd, char *const *argv);
 #define execvp mingw_execvp
 
@@ -307,3 +310,8 @@ struct mingw_dirent
 #define readdir(x) mingw_readdir(x)
 struct dirent *mingw_readdir(DIR *dir);
 #endif // !NO_MINGW_REPLACE_READDIR
+
+/*
+ * Used by Pthread API implementation for Windows
+ */
+extern int err_win_to_posix(DWORD winerr);
index 9c753a560fcadb44a6e4afb22828584b6fd20db3..023aba0238c844f79f2662a4b885058acbfc91fd 100644 (file)
@@ -21,30 +21,22 @@ static __inline int strcasecmp (const char *s1, const char *s2)
 }
 
 #undef ERROR
-#undef stat
-#undef _stati64
-#include "compat/mingw.h"
-#undef stat
-#define stat _stati64
+
+/* Use mingw_lstat() instead of lstat()/stat() and mingw_fstat() instead
+ * of fstat(). We add the declaration of these functions here, suppressing
+ * the corresponding declarations in mingw.h, so that we can use the
+ * appropriate structure type (and function) names from the msvc headers.
+ */
+#define stat _stat64
+int mingw_lstat(const char *file_name, struct stat *buf);
+int mingw_fstat(int fd, struct stat *buf);
+#define fstat mingw_fstat
+#define lstat mingw_lstat
 #define _stat64(x,y) mingw_lstat(x,y)
+#define ALREADY_DECLARED_STAT_FUNCS
+
+#include "compat/mingw.h"
+
+#undef ALREADY_DECLARED_STAT_FUNCS
 
-/*
-   Even though _stati64 is normally just defined at _stat64
-   on Windows, we specify it here as a proper struct to avoid
-   compiler warnings about macro redefinition due to magic in
-   mingw.h. Struct taken from ReactOS (GNU GPL license).
-*/
-struct _stati64 {
-       _dev_t  st_dev;
-       _ino_t  st_ino;
-       unsigned short st_mode;
-       short   st_nlink;
-       short   st_uid;
-       short   st_gid;
-       _dev_t  st_rdev;
-       __int64 st_size;
-       time_t  st_atime;
-       time_t  st_mtime;
-       time_t  st_ctime;
-};
 #endif
index 67d5c370a04c0630089e8601eafde5c05957959c..556d8ab11f40550335066f491d08383557e02a00 100644 (file)
@@ -2808,7 +2808,7 @@ re_set_registers (bufp, regs, num_regs, starts, ends)
     {
       bufp->regs_allocated = REGS_UNALLOCATED;
       regs->num_regs = 0;
-      regs->start = regs->end = (regoff_t) 0;
+      regs->start = regs->end = (regoff_t *) 0;
     }
 }
 \f
index 8a2112f22f46b3066d75bd5472dfe05bf88836c8..4374771df202ed3c9d96439ec4695ccd55998167 100644 (file)
@@ -31,6 +31,7 @@ while (@ARGV) {
                push(@args, "iconv.lib");
        } elsif ("$arg" eq "-lcrypto") {
                push(@args, "libeay32.lib");
+       } elsif ("$arg" eq "-lssl") {
                push(@args, "ssleay32.lib");
        } elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") {
                $arg =~ s/^-L/-LIBPATH:/;
diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c
new file mode 100644 (file)
index 0000000..5fc1670
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2009 Andrzej K. Haczewski <ahaczewski@gmail.com>
+ *
+ * DISCLAMER: The implementation is Git-specific, it is subset of original
+ * Pthreads API, without lots of other features that Git doesn't use.
+ * Git also makes sure that the passed arguments are valid, so there's
+ * no need for double-checking.
+ */
+
+#include "../../git-compat-util.h"
+#include "pthread.h"
+
+#include <errno.h>
+#include <limits.h>
+
+static unsigned __stdcall win32_start_routine(void *arg)
+{
+       pthread_t *thread = arg;
+       thread->arg = thread->start_routine(thread->arg);
+       return 0;
+}
+
+int pthread_create(pthread_t *thread, const void *unused,
+                  void *(*start_routine)(void*), void *arg)
+{
+       thread->arg = arg;
+       thread->start_routine = start_routine;
+       thread->handle = (HANDLE)
+               _beginthreadex(NULL, 0, win32_start_routine, thread, 0, NULL);
+
+       if (!thread->handle)
+               return errno;
+       else
+               return 0;
+}
+
+int win32_pthread_join(pthread_t *thread, void **value_ptr)
+{
+       DWORD result = WaitForSingleObject(thread->handle, INFINITE);
+       switch (result) {
+               case WAIT_OBJECT_0:
+                       if (value_ptr)
+                               *value_ptr = thread->arg;
+                       return 0;
+               case WAIT_ABANDONED:
+                       return EINVAL;
+               default:
+                       return err_win_to_posix(GetLastError());
+       }
+}
+
+int pthread_cond_init(pthread_cond_t *cond, const void *unused)
+{
+       cond->waiters = 0;
+       cond->was_broadcast = 0;
+       InitializeCriticalSection(&cond->waiters_lock);
+
+       cond->sema = CreateSemaphore(NULL, 0, LONG_MAX, NULL);
+       if (!cond->sema)
+               die("CreateSemaphore() failed");
+
+       cond->continue_broadcast = CreateEvent(NULL,    /* security */
+                               FALSE,                  /* auto-reset */
+                               FALSE,                  /* not signaled */
+                               NULL);                  /* name */
+       if (!cond->continue_broadcast)
+               die("CreateEvent() failed");
+
+       return 0;
+}
+
+int pthread_cond_destroy(pthread_cond_t *cond)
+{
+       CloseHandle(cond->sema);
+       CloseHandle(cond->continue_broadcast);
+       DeleteCriticalSection(&cond->waiters_lock);
+       return 0;
+}
+
+int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex)
+{
+       int last_waiter;
+
+       EnterCriticalSection(&cond->waiters_lock);
+       cond->waiters++;
+       LeaveCriticalSection(&cond->waiters_lock);
+
+       /*
+        * Unlock external mutex and wait for signal.
+        * NOTE: we've held mutex locked long enough to increment
+        * waiters count above, so there's no problem with
+        * leaving mutex unlocked before we wait on semaphore.
+        */
+       LeaveCriticalSection(mutex);
+
+       /* let's wait - ignore return value */
+       WaitForSingleObject(cond->sema, INFINITE);
+
+       /*
+        * Decrease waiters count. If we are the last waiter, then we must
+        * notify the broadcasting thread that it can continue.
+        * But if we continued due to cond_signal, we do not have to do that
+        * because the signaling thread knows that only one waiter continued.
+        */
+       EnterCriticalSection(&cond->waiters_lock);
+       cond->waiters--;
+       last_waiter = cond->was_broadcast && cond->waiters == 0;
+       LeaveCriticalSection(&cond->waiters_lock);
+
+       if (last_waiter) {
+               /*
+                * cond_broadcast was issued while mutex was held. This means
+                * that all other waiters have continued, but are contending
+                * for the mutex at the end of this function because the
+                * broadcasting thread did not leave cond_broadcast, yet.
+                * (This is so that it can be sure that each waiter has
+                * consumed exactly one slice of the semaphor.)
+                * The last waiter must tell the broadcasting thread that it
+                * can go on.
+                */
+               SetEvent(cond->continue_broadcast);
+               /*
+                * Now we go on to contend with all other waiters for
+                * the mutex. Auf in den Kampf!
+                */
+       }
+       /* lock external mutex again */
+       EnterCriticalSection(mutex);
+
+       return 0;
+}
+
+/*
+ * IMPORTANT: This implementation requires that pthread_cond_signal
+ * is called while the mutex is held that is used in the corresponding
+ * pthread_cond_wait calls!
+ */
+int pthread_cond_signal(pthread_cond_t *cond)
+{
+       int have_waiters;
+
+       EnterCriticalSection(&cond->waiters_lock);
+       have_waiters = cond->waiters > 0;
+       LeaveCriticalSection(&cond->waiters_lock);
+
+       /*
+        * Signal only when there are waiters
+        */
+       if (have_waiters)
+               return ReleaseSemaphore(cond->sema, 1, NULL) ?
+                       0 : err_win_to_posix(GetLastError());
+       else
+               return 0;
+}
+
+/*
+ * DOUBLY IMPORTANT: This implementation requires that pthread_cond_broadcast
+ * is called while the mutex is held that is used in the corresponding
+ * pthread_cond_wait calls!
+ */
+int pthread_cond_broadcast(pthread_cond_t *cond)
+{
+       EnterCriticalSection(&cond->waiters_lock);
+
+       if ((cond->was_broadcast = cond->waiters > 0)) {
+               /* wake up all waiters */
+               ReleaseSemaphore(cond->sema, cond->waiters, NULL);
+               LeaveCriticalSection(&cond->waiters_lock);
+               /*
+                * At this point all waiters continue. Each one takes its
+                * slice of the semaphor. Now it's our turn to wait: Since
+                * the external mutex is held, no thread can leave cond_wait,
+                * yet. For this reason, we can be sure that no thread gets
+                * a chance to eat *more* than one slice. OTOH, it means
+                * that the last waiter must send us a wake-up.
+                */
+               WaitForSingleObject(cond->continue_broadcast, INFINITE);
+               /*
+                * Since the external mutex is held, no thread can enter
+                * cond_wait, and, hence, it is safe to reset this flag
+                * without cond->waiters_lock held.
+                */
+               cond->was_broadcast = 0;
+       } else {
+               LeaveCriticalSection(&cond->waiters_lock);
+       }
+       return 0;
+}
diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h
new file mode 100644 (file)
index 0000000..c72f100
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Header used to adapt pthread-based POSIX code to Windows API threads.
+ *
+ * Copyright (C) 2009 Andrzej K. Haczewski <ahaczewski@gmail.com>
+ */
+
+#ifndef PTHREAD_H
+#define PTHREAD_H
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <windows.h>
+
+/*
+ * Defines that adapt Windows API threads to pthreads API
+ */
+#define pthread_mutex_t CRITICAL_SECTION
+
+#define pthread_mutex_init(a,b) InitializeCriticalSection((a))
+#define pthread_mutex_destroy(a) DeleteCriticalSection((a))
+#define pthread_mutex_lock EnterCriticalSection
+#define pthread_mutex_unlock LeaveCriticalSection
+
+/*
+ * Implement simple condition variable for Windows threads, based on ACE
+ * implementation.
+ *
+ * See original implementation: http://bit.ly/1vkDjo
+ * ACE homepage: http://www.cse.wustl.edu/~schmidt/ACE.html
+ * See also: http://www.cse.wustl.edu/~schmidt/win32-cv-1.html
+ */
+typedef struct {
+       LONG waiters;
+       int was_broadcast;
+       CRITICAL_SECTION waiters_lock;
+       HANDLE sema;
+       HANDLE continue_broadcast;
+} pthread_cond_t;
+
+extern int pthread_cond_init(pthread_cond_t *cond, const void *unused);
+extern int pthread_cond_destroy(pthread_cond_t *cond);
+extern int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex);
+extern int pthread_cond_signal(pthread_cond_t *cond);
+extern int pthread_cond_broadcast(pthread_cond_t *cond);
+
+/*
+ * Simple thread creation implementation using pthread API
+ */
+typedef struct {
+       HANDLE handle;
+       void *(*start_routine)(void*);
+       void *arg;
+} pthread_t;
+
+extern int pthread_create(pthread_t *thread, const void *unused,
+                         void *(*start_routine)(void*), void *arg);
+
+/*
+ * To avoid the need of copying a struct, we use small macro wrapper to pass
+ * pointer to win32_pthread_join instead.
+ */
+#define pthread_join(a, b) win32_pthread_join(&(a), (b))
+
+extern int win32_pthread_join(pthread_t *thread, void **value_ptr);
+
+#endif /* PTHREAD_H */
index 37385ce9d338ecbd2556cef9455000784ebab7e5..6963fbea43e6420f5f8dafc5b94fb5c27de6ffd2 100644 (file)
--- a/config.c
+++ b/config.c
@@ -518,6 +518,11 @@ static int git_default_core_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.sparsecheckout")) {
+               core_apply_sparse_checkout = git_config_bool(var, value);
+               return 0;
+       }
+
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
 }
@@ -528,8 +533,7 @@ static int git_default_user_config(const char *var, const char *value)
                if (!value)
                        return config_error_nonbool(var);
                strlcpy(git_default_name, value, sizeof(git_default_name));
-               if (git_default_email[0])
-                       user_ident_explicitly_given = 1;
+               user_ident_explicitly_given |= IDENT_NAME_GIVEN;
                return 0;
        }
 
@@ -537,8 +541,7 @@ static int git_default_user_config(const char *var, const char *value)
                if (!value)
                        return config_error_nonbool(var);
                strlcpy(git_default_email, value, sizeof(git_default_email));
-               if (git_default_name[0])
-                       user_ident_explicitly_given = 1;
+               user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
                return 0;
        }
 
index 67b12f73a1bb795207082973701ece8009167f1c..6008ac9f1b8d056e522d5fe83c8d56bff314ca92 100644 (file)
@@ -56,5 +56,4 @@ NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@
 FREAD_READS_DIRECTORIES=@FREAD_READS_DIRECTORIES@
 SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@
 NO_PTHREADS=@NO_PTHREADS@
-THREADED_DELTA_SEARCH=@THREADED_DELTA_SEARCH@
 PTHREAD_LIBS=@PTHREAD_LIBS@
index 4625b8672bf5b0dcc977737c2c5086c5c1aa54a6..914ae5759f6932c28ee39afec1e36d80c8e63cd9 100644 (file)
@@ -23,21 +23,32 @@ AC_DEFUN([GIT_CONF_APPEND_LINE],
 # GIT_ARG_SET_PATH(PROGRAM)
 # -------------------------
 # Provide --with-PROGRAM=PATH option to set PATH to PROGRAM
+# Optional second argument allows setting NO_PROGRAM=YesPlease if
+# --without-PROGRAM version used.
 AC_DEFUN([GIT_ARG_SET_PATH],
 [AC_ARG_WITH([$1],
  [AS_HELP_STRING([--with-$1=PATH],
                  [provide PATH to $1])],
- [GIT_CONF_APPEND_PATH($1)],[])
+ [GIT_CONF_APPEND_PATH($1,$2)],[])
 ])# GIT_ARG_SET_PATH
 #
 # GIT_CONF_APPEND_PATH(PROGRAM)
 # ------------------------------
 # Parse --with-PROGRAM=PATH option to set PROGRAM_PATH=PATH
 # Used by GIT_ARG_SET_PATH(PROGRAM)
+# Optional second argument allows setting NO_PROGRAM=YesPlease if
+# --without-PROGRAM is used.
 AC_DEFUN([GIT_CONF_APPEND_PATH],
 [PROGRAM=m4_toupper($1); \
 if test "$withval" = "no"; then \
-       AC_MSG_ERROR([You cannot use git without $1]); \
+       if test -n "$2"; then \
+               m4_toupper($1)_PATH=$withval; \
+               AC_MSG_NOTICE([Disabling use of ${PROGRAM}]); \
+               GIT_CONF_APPEND_LINE(NO_${PROGRAM}=YesPlease); \
+               GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=); \
+       else \
+               AC_MSG_ERROR([You cannot use git without $1]); \
+       fi; \
 else \
        if test "$withval" = "yes"; then \
                AC_MSG_WARN([You should provide path for --with-$1=PATH]); \
@@ -276,6 +287,9 @@ GIT_ARG_SET_PATH(shell)
 # Define PERL_PATH to provide path to Perl.
 GIT_ARG_SET_PATH(perl)
 #
+# Define PYTHON_PATH to provide path to Python.
+GIT_ARG_SET_PATH(python, allow-without)
+#
 # Define ZLIB_PATH to provide path to zlib.
 GIT_ARG_SET_PATH(zlib)
 #
@@ -759,10 +773,9 @@ AC_SUBST(NO_MKSTEMPS)
 # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
 # Enable it on Windows.  By default, symrefs are still used.
 #
-# Define NO_PTHREADS if we do not have pthreads
+# Define NO_PTHREADS if we do not have pthreads.
 #
-# Define PTHREAD_LIBS to the linker flag used for Pthread support and define
-# THREADED_DELTA_SEARCH if Pthreads are available.
+# Define PTHREAD_LIBS to the linker flag used for Pthread support.
 AC_DEFUN([PTHREADTEST_SRC], [
 #include <pthread.h>
 
@@ -779,7 +792,6 @@ dnl   [[pthread_mutex_t test_mutex;]]
 dnl )])
 
 NO_PTHREADS=UnfortunatelyYes
-THREADED_DELTA_SEARCH=
 PTHREAD_LIBS=
 
 if test -n "$USER_NOPTHREAD"; then
@@ -795,7 +807,6 @@ elif test -z "$PTHREAD_CFLAGS"; then
        [AC_MSG_RESULT([yes])
                NO_PTHREADS=
                PTHREAD_LIBS="$opt"
-               THREADED_DELTA_SEARCH=YesPlease
                break
        ],
        [AC_MSG_RESULT([no])])
@@ -809,7 +820,6 @@ else
        [AC_MSG_RESULT([yes])
                NO_PTHREADS=
                PTHREAD_LIBS="$PTHREAD_CFLAGS"
-               THREADED_DELTA_SEARCH=YesPlease
        ],
        [AC_MSG_RESULT([no])])
 
@@ -820,7 +830,6 @@ CFLAGS="$old_CFLAGS"
 
 AC_SUBST(PTHREAD_LIBS)
 AC_SUBST(NO_PTHREADS)
-AC_SUBST(THREADED_DELTA_SEARCH)
 
 ## Output files
 AC_CONFIG_FILES(["${config_file}":"${config_in}":"${config_append}"])
index db965c9982f1f2ae7e08331d3fa1d40e194a6520..20054e4d0fd4cf94288593726be179d07d19271c 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -502,12 +502,18 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
                c = ':';
        }
 
+       /*
+        * Don't do destructive transforms with git:// as that
+        * protocol code does '[]' dewrapping of its own.
+        */
        if (host[0] == '[') {
                end = strchr(host + 1, ']');
                if (end) {
-                       *end = 0;
+                       if (protocol != PROTO_GIT) {
+                               *end = 0;
+                               host++;
+                       }
                        end++;
-                       host++;
                } else
                        end = host;
        } else
@@ -613,8 +619,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
                        NULL
                };
                conn->env = env;
-               *arg++ = "sh";
-               *arg++ = "-c";
+               conn->use_shell = 1;
        }
        *arg++ = cmd.buf;
        *arg = NULL;
index d506717bfd304a1aaf4a02ad262ee96a0e468771..23da787dc5565193dd93fa7406cd86f39651bb52 100644 (file)
@@ -135,6 +135,11 @@ sub parseMakeOutput
             }
         } while($ate_next);
 
+        if ($text =~ /^test /) {
+            # options to test (eg -o) may be mistaken for linker options
+            next;
+        }
+
         if($text =~ / -c /) {
             # compilation
             handleCompileLine($text, $line);
@@ -180,9 +185,6 @@ sub parseMakeOutput
 #        } elsif ($text =~ /generate-cmdlist\.sh/) {
 #            # command for generating list of commands
 #
-#        } elsif ($text =~ /^test / && $text =~ /|| rm -f /) {
-#            # commands removing executables, if they exist
-#
 #        } elsif ($text =~ /new locations or Tcl/) {
 #            # command for detecting Tcl/Tk changes
 #
@@ -317,6 +319,7 @@ sub handleLinkLine
             push(@libs, "zlib.lib");
        } elsif ("$part" eq "-lcrypto") {
             push(@libs, "libeay32.lib");
+        } elsif ("$part" eq "-lssl") {
             push(@libs, "ssleay32.lib");
         } elsif ($part =~ /^-/) {
             push(@lflags, $part);
index 1a762e88e7fcf3b36b3acdffa3e8ec18e3644e2e..35acad004e041d6c35316373635da45b61f6e8c0 100755 (executable)
@@ -161,11 +161,8 @@ __git_ps1 ()
                        fi
                fi
 
-               if [ -n "${1-}" ]; then
-                       printf "$1" "$c${b##refs/heads/}$w$i$s$u$r"
-               else
-                       printf " (%s)" "$c${b##refs/heads/}$w$i$s$u$r"
-               fi
+               local f="$w$i$s$u"
+               printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r"
        fi
 }
 
@@ -1310,6 +1307,24 @@ _git_name_rev ()
        __gitcomp "--tags --all --stdin"
 }
 
+_git_notes ()
+{
+       local subcommands="edit show"
+       if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
+               __gitcomp "$subcommands"
+               return
+       fi
+
+       case "${COMP_WORDS[COMP_CWORD-1]}" in
+       -m|-F)
+               COMPREPLY=()
+               ;;
+       *)
+               __gitcomp "$(__git_refs)"
+               ;;
+       esac
+}
+
 _git_pull ()
 {
        __git_complete_strategy && return
@@ -1371,6 +1386,7 @@ _git_rebase ()
                        --preserve-merges --stat --no-stat
                        --committer-date-is-author-date --ignore-date
                        --ignore-whitespace --whitespace=
+                       --autosquash
                        "
 
                return
@@ -2021,7 +2037,7 @@ _git_svn ()
                init fetch clone rebase dcommit log find-rev
                set-tree commit-diff info create-ignore propget
                proplist show-ignore show-externals branch tag blame
-               migrate
+               migrate mkdirs reset gc
                "
        local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
@@ -2068,7 +2084,7 @@ _git_svn ()
                        __gitcomp "--stdin $cmt_opts $fc_opts"
                        ;;
                create-ignore,--*|propget,--*|proplist,--*|show-ignore,--*|\
-               show-externals,--*)
+               show-externals,--*|mkdirs,--*)
                        __gitcomp "--revision="
                        ;;
                log,--*)
@@ -2105,6 +2121,9 @@ _git_svn ()
                                --no-auth-cache --username=
                                "
                        ;;
+               reset,--*)
+                       __gitcomp "--revision= --parent"
+                       ;;
                *)
                        COMPREPLY=()
                        ;;
@@ -2219,6 +2238,7 @@ _git ()
        merge-base)  _git_merge_base ;;
        mv)          _git_mv ;;
        name-rev)    _git_name_rev ;;
+       notes)       _git_notes ;;
        pull)        _git_pull ;;
        push)        _git_push ;;
        rebase)      _git_rebase ;;
index 500635fe4bb5a8f60bc76502ef15cd97bd273f74..8f617fcb7089caedce163871c3eb3a97eadb204b 100755 (executable)
@@ -31,10 +31,11 @@ LF='
 '
 
 all_strategies='recur recursive octopus resolve stupid ours subtree'
+all_strategies="$all_strategies recursive-ours recursive-theirs"
 default_twohead_strategies='recursive'
 default_octopus_strategies='octopus'
 no_fast_forward_strategies='subtree ours'
-no_trivial_strategies='recursive recur subtree ours'
+no_trivial_strategies='recursive recur subtree ours recursive-ours recursive-theirs'
 use_strategies=
 
 allow_fast_forward=t
index 48059d0aa74fe5f4c14bd74f599d449335ef8519..e7c48144e62a0829a5f817c5debaee28516b351d 100755 (executable)
@@ -732,7 +732,7 @@ class P4Submit(Command):
             if os.environ.has_key("P4EDITOR"):
                 editor = os.environ.get("P4EDITOR")
             else:
-                editor = read_pipe("git var GIT_EDITOR")
+                editor = read_pipe("git var GIT_EDITOR").strip()
             system(editor + " " + fileName)
 
             response = "y"
@@ -1037,7 +1037,7 @@ class P4Sync(Command):
 
             if includeFile:
                 filesForCommit.append(f)
-                if f['action'] not in ('delete', 'purge'):
+                if f['action'] not in ('delete', 'move/delete', 'purge'):
                     filesToRead.append(f)
                 else:
                     filesToDelete.append(f)
index 2a6839d81ee970cb799859c7a833f8f8cb780864..854cd94ba55e498a3ff6c26be3dbe5191faa19dc 100755 (executable)
@@ -59,14 +59,14 @@ def getgitenv(user, date):
     elems = re.compile('(.*?)\s+<(.*)>').match(user)
     if elems:
         env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1)
-        env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1)
+        env += 'export GIT_COMMITTER_NAME="%s" ;' % elems.group(1)
         env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2)
-        env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2)
+        env += 'export GIT_COMMITTER_EMAIL="%s" ;' % elems.group(2)
     else:
         env += 'export GIT_AUTHOR_NAME="%s" ;' % user
-        env += 'export GIT_COMMITER_NAME="%s" ;' % user
+        env += 'export GIT_COMMITTER_NAME="%s" ;' % user
         env += 'export GIT_AUTHOR_EMAIL= ;'
-        env += 'export GIT_COMMITER_EMAIL= ;'
+        env += 'export GIT_COMMITTER_EMAIL= ;'
 
     env += 'export GIT_AUTHOR_DATE="%s" ;' % date
     env += 'export GIT_COMMITTER_DATE="%s" ;' % date
index 491e7141b4ea29b3cf754cbaf2656a0c3ca8c46c..27acce58bc4bec60a394f03db1f6e60e1e4cfc3e 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -249,10 +249,11 @@ static int filter_buffer(int fd, void *data)
        struct child_process child_process;
        struct filter_params *params = (struct filter_params *)data;
        int write_err, status;
-       const char *argv[] = { "sh", "-c", params->cmd, NULL };
+       const char *argv[] = { params->cmd, NULL };
 
        memset(&child_process, 0, sizeof(child_process));
        child_process.argv = argv;
+       child_process.use_shell = 1;
        child_process.in = -1;
        child_process.out = fd;
 
@@ -377,9 +378,9 @@ static void setup_convert_check(struct git_attr_check *check)
        static struct git_attr *attr_filter;
 
        if (!attr_crlf) {
-               attr_crlf = git_attr("crlf", 4);
-               attr_ident = git_attr("ident", 5);
-               attr_filter = git_attr("filter", 6);
+               attr_crlf = git_attr("crlf");
+               attr_ident = git_attr("ident");
+               attr_filter = git_attr("filter");
                user_convert_tail = &user_convert;
                git_config(read_convert_config, NULL);
        }
index 5783e2401108adb1fef6943ef80bd78dbc76ecad..6c2bd977131752e05d3ac545af0d977d6d7ca672 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -147,7 +147,6 @@ static char *path_ok(char *directory)
                        { "IP", ip_address },
                        { "P", tcp_port },
                        { "D", directory },
-                       { "%", "%" },
                        { NULL }
                };
 
@@ -400,6 +399,33 @@ static char *xstrdup_tolower(const char *str)
        return dup;
 }
 
+static void parse_host_and_port(char *hostport, char **host,
+       char **port)
+{
+       if (*hostport == '[') {
+               char *end;
+
+               end = strchr(hostport, ']');
+               if (!end)
+                       die("Invalid reqeuest ('[' without ']')");
+               *end = '\0';
+               *host = hostport + 1;
+               if (!end[1])
+                       *port = NULL;
+               else if (end[1] == ':')
+                       *port = end + 2;
+               else
+                       die("Garbage after end of host part");
+       } else {
+               *host = hostport;
+               *port = strrchr(hostport, ':');
+               if (*port) {
+                       *port = '\0';
+                       ++*port;
+               }
+       }
+}
+
 /*
  * Read the host as supplied by the client connection.
  */
@@ -416,11 +442,10 @@ static void parse_host_arg(char *extra_args, int buflen)
                        vallen = strlen(val) + 1;
                        if (*val) {
                                /* Split <host>:<port> at colon. */
-                               char *host = val;
-                               char *port = strrchr(host, ':');
+                               char *host;
+                               char *port;
+                               parse_host_and_port(val, &host, &port);
                                if (port) {
-                                       *port = 0;
-                                       port++;
                                        free(tcp_port);
                                        tcp_port = xstrdup(port);
                                }
@@ -562,6 +587,24 @@ static int execute(struct sockaddr *addr)
        return -1;
 }
 
+static int addrcmp(const struct sockaddr_storage *s1,
+    const struct sockaddr_storage *s2)
+{
+       if (s1->ss_family != s2->ss_family)
+               return s1->ss_family - s2->ss_family;
+       if (s1->ss_family == AF_INET)
+               return memcmp(&((struct sockaddr_in *)s1)->sin_addr,
+                   &((struct sockaddr_in *)s2)->sin_addr,
+                   sizeof(struct in_addr));
+#ifndef NO_IPV6
+       if (s1->ss_family == AF_INET6)
+               return memcmp(&((struct sockaddr_in6 *)s1)->sin6_addr,
+                   &((struct sockaddr_in6 *)s2)->sin6_addr,
+                   sizeof(struct in6_addr));
+#endif
+       return 0;
+}
+
 static int max_connections = 32;
 
 static unsigned int live_children;
@@ -576,17 +619,12 @@ static void add_child(pid_t pid, struct sockaddr *addr, int addrlen)
 {
        struct child *newborn, **cradle;
 
-       /*
-        * This must be xcalloc() -- we'll compare the whole sockaddr_storage
-        * but individual address may be shorter.
-        */
        newborn = xcalloc(1, sizeof(*newborn));
        live_children++;
        newborn->pid = pid;
        memcpy(&newborn->address, addr, addrlen);
        for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next)
-               if (!memcmp(&(*cradle)->address, &newborn->address,
-                           sizeof(newborn->address)))
+               if (!addrcmp(&(*cradle)->address, &newborn->address))
                        break;
        newborn->next = *cradle;
        *cradle = newborn;
@@ -619,8 +657,7 @@ static void kill_some_child(void)
                return;
 
        for (; (next = blanket->next); blanket = next)
-               if (!memcmp(&blanket->address, &next->address,
-                           sizeof(next->address))) {
+               if (!addrcmp(&blanket->address, &next->address)) {
                        kill(blanket->pid, SIGTERM);
                        break;
                }
diff --git a/date.c b/date.c
index 5d05ef61cfb140f004702a5ed614afa755c50670..002aa3c8d6d4ff08d8790a155b8979bc117a2b95 100644 (file)
--- a/date.c
+++ b/date.c
@@ -9,7 +9,7 @@
 /*
  * This is like mktime, but without normalization of tm_wday and tm_yday.
  */
-time_t tm_to_time_t(const struct tm *tm)
+static time_t tm_to_time_t(const struct tm *tm)
 {
        static const int mdays[] = {
            0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
@@ -696,6 +696,11 @@ static unsigned long update_tm(struct tm *tm, struct tm *now, unsigned long sec)
        return n;
 }
 
+static void date_now(struct tm *tm, struct tm *now, int *num)
+{
+       update_tm(tm, now, 0);
+}
+
 static void date_yesterday(struct tm *tm, struct tm *now, int *num)
 {
        update_tm(tm, now, 24*60*60);
@@ -770,6 +775,7 @@ static const struct special {
        { "PM", date_pm },
        { "AM", date_am },
        { "never", date_never },
+       { "now", date_now },
        { NULL }
 };
 
@@ -790,7 +796,7 @@ static const struct typelen {
        { NULL }
 };
 
-static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num)
+static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched)
 {
        const struct typelen *tl;
        const struct special *s;
@@ -804,6 +810,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                int match = match_string(date, month_names[i]);
                if (match >= 3) {
                        tm->tm_mon = i;
+                       *touched = 1;
                        return end;
                }
        }
@@ -812,6 +819,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                int len = strlen(s->name);
                if (match_string(date, s->name) == len) {
                        s->fn(tm, now, num);
+                       *touched = 1;
                        return end;
                }
        }
@@ -821,11 +829,14 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                        int len = strlen(number_name[i]);
                        if (match_string(date, number_name[i]) == len) {
                                *num = i;
+                               *touched = 1;
                                return end;
                        }
                }
-               if (match_string(date, "last") == 4)
+               if (match_string(date, "last") == 4) {
                        *num = 1;
+                       *touched = 1;
+               }
                return end;
        }
 
@@ -835,6 +846,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                if (match_string(date, tl->type) >= len-1) {
                        update_tm(tm, now, tl->length * *num);
                        *num = 0;
+                       *touched = 1;
                        return end;
                }
                tl++;
@@ -852,6 +864,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                        diff += 7*n;
 
                        update_tm(tm, now, diff * 24 * 60 * 60);
+                       *touched = 1;
                        return end;
                }
        }
@@ -866,6 +879,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                        tm->tm_year--;
                }
                tm->tm_mon = n;
+               *touched = 1;
                return end;
        }
 
@@ -873,6 +887,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm
                update_tm(tm, now, 0); /* fill in date fields if needed */
                tm->tm_year -= *num;
                *num = 0;
+               *touched = 1;
                return end;
        }
 
@@ -929,9 +944,12 @@ static void pending_number(struct tm *tm, int *num)
        }
 }
 
-static unsigned long approxidate_str(const char *date, const struct timeval *tv)
+static unsigned long approxidate_str(const char *date,
+                                    const struct timeval *tv,
+                                    int *error_ret)
 {
        int number = 0;
+       int touched = 0;
        struct tm tm, now;
        time_t time_sec;
 
@@ -951,33 +969,42 @@ static unsigned long approxidate_str(const char *date, const struct timeval *tv)
                if (isdigit(c)) {
                        pending_number(&tm, &number);
                        date = approxidate_digit(date-1, &tm, &number);
+                       touched = 1;
                        continue;
                }
                if (isalpha(c))
-                       date = approxidate_alpha(date-1, &tm, &now, &number);
+                       date = approxidate_alpha(date-1, &tm, &now, &number, &touched);
        }
        pending_number(&tm, &number);
+       if (!touched)
+               *error_ret = 1;
        return update_tm(&tm, &now, 0);
 }
 
 unsigned long approxidate_relative(const char *date, const struct timeval *tv)
 {
        char buffer[50];
+       int errors = 0;
 
        if (parse_date(date, buffer, sizeof(buffer)) > 0)
                return strtoul(buffer, NULL, 0);
 
-       return approxidate_str(date, tv);
+       return approxidate_str(date, tv, &errors);
 }
 
-unsigned long approxidate(const char *date)
+unsigned long approxidate_careful(const char *date, int *error_ret)
 {
        struct timeval tv;
        char buffer[50];
+       int dummy = 0;
+       if (!error_ret)
+               error_ret = &dummy;
 
-       if (parse_date(date, buffer, sizeof(buffer)) > 0)
+       if (parse_date(date, buffer, sizeof(buffer)) > 0) {
+               *error_ret = 0;
                return strtoul(buffer, NULL, 0);
+       }
 
        gettimeofday(&tv, NULL);
-       return approxidate_str(date, &tv);
+       return approxidate_str(date, &tv, error_ret);
 }
index adf1c5fdee2bc836f03eaa89160573973bb46a67..d7e13cb177a3c345eb076a9ffede87c6e6afa367 100644 (file)
@@ -10,6 +10,7 @@
 #include "cache-tree.h"
 #include "unpack-trees.h"
 #include "refs.h"
+#include "submodule.h"
 
 /*
  * diff-files
@@ -72,8 +73,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                unsigned int oldmode, newmode;
                struct cache_entry *ce = active_cache[i];
                int changed;
+               unsigned dirty_submodule = 0;
 
-               if (DIFF_OPT_TST(&revs->diffopt, QUIET) &&
+               if (DIFF_OPT_TST(&revs->diffopt, QUICK) &&
                        DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
                        break;
 
@@ -159,7 +161,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                                continue;
                }
 
-               if (ce_uptodate(ce))
+               if (ce_uptodate(ce) || ce_skip_worktree(ce))
                        continue;
 
                /* If CE_VALID is set, don't look at workdir for file removal */
@@ -172,10 +174,17 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                        if (silent_on_removed)
                                continue;
                        diff_addremove(&revs->diffopt, '-', ce->ce_mode,
-                                      ce->sha1, ce->name);
+                                      ce->sha1, ce->name, 0);
                        continue;
                }
                changed = ce_match_stat(ce, &st, ce_option);
+               if (S_ISGITLINK(ce->ce_mode)
+                   && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES)
+                   && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH))
+                   && is_submodule_modified(ce->name)) {
+                       changed = 1;
+                       dirty_submodule = 1;
+               }
                if (!changed) {
                        ce_mark_uptodate(ce);
                        if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))
@@ -185,7 +194,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                newmode = ce_mode_from_stat(ce, st.st_mode);
                diff_change(&revs->diffopt, oldmode, newmode,
                            ce->sha1, (changed ? null_sha1 : ce->sha1),
-                           ce->name);
+                           ce->name, 0, dirty_submodule);
 
        }
        diffcore_std(&revs->diffopt);
@@ -201,16 +210,18 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
 static void diff_index_show_file(struct rev_info *revs,
                                 const char *prefix,
                                 struct cache_entry *ce,
-                                const unsigned char *sha1, unsigned int mode)
+                                const unsigned char *sha1, unsigned int mode,
+                                unsigned dirty_submodule)
 {
        diff_addremove(&revs->diffopt, prefix[0], mode,
-                      sha1, ce->name);
+                      sha1, ce->name, dirty_submodule);
 }
 
 static int get_stat_data(struct cache_entry *ce,
                         const unsigned char **sha1p,
                         unsigned int *modep,
-                        int cached, int match_missing)
+                        int cached, int match_missing,
+                        unsigned *dirty_submodule, struct diff_options *diffopt)
 {
        const unsigned char *sha1 = ce->sha1;
        unsigned int mode = ce->ce_mode;
@@ -230,6 +241,13 @@ static int get_stat_data(struct cache_entry *ce,
                        return -1;
                }
                changed = ce_match_stat(ce, &st, 0);
+               if (S_ISGITLINK(ce->ce_mode)
+                   && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES)
+                   && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH))
+                   && is_submodule_modified(ce->name)) {
+                       changed = 1;
+                       *dirty_submodule = 1;
+               }
                if (changed) {
                        mode = ce_mode_from_stat(ce, st.st_mode);
                        sha1 = null_sha1;
@@ -247,15 +265,17 @@ static void show_new_file(struct rev_info *revs,
 {
        const unsigned char *sha1;
        unsigned int mode;
+       unsigned dirty_submodule = 0;
 
        /*
         * New file in the index: it might actually be different in
         * the working copy.
         */
-       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0)
+       if (get_stat_data(new, &sha1, &mode, cached, match_missing,
+           &dirty_submodule, &revs->diffopt) < 0)
                return;
 
-       diff_index_show_file(revs, "+", new, sha1, mode);
+       diff_index_show_file(revs, "+", new, sha1, mode, dirty_submodule);
 }
 
 static int show_modified(struct rev_info *revs,
@@ -266,11 +286,13 @@ static int show_modified(struct rev_info *revs,
 {
        unsigned int mode, oldmode;
        const unsigned char *sha1;
+       unsigned dirty_submodule = 0;
 
-       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
+       if (get_stat_data(new, &sha1, &mode, cached, match_missing,
+                         &dirty_submodule, &revs->diffopt) < 0) {
                if (report_missing)
                        diff_index_show_file(revs, "-", old,
-                                            old->sha1, old->ce_mode);
+                                            old->sha1, old->ce_mode, 0);
                return -1;
        }
 
@@ -305,7 +327,7 @@ static int show_modified(struct rev_info *revs,
                return 0;
 
        diff_change(&revs->diffopt, oldmode, mode,
-                   old->sha1, sha1, old->name);
+                   old->sha1, sha1, old->name, 0, dirty_submodule);
        return 0;
 }
 
@@ -323,7 +345,8 @@ static void do_oneway_diff(struct unpack_trees_options *o,
        int match_missing, cached;
 
        /* if the entry is not checked out, don't examine work tree */
-       cached = o->index_only || (idx && (idx->ce_flags & CE_VALID));
+       cached = o->index_only ||
+               (idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx)));
        /*
         * Backward compatibility wart - "diff-index -m" does
         * not mean "do not ignore merges", but "match_missing".
@@ -351,7 +374,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,
         * Something removed from the tree?
         */
        if (!idx) {
-               diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode);
+               diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode, 0);
                return;
        }
 
@@ -359,21 +382,6 @@ static void do_oneway_diff(struct unpack_trees_options *o,
        show_modified(revs, tree, idx, 1, cached, match_missing);
 }
 
-static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o)
-{
-       int len = ce_namelen(ce);
-       const struct index_state *index = o->src_index;
-
-       while (o->pos < index->cache_nr) {
-               struct cache_entry *next = index->cache[o->pos];
-               if (len != ce_namelen(next))
-                       break;
-               if (memcmp(ce->name, next->name, len))
-                       break;
-               o->pos++;
-       }
-}
-
 /*
  * The unpack_trees() interface is designed for merging, so
  * the different source entries are designed primarily for
@@ -395,9 +403,6 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
        struct cache_entry *tree = src[1];
        struct rev_info *revs = o->unpack_data;
 
-       if (idx && ce_stage(idx))
-               skip_same_name(idx, o);
-
        /*
         * Unpack-trees generates a DF/conflict entry if
         * there was a directory in the index and a tree
@@ -443,6 +448,7 @@ int run_diff_index(struct rev_info *revs, int cached)
                exit(128);
 
        diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
+       diffcore_fix_diff_index(&revs->diffopt);
        diffcore_std(&revs->diffopt);
        diff_flush(&revs->diffopt);
        return 0;
@@ -507,7 +513,7 @@ int index_differs_from(const char *def, int diff_flags)
 
        init_revisions(&rev, NULL);
        setup_revisions(0, NULL, &rev, def);
-       DIFF_OPT_SET(&rev.diffopt, QUIET);
+       DIFF_OPT_SET(&rev.diffopt, QUICK);
        DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
        rev.diffopt.flags |= diff_flags;
        run_diff_index(&rev, 1);
diff --git a/diff.c b/diff.c
index 6da52e0c49bc5646594ab47cba3971fd156e021d..381cc8d4fd69ca31fb8fc8af31422160e3ec1fd3 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -194,6 +194,7 @@ struct emit_callback {
        struct diff_words_data *diff_words;
        int *found_changesp;
        FILE *file;
+       struct strbuf *header;
 };
 
 static int count_lines(const char *data, int size)
@@ -797,6 +798,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
        const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
 
+       if (ecbdata->header) {
+               fprintf(ecbdata->file, "%s", ecbdata->header->buf);
+               strbuf_reset(ecbdata->header);
+               ecbdata->header = NULL;
+       }
        *(ecbdata->found_changesp) = 1;
 
        if (ecbdata->label_path[0]) {
@@ -1601,6 +1607,7 @@ static void builtin_diff(const char *name_a,
        const char *reset = diff_get_color_opt(o, DIFF_RESET);
        const char *a_prefix, *b_prefix;
        const char *textconv_one = NULL, *textconv_two = NULL;
+       struct strbuf header = STRBUF_INIT;
 
        if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
                        (!one->mode || S_ISGITLINK(one->mode)) &&
@@ -1608,7 +1615,7 @@ static void builtin_diff(const char *name_a,
                const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
                show_submodule_summary(o->file, one ? one->path : two->path,
-                               one->sha1, two->sha1,
+                               one->sha1, two->sha1, two->dirty_submodule,
                                del, add, reset);
                return;
        }
@@ -1635,25 +1642,26 @@ static void builtin_diff(const char *name_a,
        b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
        lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
        lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
-       fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
+       strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
        if (lbl[0][0] == '/') {
                /* /dev/null */
-               fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset);
+               strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset);
                if (xfrm_msg && xfrm_msg[0])
-                       fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+                       strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
        }
        else if (lbl[1][0] == '/') {
-               fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
+               strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
                if (xfrm_msg && xfrm_msg[0])
-                       fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+                       strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
        }
        else {
                if (one->mode != two->mode) {
-                       fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset);
-                       fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset);
+                       strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset);
+                       strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset);
                }
                if (xfrm_msg && xfrm_msg[0])
-                       fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+                       strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
+
                /*
                 * we do not run diff between different kind
                 * of objects.
@@ -1663,6 +1671,8 @@ static void builtin_diff(const char *name_a,
                if (complete_rewrite &&
                    (textconv_one || !diff_filespec_is_binary(one)) &&
                    (textconv_two || !diff_filespec_is_binary(two))) {
+                       fprintf(o->file, "%s", header.buf);
+                       strbuf_reset(&header);
                        emit_rewrite_diff(name_a, name_b, one, two,
                                                textconv_one, textconv_two, o);
                        o->found_changes = 1;
@@ -1680,6 +1690,8 @@ static void builtin_diff(const char *name_a,
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size))
                        goto free_ab_and_return;
+               fprintf(o->file, "%s", header.buf);
+               strbuf_reset(&header);
                if (DIFF_OPT_TST(o, BINARY))
                        emit_binary_diff(o->file, &mf1, &mf2);
                else
@@ -1696,6 +1708,11 @@ static void builtin_diff(const char *name_a,
                struct emit_callback ecbdata;
                const struct userdiff_funcname *pe;
 
+               if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) {
+                       fprintf(o->file, "%s", header.buf);
+                       strbuf_reset(&header);
+               }
+
                if (textconv_one) {
                        size_t size;
                        mf1.ptr = run_textconv(textconv_one, one, &size);
@@ -1725,6 +1742,7 @@ static void builtin_diff(const char *name_a,
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.file = o->file;
+               ecbdata.header = header.len ? &header : NULL;
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
@@ -1769,6 +1787,7 @@ static void builtin_diff(const char *name_a,
        }
 
  free_ab_and_return:
+       strbuf_release(&header);
        diff_free_filespec_data(one);
        diff_free_filespec_data(two);
        free(a_one);
@@ -1978,7 +1997,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
         * If ce is marked as "assume unchanged", there is no
         * guarantee that work tree matches what we are looking for.
         */
-       if (ce->ce_flags & CE_VALID)
+       if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))
                return 0;
 
        /*
@@ -2010,9 +2029,14 @@ static int populate_from_stdin(struct diff_filespec *s)
 static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 {
        int len;
-       char *data = xmalloc(100);
+       char *data = xmalloc(100), *dirty = "";
+
+       /* Are we looking at the work tree? */
+       if (!s->sha1_valid && s->dirty_submodule)
+               dirty = "-dirty";
+
        len = snprintf(data, 100,
-               "Subproject commit %s\n", sha1_to_hex(s->sha1));
+                      "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty);
        s->data = data;
        s->size = len;
        s->should_free = 1;
@@ -2275,7 +2299,7 @@ static void run_external_diff(const char *pgm,
        }
        *arg = NULL;
        fflush(NULL);
-       retval = run_command_v_opt(spawn_arg, 0);
+       retval = run_command_v_opt(spawn_arg, RUN_USING_SHELL);
        remove_tempfile();
        if (retval) {
                fprintf(stderr, "external diff died, stopping at %s.\n", name);
@@ -2551,6 +2575,20 @@ int diff_setup_done(struct diff_options *options)
        if (count > 1)
                die("--name-only, --name-status, --check and -s are mutually exclusive");
 
+       /*
+        * Most of the time we can say "there are changes"
+        * only by checking if there are changed paths, but
+        * --ignore-whitespace* options force us to look
+        * inside contents.
+        */
+
+       if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
+           DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
+           DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
+               DIFF_OPT_SET(options, DIFF_FROM_CONTENTS);
+       else
+               DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS);
+
        if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
                options->detect_rename = DIFF_DETECT_COPY;
 
@@ -2611,7 +2649,7 @@ int diff_setup_done(struct diff_options *options)
         * to have found.  It does not make sense not to return with
         * exit code in such a case either.
         */
-       if (DIFF_OPT_TST(options, QUIET)) {
+       if (DIFF_OPT_TST(options, QUICK)) {
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        }
@@ -2802,7 +2840,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        else if (!strcmp(arg, "--quiet"))
-               DIFF_OPT_SET(options, QUIET);
+               DIFF_OPT_SET(options, QUICK);
        else if (!strcmp(arg, "--ext-diff"))
                DIFF_OPT_SET(options, ALLOW_EXTERNAL);
        else if (!strcmp(arg, "--no-ext-diff"))
@@ -3509,6 +3547,18 @@ free_queue:
        q->nr = q->alloc = 0;
        if (options->close_file)
                fclose(options->file);
+
+       /*
+        * Report the content-level differences with HAS_CHANGES;
+        * diff_addremove/diff_change does not set the bit when
+        * DIFF_FROM_CONTENTS is in effect (e.g. with -w).
+        */
+       if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
+               if (options->found_changes)
+                       DIFF_OPT_SET(options, HAS_CHANGES);
+               else
+                       DIFF_OPT_CLR(options, HAS_CHANGES);
+       }
 }
 
 static void diffcore_apply_filter(const char *filter)
@@ -3628,6 +3678,23 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
        *q = outq;
 }
 
+static int diffnamecmp(const void *a_, const void *b_)
+{
+       const struct diff_filepair *a = *((const struct diff_filepair **)a_);
+       const struct diff_filepair *b = *((const struct diff_filepair **)b_);
+       const char *name_a, *name_b;
+
+       name_a = a->one ? a->one->path : a->two->path;
+       name_b = b->one ? b->one->path : b->two->path;
+       return strcmp(name_a, name_b);
+}
+
+void diffcore_fix_diff_index(struct diff_options *options)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp);
+}
+
 void diffcore_std(struct diff_options *options)
 {
        if (options->skip_stat_unmatch)
@@ -3645,7 +3712,7 @@ void diffcore_std(struct diff_options *options)
        diff_resolve_rename_copy();
        diffcore_apply_filter(options->filter);
 
-       if (diff_queued_diff.nr)
+       if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
                DIFF_OPT_SET(options, HAS_CHANGES);
        else
                DIFF_OPT_CLR(options, HAS_CHANGES);
@@ -3669,7 +3736,7 @@ int diff_result_code(struct diff_options *opt, int status)
 void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *concatpath)
+                   const char *concatpath, unsigned dirty_submodule)
 {
        struct diff_filespec *one, *two;
 
@@ -3701,18 +3768,22 @@ void diff_addremove(struct diff_options *options,
 
        if (addremove != '+')
                fill_filespec(one, sha1, mode);
-       if (addremove != '-')
+       if (addremove != '-') {
                fill_filespec(two, sha1, mode);
+               two->dirty_submodule = dirty_submodule;
+       }
 
        diff_queue(&diff_queued_diff, one, two);
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
 void diff_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
-                const char *concatpath)
+                const char *concatpath,
+                unsigned old_dirty_submodule, unsigned new_dirty_submodule)
 {
        struct diff_filespec *one, *two;
 
@@ -3725,6 +3796,8 @@ void diff_change(struct diff_options *options,
                const unsigned char *tmp_c;
                tmp = old_mode; old_mode = new_mode; new_mode = tmp;
                tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
+               tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule;
+                       new_dirty_submodule = tmp;
        }
 
        if (options->prefix &&
@@ -3735,9 +3808,12 @@ void diff_change(struct diff_options *options,
        two = alloc_filespec(concatpath);
        fill_filespec(one, old_sha1, old_mode);
        fill_filespec(two, new_sha1, new_mode);
+       one->dirty_submodule = old_dirty_submodule;
+       two->dirty_submodule = new_dirty_submodule;
 
        diff_queue(&diff_queued_diff, one, two);
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
 void diff_unmerge(struct diff_options *options,
@@ -3771,6 +3847,7 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        *arg = NULL;
 
        memset(&child, 0, sizeof(child));
+       child.use_shell = 1;
        child.argv = argv;
        child.out = -1;
        if (start_command(&child) != 0 ||
diff --git a/diff.h b/diff.h
index 15fcecdecd9b033700902de44138eafdb66932eb..2ef3341fb0852fc8958fa5c5eacab69ee68c0ad9 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -14,12 +14,13 @@ typedef void (*change_fn_t)(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
-                const char *fullpath);
+                const char *fullpath,
+                unsigned old_dirty_submodule, unsigned new_dirty_submodule);
 
 typedef void (*add_remove_fn_t)(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *fullpath);
+                   const char *fullpath, unsigned dirty_submodule);
 
 typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
                struct diff_options *options, void *data);
@@ -55,7 +56,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_OPT_COLOR_DIFF          (1 <<  8)
 #define DIFF_OPT_COLOR_DIFF_WORDS    (1 <<  9)
 #define DIFF_OPT_HAS_CHANGES         (1 << 10)
-#define DIFF_OPT_QUIET               (1 << 11)
+#define DIFF_OPT_QUICK               (1 << 11)
 #define DIFF_OPT_NO_INDEX            (1 << 12)
 #define DIFF_OPT_ALLOW_EXTERNAL      (1 << 13)
 #define DIFF_OPT_EXIT_WITH_STATUS    (1 << 14)
@@ -66,7 +67,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_OPT_DIRSTAT_CUMULATIVE  (1 << 19)
 #define DIFF_OPT_DIRSTAT_BY_FILE     (1 << 20)
 #define DIFF_OPT_ALLOW_TEXTCONV      (1 << 21)
-
+#define DIFF_OPT_DIFF_FROM_CONTENTS  (1 << 22)
 #define DIFF_OPT_SUBMODULE_LOG       (1 << 23)
 
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
@@ -177,13 +178,14 @@ extern void diff_addremove(struct diff_options *,
                           int addremove,
                           unsigned mode,
                           const unsigned char *sha1,
-                          const char *fullpath);
+                          const char *fullpath, unsigned dirty_submodule);
 
 extern void diff_change(struct diff_options *,
                        unsigned mode1, unsigned mode2,
                        const unsigned char *sha1,
                        const unsigned char *sha2,
-                       const char *fullpath);
+                       const char *fullpath,
+                       unsigned dirty_submodule1, unsigned dirty_submodule2);
 
 extern void diff_unmerge(struct diff_options *,
                         const char *path,
@@ -208,6 +210,7 @@ extern int diff_setup_done(struct diff_options *);
 #define DIFF_PICKAXE_REGEX     2
 
 extern void diffcore_std(struct diff_options *);
+extern void diffcore_fix_diff_index(struct diff_options *);
 
 #define COMMON_DIFF_OPTIONS_HELP \
 "\ncommon diff options:\n" \
index 5b634585e8970de81067b9807d8813c083b86b0a..66687c3fe5ea4552cff2b864b73696460ca40b1e 100644 (file)
@@ -42,6 +42,7 @@ struct diff_filespec {
 #define DIFF_FILE_VALID(spec) (((spec)->mode) != 0)
        unsigned should_free : 1; /* data should be free()'ed */
        unsigned should_munmap : 1; /* data should be munmap()'ed */
+       unsigned dirty_submodule : 1;  /* For submodules: its work tree is dirty */
 
        struct userdiff_driver *driver;
        /* data should be considered "binary"; -1 means "don't know yet" */
diff --git a/dir.c b/dir.c
index d0999ba055367c31571b251fb34bb46ed6c7051d..67c3af6a1a91e2acaa873587d6df5318d2fb9ba8 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -200,11 +200,35 @@ void add_exclude(const char *string, const char *base,
        which->excludes[which->nr++] = x;
 }
 
-static int add_excludes_from_file_1(const char *fname,
-                                   const char *base,
-                                   int baselen,
-                                   char **buf_p,
-                                   struct exclude_list *which)
+static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
+{
+       int pos, len;
+       unsigned long sz;
+       enum object_type type;
+       void *data;
+       struct index_state *istate = &the_index;
+
+       len = strlen(path);
+       pos = index_name_pos(istate, path, len);
+       if (pos < 0)
+               return NULL;
+       if (!ce_skip_worktree(istate->cache[pos]))
+               return NULL;
+       data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
+       if (!data || type != OBJ_BLOB) {
+               free(data);
+               return NULL;
+       }
+       *size = xsize_t(sz);
+       return data;
+}
+
+int add_excludes_from_file_to_list(const char *fname,
+                                  const char *base,
+                                  int baselen,
+                                  char **buf_p,
+                                  struct exclude_list *which,
+                                  int check_index)
 {
        struct stat st;
        int fd, i;
@@ -212,24 +236,39 @@ static int add_excludes_from_file_1(const char *fname,
        char *buf, *entry;
 
        fd = open(fname, O_RDONLY);
-       if (fd < 0 || fstat(fd, &st) < 0)
-               goto err;
-       size = xsize_t(st.st_size);
-       if (size == 0) {
-               close(fd);
-               return 0;
+       if (fd < 0 || fstat(fd, &st) < 0) {
+               if (0 <= fd)
+                       close(fd);
+               if (!check_index ||
+                   (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL)
+                       return -1;
+               if (size == 0) {
+                       free(buf);
+                       return 0;
+               }
+               if (buf[size-1] != '\n') {
+                       buf = xrealloc(buf, size+1);
+                       buf[size++] = '\n';
+               }
        }
-       buf = xmalloc(size+1);
-       if (read_in_full(fd, buf, size) != size)
-       {
-               free(buf);
-               goto err;
+       else {
+               size = xsize_t(st.st_size);
+               if (size == 0) {
+                       close(fd);
+                       return 0;
+               }
+               buf = xmalloc(size+1);
+               if (read_in_full(fd, buf, size) != size) {
+                       free(buf);
+                       close(fd);
+                       return -1;
+               }
+               buf[size++] = '\n';
+               close(fd);
        }
-       close(fd);
 
        if (buf_p)
                *buf_p = buf;
-       buf[size++] = '\n';
        entry = buf;
        for (i = 0; i < size; i++) {
                if (buf[i] == '\n') {
@@ -241,17 +280,12 @@ static int add_excludes_from_file_1(const char *fname,
                }
        }
        return 0;
-
- err:
-       if (0 <= fd)
-               close(fd);
-       return -1;
 }
 
 void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 {
-       if (add_excludes_from_file_1(fname, "", 0, NULL,
-                                    &dir->exclude_list[EXC_FILE]) < 0)
+       if (add_excludes_from_file_to_list(fname, "", 0, NULL,
+                                          &dir->exclude_list[EXC_FILE], 0) < 0)
                die("cannot use %s as an exclude file", fname);
 }
 
@@ -300,9 +334,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
                memcpy(dir->basebuf + current, base + current,
                       stk->baselen - current);
                strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
-               add_excludes_from_file_1(dir->basebuf,
-                                        dir->basebuf, stk->baselen,
-                                        &stk->filebuf, el);
+               add_excludes_from_file_to_list(dir->basebuf,
+                                              dir->basebuf, stk->baselen,
+                                              &stk->filebuf, el, 1);
                dir->exclude_stack = stk;
                current = stk->baselen;
        }
@@ -312,9 +346,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 /* Scan the list and let the last match determine the fate.
  * Return 1 for exclude, 0 for include and -1 for undecided.
  */
-static int excluded_1(const char *pathname,
-                     int pathlen, const char *basename, int *dtype,
-                     struct exclude_list *el)
+int excluded_from_list(const char *pathname,
+                      int pathlen, const char *basename, int *dtype,
+                      struct exclude_list *el)
 {
        int i;
 
@@ -325,6 +359,12 @@ static int excluded_1(const char *pathname,
                        int to_exclude = x->to_exclude;
 
                        if (x->flags & EXC_FLAG_MUSTBEDIR) {
+                               if (!dtype) {
+                                       if (!prefixcmp(pathname, exclude))
+                                               return to_exclude;
+                                       else
+                                               continue;
+                               }
                                if (*dtype == DT_UNKNOWN)
                                        *dtype = get_dtype(NULL, pathname, pathlen);
                                if (*dtype != DT_DIR)
@@ -382,8 +422,8 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
 
        prep_exclude(dir, pathname, basename-pathname);
        for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-               switch (excluded_1(pathname, pathlen, basename,
-                                  dtype_p, &dir->exclude_list[st])) {
+               switch (excluded_from_list(pathname, pathlen, basename,
+                                          dtype_p, &dir->exclude_list[st])) {
                case 0:
                        return 0;
                case 1:
@@ -625,6 +665,92 @@ static int get_dtype(struct dirent *de, const char *path, int len)
        return dtype;
 }
 
+enum path_treatment {
+       path_ignored,
+       path_handled,
+       path_recurse,
+};
+
+static enum path_treatment treat_one_path(struct dir_struct *dir,
+                                         char *path, int *len,
+                                         const struct path_simplify *simplify,
+                                         int dtype, struct dirent *de)
+{
+       int exclude = excluded(dir, path, &dtype);
+       if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
+           && in_pathspec(path, *len, simplify))
+               dir_add_ignored(dir, path, *len);
+
+       /*
+        * Excluded? If we don't explicitly want to show
+        * ignored files, ignore it
+        */
+       if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
+               return path_ignored;
+
+       if (dtype == DT_UNKNOWN)
+               dtype = get_dtype(de, path, *len);
+
+       /*
+        * Do we want to see just the ignored files?
+        * We still need to recurse into directories,
+        * even if we don't ignore them, since the
+        * directory may contain files that we do..
+        */
+       if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
+               if (dtype != DT_DIR)
+                       return path_ignored;
+       }
+
+       switch (dtype) {
+       default:
+               return path_ignored;
+       case DT_DIR:
+               memcpy(path + *len, "/", 2);
+               (*len)++;
+               switch (treat_directory(dir, path, *len, simplify)) {
+               case show_directory:
+                       if (exclude != !!(dir->flags
+                                         & DIR_SHOW_IGNORED))
+                               return path_ignored;
+                       break;
+               case recurse_into_directory:
+                       return path_recurse;
+               case ignore_directory:
+                       return path_ignored;
+               }
+               break;
+       case DT_REG:
+       case DT_LNK:
+               break;
+       }
+       return path_handled;
+}
+
+static enum path_treatment treat_path(struct dir_struct *dir,
+                                     struct dirent *de,
+                                     char *path, int path_max,
+                                     int baselen,
+                                     const struct path_simplify *simplify,
+                                     int *len)
+{
+       int dtype;
+
+       if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
+               return path_ignored;
+       *len = strlen(de->d_name);
+       /* Ignore overly long pathnames! */
+       if (*len + baselen + 8 > path_max)
+               return path_ignored;
+       memcpy(path + baselen, de->d_name, *len + 1);
+       *len += baselen;
+       if (simplify_away(path, *len, simplify))
+               return path_ignored;
+
+       dtype = DTYPE(de);
+       return treat_one_path(dir, path, len, simplify, dtype, de);
+}
+
 /*
  * Read a directory tree. We currently ignore anything but
  * directories, regular files and symlinks. That's because git
@@ -634,7 +760,10 @@ static int get_dtype(struct dirent *de, const char *path, int len)
  * Also, we ignore the name ".git" (even if it is not a directory).
  * That likely will not change.
  */
-static int read_directory_recursive(struct dir_struct *dir, const char *base, int baselen, int check_only, const struct path_simplify *simplify)
+static int read_directory_recursive(struct dir_struct *dir,
+                                   const char *base, int baselen,
+                                   int check_only,
+                                   const struct path_simplify *simplify)
 {
        DIR *fdir = opendir(*base ? base : ".");
        int contents = 0;
@@ -645,70 +774,16 @@ static int read_directory_recursive(struct dir_struct *dir, const char *base, in
                memcpy(path, base, baselen);
 
                while ((de = readdir(fdir)) != NULL) {
-                       int len, dtype;
-                       int exclude;
-
-                       if (is_dot_or_dotdot(de->d_name) ||
-                            !strcmp(de->d_name, ".git"))
-                               continue;
-                       len = strlen(de->d_name);
-                       /* Ignore overly long pathnames! */
-                       if (len + baselen + 8 > sizeof(path))
+                       int len;
+                       switch (treat_path(dir, de, path, sizeof(path),
+                                          baselen, simplify, &len)) {
+                       case path_recurse:
+                               contents += read_directory_recursive
+                                       (dir, path, len, 0, simplify);
                                continue;
-                       memcpy(path + baselen, de->d_name, len+1);
-                       len = baselen + len;
-                       if (simplify_away(path, len, simplify))
+                       case path_ignored:
                                continue;
-
-                       dtype = DTYPE(de);
-                       exclude = excluded(dir, path, &dtype);
-                       if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
-                           && in_pathspec(path, len, simplify))
-                               dir_add_ignored(dir, path,len);
-
-                       /*
-                        * Excluded? If we don't explicitly want to show
-                        * ignored files, ignore it
-                        */
-                       if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
-                               continue;
-
-                       if (dtype == DT_UNKNOWN)
-                               dtype = get_dtype(de, path, len);
-
-                       /*
-                        * Do we want to see just the ignored files?
-                        * We still need to recurse into directories,
-                        * even if we don't ignore them, since the
-                        * directory may contain files that we do..
-                        */
-                       if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
-                               if (dtype != DT_DIR)
-                                       continue;
-                       }
-
-                       switch (dtype) {
-                       default:
-                               continue;
-                       case DT_DIR:
-                               memcpy(path + len, "/", 2);
-                               len++;
-                               switch (treat_directory(dir, path, len, simplify)) {
-                               case show_directory:
-                                       if (exclude != !!(dir->flags
-                                                       & DIR_SHOW_IGNORED))
-                                               continue;
-                                       break;
-                               case recurse_into_directory:
-                                       contents += read_directory_recursive(dir,
-                                               path, len, 0, simplify);
-                                       continue;
-                               case ignore_directory:
-                                       continue;
-                               }
-                               break;
-                       case DT_REG:
-                       case DT_LNK:
+                       case path_handled:
                                break;
                        }
                        contents++;
@@ -778,6 +853,41 @@ static void free_simplify(struct path_simplify *simplify)
        free(simplify);
 }
 
+static int treat_leading_path(struct dir_struct *dir,
+                             const char *path, int len,
+                             const struct path_simplify *simplify)
+{
+       char pathbuf[PATH_MAX];
+       int baselen, blen;
+       const char *cp;
+
+       while (len && path[len - 1] == '/')
+               len--;
+       if (!len)
+               return 1;
+       baselen = 0;
+       while (1) {
+               cp = path + baselen + !!baselen;
+               cp = memchr(cp, '/', path + len - cp);
+               if (!cp)
+                       baselen = len;
+               else
+                       baselen = cp - path;
+               memcpy(pathbuf, path, baselen);
+               pathbuf[baselen] = '\0';
+               if (!is_directory(pathbuf))
+                       return 0;
+               if (simplify_away(pathbuf, baselen, simplify))
+                       return 0;
+               blen = baselen;
+               if (treat_one_path(dir, pathbuf, &blen, simplify,
+                                  DT_DIR, NULL) == path_ignored)
+                       return 0; /* do not recurse into it */
+               if (len <= baselen)
+                       return 1; /* finished checking */
+       }
+}
+
 int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec)
 {
        struct path_simplify *simplify;
@@ -786,7 +896,8 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const char
                return dir->nr;
 
        simplify = create_simplify(pathspec);
-       read_directory_recursive(dir, path, len, 0, simplify);
+       if (!len || treat_leading_path(dir, path, len, simplify))
+               read_directory_recursive(dir, path, len, 0, simplify);
        free_simplify(simplify);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
        qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
diff --git a/dir.h b/dir.h
index 320b6a2f38b9289f910141148b5dddb4cb80815f..3bead5f0e25a864c34f515b7bff4c89b55ce84fe 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -69,7 +69,11 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen,
 extern int fill_directory(struct dir_struct *dir, const char **pathspec);
 extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
 
+extern int excluded_from_list(const char *pathname, int pathlen, const char *basename,
+                             int *dtype, struct exclude_list *el);
 extern int excluded(struct dir_struct *, const char *, int *);
+extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
+                                         char **buf_p, struct exclude_list *which, int check_index);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
 extern void add_exclude(const char *string, const char *base,
                        int baselen, struct exclude_list *which);
index 615f5754d66ba7ea611a4837b1ff802f9f9de598..d8340031d24828483697c10257806efedfe041f5 100644 (file)
--- a/editor.c
+++ b/editor.c
@@ -36,26 +36,9 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
                return error("Terminal is dumb, but EDITOR unset");
 
        if (strcmp(editor, ":")) {
-               size_t len = strlen(editor);
-               int i = 0;
-               int failed;
-               const char *args[6];
-               struct strbuf arg0 = STRBUF_INIT;
+               const char *args[] = { editor, path, NULL };
 
-               if (strcspn(editor, "|&;<>()$`\\\"' \t\n*?[#~=%") != len) {
-                       /* there are specials */
-                       strbuf_addf(&arg0, "%s \"$@\"", editor);
-                       args[i++] = "sh";
-                       args[i++] = "-c";
-                       args[i++] = arg0.buf;
-               }
-               args[i++] = editor;
-               args[i++] = path;
-               args[i] = NULL;
-
-               failed = run_command_v_opt_cd_env(args, 0, NULL, env);
-               strbuf_release(&arg0);
-               if (failed)
+               if (run_command_v_opt_cd_env(args, RUN_USING_SHELL, NULL, env))
                        return error("There was a problem with the editor '%s'.",
                                        editor);
        }
diff --git a/entry.c b/entry.c
index 06d24f14c6ba9401637aebfb11659ae747796c06..004182c99d27a6a5825d2429f9104fc7a8f1dc80 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -179,7 +179,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
  * This is like 'lstat()', except it refuses to follow symlinks
  * in the path, after skipping "skiplen".
  */
-int check_path(const char *path, int len, struct stat *st, int skiplen)
+static int check_path(const char *path, int len, struct stat *st, int skiplen)
 {
        const char *slash = path + len;
 
@@ -206,7 +206,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
        len += ce_namelen(ce);
 
        if (!check_path(path, len, &st, state->base_dir_len)) {
-               unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
+               unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
                if (!changed)
                        return 0;
                if (!state->force) {
index 5171d9f9a4a30054983638e9e19e61f422724373..739ec2704031f0dd9d4a380e07f001c09742a7be 100644 (file)
@@ -51,6 +51,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING;
 enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
 char *notes_ref_name;
 int grafts_replace_parents = 1;
+int core_apply_sparse_checkout;
 
 /* Parallel index stat data preload? */
 int core_preload_index = 0;
index 60d0aa2bb45490b90b1d68b26bb7ae5f7d035541..b477dc6a8f8104cecf21b68375b7c5cf7ab8842c 100644 (file)
@@ -245,6 +245,7 @@ struct branch
        const char *name;
        struct tree_entry branch_tree;
        uintmax_t last_commit;
+       uintmax_t num_notes;
        unsigned active : 1;
        unsigned pack_id : PACK_ID_BITS;
        unsigned char sha1[20];
@@ -280,6 +281,7 @@ struct recent_command
 /* Configured limits on output */
 static unsigned long max_depth = 10;
 static off_t max_packsize = (1LL << 32) - 1;
+static uintmax_t big_file_threshold = 512 * 1024 * 1024;
 static int force_update;
 static int pack_compression_level = Z_DEFAULT_COMPRESSION;
 static int pack_compression_seen;
@@ -295,6 +297,9 @@ static unsigned long branch_count;
 static unsigned long branch_load_count;
 static int failure;
 static FILE *pack_edges;
+static unsigned int show_stats = 1;
+static int global_argc;
+static const char **global_argv;
 
 /* Memory pools */
 static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool);
@@ -317,7 +322,10 @@ static unsigned int object_entry_alloc = 5000;
 static struct object_entry_pool *blocks;
 static struct object_entry *object_table[1 << 16];
 static struct mark_set *marks;
-static const char *mark_file;
+static const char *export_marks_file;
+static const char *import_marks_file;
+static int import_marks_file_from_stream;
+static int relative_marks_paths;
 
 /* Our last blob */
 static struct last_object last_blob = { STRBUF_INIT, 0, 0, 0 };
@@ -351,6 +359,9 @@ static struct recent_command *rc_free;
 static unsigned int cmd_save = 100;
 static uintmax_t next_mark;
 static struct strbuf new_data = STRBUF_INIT;
+static int seen_data_command;
+
+static void parse_argv(void);
 
 static void write_branch_report(FILE *rpt, struct branch *b)
 {
@@ -454,8 +465,8 @@ static void write_crash_report(const char *err)
        fputc('\n', rpt);
        fputs("Marks\n", rpt);
        fputs("-----\n", rpt);
-       if (mark_file)
-               fprintf(rpt, "  exported to %s\n", mark_file);
+       if (export_marks_file)
+               fprintf(rpt, "  exported to %s\n", export_marks_file);
        else
                dump_marks_helper(rpt, 0, marks);
 
@@ -693,6 +704,7 @@ static struct branch *new_branch(const char *name)
        b->table_next_branch = branch_table[hc];
        b->branch_tree.versions[0].mode = S_IFDIR;
        b->branch_tree.versions[1].mode = S_IFDIR;
+       b->num_notes = 0;
        b->active = 0;
        b->pack_id = MAX_PACK_ID;
        branch_table[hc] = b;
@@ -1003,7 +1015,7 @@ static void cycle_packfile(void)
 
 static size_t encode_header(
        enum object_type type,
-       size_t size,
+       uintmax_t size,
        unsigned char *hdr)
 {
        int n = 1;
@@ -1159,6 +1171,118 @@ static int store_object(
        return 0;
 }
 
+static void truncate_pack(off_t to)
+{
+       if (ftruncate(pack_data->pack_fd, to)
+        || lseek(pack_data->pack_fd, to, SEEK_SET) != to)
+               die_errno("cannot truncate pack to skip duplicate");
+       pack_size = to;
+}
+
+static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
+{
+       size_t in_sz = 64 * 1024, out_sz = 64 * 1024;
+       unsigned char *in_buf = xmalloc(in_sz);
+       unsigned char *out_buf = xmalloc(out_sz);
+       struct object_entry *e;
+       unsigned char sha1[20];
+       unsigned long hdrlen;
+       off_t offset;
+       git_SHA_CTX c;
+       z_stream s;
+       int status = Z_OK;
+
+       /* Determine if we should auto-checkpoint. */
+       if ((pack_size + 60 + len) > max_packsize
+               || (pack_size + 60 + len) < pack_size)
+               cycle_packfile();
+
+       offset = pack_size;
+
+       hdrlen = snprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1;
+       if (out_sz <= hdrlen)
+               die("impossibly large object header");
+
+       git_SHA1_Init(&c);
+       git_SHA1_Update(&c, out_buf, hdrlen);
+
+       memset(&s, 0, sizeof(s));
+       deflateInit(&s, pack_compression_level);
+
+       hdrlen = encode_header(OBJ_BLOB, len, out_buf);
+       if (out_sz <= hdrlen)
+               die("impossibly large object header");
+
+       s.next_out = out_buf + hdrlen;
+       s.avail_out = out_sz - hdrlen;
+
+       while (status != Z_STREAM_END) {
+               if (0 < len && !s.avail_in) {
+                       size_t cnt = in_sz < len ? in_sz : (size_t)len;
+                       size_t n = fread(in_buf, 1, cnt, stdin);
+                       if (!n && feof(stdin))
+                               die("EOF in data (%" PRIuMAX " bytes remaining)", len);
+
+                       git_SHA1_Update(&c, in_buf, n);
+                       s.next_in = in_buf;
+                       s.avail_in = n;
+                       len -= n;
+               }
+
+               status = deflate(&s, len ? 0 : Z_FINISH);
+
+               if (!s.avail_out || status == Z_STREAM_END) {
+                       size_t n = s.next_out - out_buf;
+                       write_or_die(pack_data->pack_fd, out_buf, n);
+                       pack_size += n;
+                       s.next_out = out_buf;
+                       s.avail_out = out_sz;
+               }
+
+               switch (status) {
+               case Z_OK:
+               case Z_BUF_ERROR:
+               case Z_STREAM_END:
+                       continue;
+               default:
+                       die("unexpected deflate failure: %d", status);
+               }
+       }
+       deflateEnd(&s);
+       git_SHA1_Final(sha1, &c);
+
+       if (sha1out)
+               hashcpy(sha1out, sha1);
+
+       e = insert_object(sha1);
+
+       if (mark)
+               insert_mark(mark, e);
+
+       if (e->offset) {
+               duplicate_count_by_type[OBJ_BLOB]++;
+               truncate_pack(offset);
+
+       } else if (find_sha1_pack(sha1, packed_git)) {
+               e->type = OBJ_BLOB;
+               e->pack_id = MAX_PACK_ID;
+               e->offset = 1; /* just not zero! */
+               duplicate_count_by_type[OBJ_BLOB]++;
+               truncate_pack(offset);
+
+       } else {
+               e->depth = 0;
+               e->type = OBJ_BLOB;
+               e->pack_id = pack_id;
+               e->offset = offset;
+               object_count++;
+               object_count_by_type[OBJ_BLOB]++;
+       }
+
+       free(in_buf);
+       free(out_buf);
+}
+
 /* All calls must be guarded by find_object() or find_mark() to
  * ensure the 'struct object_entry' passed was written by this
  * process instance.  We unpack the entry by the offset, avoiding
@@ -1602,13 +1726,13 @@ static void dump_marks(void)
        int mark_fd;
        FILE *f;
 
-       if (!mark_file)
+       if (!export_marks_file)
                return;
 
-       mark_fd = hold_lock_file_for_update(&mark_lock, mark_file, 0);
+       mark_fd = hold_lock_file_for_update(&mark_lock, export_marks_file, 0);
        if (mark_fd < 0) {
                failure |= error("Unable to write marks file %s: %s",
-                       mark_file, strerror(errno));
+                       export_marks_file, strerror(errno));
                return;
        }
 
@@ -1617,7 +1741,7 @@ static void dump_marks(void)
                int saved_errno = errno;
                rollback_lock_file(&mark_lock);
                failure |= error("Unable to write marks file %s: %s",
-                       mark_file, strerror(saved_errno));
+                       export_marks_file, strerror(saved_errno));
                return;
        }
 
@@ -1633,7 +1757,7 @@ static void dump_marks(void)
                int saved_errno = errno;
                rollback_lock_file(&mark_lock);
                failure |= error("Unable to write marks file %s: %s",
-                       mark_file, strerror(saved_errno));
+                       export_marks_file, strerror(saved_errno));
                return;
        }
 
@@ -1641,11 +1765,47 @@ static void dump_marks(void)
                int saved_errno = errno;
                rollback_lock_file(&mark_lock);
                failure |= error("Unable to commit marks file %s: %s",
-                       mark_file, strerror(saved_errno));
+                       export_marks_file, strerror(saved_errno));
                return;
        }
 }
 
+static void read_marks(void)
+{
+       char line[512];
+       FILE *f = fopen(import_marks_file, "r");
+       if (!f)
+               die_errno("cannot read '%s'", import_marks_file);
+       while (fgets(line, sizeof(line), f)) {
+               uintmax_t mark;
+               char *end;
+               unsigned char sha1[20];
+               struct object_entry *e;
+
+               end = strchr(line, '\n');
+               if (line[0] != ':' || !end)
+                       die("corrupt mark line: %s", line);
+               *end = 0;
+               mark = strtoumax(line + 1, &end, 10);
+               if (!mark || end == line + 1
+                       || *end != ' ' || get_sha1(end + 1, sha1))
+                       die("corrupt mark line: %s", line);
+               e = find_object(sha1);
+               if (!e) {
+                       enum object_type type = sha1_object_info(sha1, NULL);
+                       if (type < 0)
+                               die("object not found: %s", sha1_to_hex(sha1));
+                       e = insert_object(sha1);
+                       e->type = type;
+                       e->pack_id = MAX_PACK_ID;
+                       e->offset = 1; /* just not zero! */
+               }
+               insert_mark(mark, e);
+       }
+       fclose(f);
+}
+
+
 static int read_next_command(void)
 {
        static int stdin_eof = 0;
@@ -1666,6 +1826,12 @@ static int read_next_command(void)
                        if (stdin_eof)
                                return EOF;
 
+                       if (!seen_data_command
+                               && prefixcmp(command_buf.buf, "feature ")
+                               && prefixcmp(command_buf.buf, "option ")) {
+                               parse_argv();
+                       }
+
                        rc = rc_free;
                        if (rc)
                                rc_free = rc->next;
@@ -1704,7 +1870,7 @@ static void parse_mark(void)
                next_mark = 0;
 }
 
-static void parse_data(struct strbuf *sb)
+static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res)
 {
        strbuf_reset(sb);
 
@@ -1728,9 +1894,15 @@ static void parse_data(struct strbuf *sb)
                free(term);
        }
        else {
-               size_t n = 0, length;
+               uintmax_t len = strtoumax(command_buf.buf + 5, NULL, 10);
+               size_t n = 0, length = (size_t)len;
 
-               length = strtoul(command_buf.buf + 5, NULL, 10);
+               if (limit && limit < len) {
+                       *len_res = len;
+                       return 0;
+               }
+               if (length < len)
+                       die("data is too large to use in this context");
 
                while (n < length) {
                        size_t s = strbuf_fread(sb, length - n, stdin);
@@ -1742,6 +1914,7 @@ static void parse_data(struct strbuf *sb)
        }
 
        skip_optional_lf();
+       return 1;
 }
 
 static int validate_raw_date(const char *src, char *result, int maxlen)
@@ -1806,14 +1979,32 @@ static char *parse_ident(const char *buf)
        return ident;
 }
 
-static void parse_new_blob(void)
+static void parse_and_store_blob(
+       struct last_object *last,
+       unsigned char *sha1out,
+       uintmax_t mark)
 {
        static struct strbuf buf = STRBUF_INIT;
+       uintmax_t len;
+
+       if (parse_data(&buf, big_file_threshold, &len))
+               store_object(OBJ_BLOB, &buf, last, sha1out, mark);
+       else {
+               if (last) {
+                       strbuf_release(&last->data);
+                       last->offset = 0;
+                       last->depth = 0;
+               }
+               stream_blob(len, sha1out, mark);
+               skip_optional_lf();
+       }
+}
 
+static void parse_new_blob(void)
+{
        read_next_command();
        parse_mark();
-       parse_data(&buf);
-       store_object(OBJ_BLOB, &buf, &last_blob, NULL, next_mark);
+       parse_and_store_blob(&last_blob, NULL, next_mark);
 }
 
 static void unload_one_branch(void)
@@ -1860,6 +2051,109 @@ static void load_branch(struct branch *b)
        }
 }
 
+static unsigned char convert_num_notes_to_fanout(uintmax_t num_notes)
+{
+       unsigned char fanout = 0;
+       while ((num_notes >>= 8))
+               fanout++;
+       return fanout;
+}
+
+static void construct_path_with_fanout(const char *hex_sha1,
+               unsigned char fanout, char *path)
+{
+       unsigned int i = 0, j = 0;
+       if (fanout >= 20)
+               die("Too large fanout (%u)", fanout);
+       while (fanout) {
+               path[i++] = hex_sha1[j++];
+               path[i++] = hex_sha1[j++];
+               path[i++] = '/';
+               fanout--;
+       }
+       memcpy(path + i, hex_sha1 + j, 40 - j);
+       path[i + 40 - j] = '\0';
+}
+
+static uintmax_t do_change_note_fanout(
+               struct tree_entry *orig_root, struct tree_entry *root,
+               char *hex_sha1, unsigned int hex_sha1_len,
+               char *fullpath, unsigned int fullpath_len,
+               unsigned char fanout)
+{
+       struct tree_content *t = root->tree;
+       struct tree_entry *e, leaf;
+       unsigned int i, tmp_hex_sha1_len, tmp_fullpath_len;
+       uintmax_t num_notes = 0;
+       unsigned char sha1[20];
+       char realpath[60];
+
+       for (i = 0; t && i < t->entry_count; i++) {
+               e = t->entries[i];
+               tmp_hex_sha1_len = hex_sha1_len + e->name->str_len;
+               tmp_fullpath_len = fullpath_len;
+
+               /*
+                * We're interested in EITHER existing note entries (entries
+                * with exactly 40 hex chars in path, not including directory
+                * separators), OR directory entries that may contain note
+                * entries (with < 40 hex chars in path).
+                * Also, each path component in a note entry must be a multiple
+                * of 2 chars.
+                */
+               if (!e->versions[1].mode ||
+                   tmp_hex_sha1_len > 40 ||
+                   e->name->str_len % 2)
+                       continue;
+
+               /* This _may_ be a note entry, or a subdir containing notes */
+               memcpy(hex_sha1 + hex_sha1_len, e->name->str_dat,
+                      e->name->str_len);
+               if (tmp_fullpath_len)
+                       fullpath[tmp_fullpath_len++] = '/';
+               memcpy(fullpath + tmp_fullpath_len, e->name->str_dat,
+                      e->name->str_len);
+               tmp_fullpath_len += e->name->str_len;
+               fullpath[tmp_fullpath_len] = '\0';
+
+               if (tmp_hex_sha1_len == 40 && !get_sha1_hex(hex_sha1, sha1)) {
+                       /* This is a note entry */
+                       construct_path_with_fanout(hex_sha1, fanout, realpath);
+                       if (!strcmp(fullpath, realpath)) {
+                               /* Note entry is in correct location */
+                               num_notes++;
+                               continue;
+                       }
+
+                       /* Rename fullpath to realpath */
+                       if (!tree_content_remove(orig_root, fullpath, &leaf))
+                               die("Failed to remove path %s", fullpath);
+                       tree_content_set(orig_root, realpath,
+                               leaf.versions[1].sha1,
+                               leaf.versions[1].mode,
+                               leaf.tree);
+               } else if (S_ISDIR(e->versions[1].mode)) {
+                       /* This is a subdir that may contain note entries */
+                       if (!e->tree)
+                               load_tree(e);
+                       num_notes += do_change_note_fanout(orig_root, e,
+                               hex_sha1, tmp_hex_sha1_len,
+                               fullpath, tmp_fullpath_len, fanout);
+               }
+
+               /* The above may have reallocated the current tree_content */
+               t = root->tree;
+       }
+       return num_notes;
+}
+
+static uintmax_t change_note_fanout(struct tree_entry *root,
+               unsigned char fanout)
+{
+       char hex_sha1[40], path[60];
+       return do_change_note_fanout(root, root, hex_sha1, 0, path, 0, fanout);
+}
+
 static void file_change_m(struct branch *b)
 {
        const char *p = command_buf.buf + 2;
@@ -1924,15 +2218,12 @@ static void file_change_m(struct branch *b)
                 * another repository.
                 */
        } else 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);
+               parse_and_store_blob(&last_blob, sha1, 0);
        } else if (oe) {
                if (oe->type != OBJ_BLOB)
                        die("Not a blob (actually a %s): %s",
@@ -2010,14 +2301,16 @@ static void file_change_cr(struct branch *b, int rename)
                leaf.tree);
 }
 
-static void note_change_n(struct branch *b)
+static void note_change_n(struct branch *b, unsigned char old_fanout)
 {
        const char *p = command_buf.buf + 2;
        static struct strbuf uq = STRBUF_INIT;
        struct object_entry *oe = oe;
        struct branch *s;
        unsigned char sha1[20], commit_sha1[20];
+       char path[60];
        uint16_t inline_data = 0;
+       unsigned char new_fanout;
 
        /* <dataref> or 'inline' */
        if (*p == ':') {
@@ -2058,20 +2351,17 @@ static void note_change_n(struct branch *b)
                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);
+               parse_and_store_blob(&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 {
+       } else if (!is_null_sha1(sha1)) {
                enum object_type type = sha1_object_info(sha1, NULL);
                if (type < 0)
                        die("Blob not found: %s", command_buf.buf);
@@ -2080,8 +2370,17 @@ static void note_change_n(struct branch *b)
                            typename(type), command_buf.buf);
        }
 
-       tree_content_set(&b->branch_tree, sha1_to_hex(commit_sha1), sha1,
-               S_IFREG | 0644, NULL);
+       construct_path_with_fanout(sha1_to_hex(commit_sha1), old_fanout, path);
+       if (tree_content_remove(&b->branch_tree, path, NULL))
+               b->num_notes--;
+
+       if (is_null_sha1(sha1))
+               return; /* nothing to insert */
+
+       b->num_notes++;
+       new_fanout = convert_num_notes_to_fanout(b->num_notes);
+       construct_path_with_fanout(sha1_to_hex(commit_sha1), new_fanout, path);
+       tree_content_set(&b->branch_tree, path, sha1, S_IFREG | 0644, NULL);
 }
 
 static void file_change_deleteall(struct branch *b)
@@ -2090,6 +2389,7 @@ static void file_change_deleteall(struct branch *b)
        hashclr(b->branch_tree.versions[0].sha1);
        hashclr(b->branch_tree.versions[1].sha1);
        load_tree(&b->branch_tree);
+       b->num_notes = 0;
 }
 
 static void parse_from_commit(struct branch *b, char *buf, unsigned long size)
@@ -2213,6 +2513,7 @@ static void parse_new_commit(void)
        char *committer = NULL;
        struct hash_list *merge_list = NULL;
        unsigned int merge_count;
+       unsigned char prev_fanout, new_fanout;
 
        /* Obtain the branch name from the rest of our command */
        sp = strchr(command_buf.buf, ' ') + 1;
@@ -2232,7 +2533,7 @@ static void parse_new_commit(void)
        }
        if (!committer)
                die("Expected committer but didn't get one");
-       parse_data(&msg);
+       parse_data(&msg, 0, NULL);
        read_next_command();
        parse_from(b);
        merge_list = parse_merge(&merge_count);
@@ -2243,6 +2544,8 @@ static void parse_new_commit(void)
                load_branch(b);
        }
 
+       prev_fanout = convert_num_notes_to_fanout(b->num_notes);
+
        /* file_change* */
        while (command_buf.len > 0) {
                if (!prefixcmp(command_buf.buf, "M "))
@@ -2254,7 +2557,7 @@ static void parse_new_commit(void)
                else if (!prefixcmp(command_buf.buf, "C "))
                        file_change_cr(b, 0);
                else if (!prefixcmp(command_buf.buf, "N "))
-                       note_change_n(b);
+                       note_change_n(b, prev_fanout);
                else if (!strcmp("deleteall", command_buf.buf))
                        file_change_deleteall(b);
                else {
@@ -2265,6 +2568,10 @@ static void parse_new_commit(void)
                        break;
        }
 
+       new_fanout = convert_num_notes_to_fanout(b->num_notes);
+       if (new_fanout != prev_fanout)
+               b->num_notes = change_note_fanout(&b->branch_tree, new_fanout);
+
        /* build the tree and the commit */
        store_tree(&b->branch_tree);
        hashcpy(b->branch_tree.versions[0].sha1,
@@ -2353,7 +2660,7 @@ static void parse_new_tag(void)
                tagger = NULL;
 
        /* tag payload/message */
-       parse_data(&msg);
+       parse_data(&msg, 0, NULL);
 
        /* build the tag object */
        strbuf_reset(&new_data);
@@ -2420,39 +2727,150 @@ static void parse_progress(void)
        skip_optional_lf();
 }
 
-static void import_marks(const char *input_file)
+static char* make_fast_import_path(const char *path)
 {
-       char line[512];
-       FILE *f = fopen(input_file, "r");
-       if (!f)
-               die_errno("cannot read '%s'", input_file);
-       while (fgets(line, sizeof(line), f)) {
-               uintmax_t mark;
-               char *end;
-               unsigned char sha1[20];
-               struct object_entry *e;
+       struct strbuf abs_path = STRBUF_INIT;
 
-               end = strchr(line, '\n');
-               if (line[0] != ':' || !end)
-                       die("corrupt mark line: %s", line);
-               *end = 0;
-               mark = strtoumax(line + 1, &end, 10);
-               if (!mark || end == line + 1
-                       || *end != ' ' || get_sha1(end + 1, sha1))
-                       die("corrupt mark line: %s", line);
-               e = find_object(sha1);
-               if (!e) {
-                       enum object_type type = sha1_object_info(sha1, NULL);
-                       if (type < 0)
-                               die("object not found: %s", sha1_to_hex(sha1));
-                       e = insert_object(sha1);
-                       e->type = type;
-                       e->pack_id = MAX_PACK_ID;
-                       e->offset = 1; /* just not zero! */
+       if (!relative_marks_paths || is_absolute_path(path))
+               return xstrdup(path);
+       strbuf_addf(&abs_path, "%s/info/fast-import/%s", get_git_dir(), path);
+       return strbuf_detach(&abs_path, NULL);
+}
+
+static void option_import_marks(const char *marks, int from_stream)
+{
+       if (import_marks_file) {
+               if (from_stream)
+                       die("Only one import-marks command allowed per stream");
+
+               /* read previous mark file */
+               if(!import_marks_file_from_stream)
+                       read_marks();
+       }
+
+       import_marks_file = make_fast_import_path(marks);
+       import_marks_file_from_stream = from_stream;
+}
+
+static void option_date_format(const char *fmt)
+{
+       if (!strcmp(fmt, "raw"))
+               whenspec = WHENSPEC_RAW;
+       else if (!strcmp(fmt, "rfc2822"))
+               whenspec = WHENSPEC_RFC2822;
+       else if (!strcmp(fmt, "now"))
+               whenspec = WHENSPEC_NOW;
+       else
+               die("unknown --date-format argument %s", fmt);
+}
+
+static void option_depth(const char *depth)
+{
+       max_depth = strtoul(depth, NULL, 0);
+       if (max_depth > MAX_DEPTH)
+               die("--depth cannot exceed %u", MAX_DEPTH);
+}
+
+static void option_active_branches(const char *branches)
+{
+       max_active_branches = strtoul(branches, NULL, 0);
+}
+
+static void option_export_marks(const char *marks)
+{
+       export_marks_file = make_fast_import_path(marks);
+}
+
+static void option_export_pack_edges(const char *edges)
+{
+       if (pack_edges)
+               fclose(pack_edges);
+       pack_edges = fopen(edges, "a");
+       if (!pack_edges)
+               die_errno("Cannot open '%s'", edges);
+}
+
+static int parse_one_option(const char *option)
+{
+       if (!prefixcmp(option, "max-pack-size=")) {
+               unsigned long v;
+               if (!git_parse_ulong(option + 14, &v))
+                       return 0;
+               if (v < 8192) {
+                       warning("max-pack-size is now in bytes, assuming --max-pack-size=%lum", v);
+                       v *= 1024 * 1024;
+               } else if (v < 1024 * 1024) {
+                       warning("minimum max-pack-size is 1 MiB");
+                       v = 1024 * 1024;
                }
-               insert_mark(mark, e);
+               max_packsize = v;
+       } else if (!prefixcmp(option, "big-file-threshold=")) {
+               unsigned long v;
+               if (!git_parse_ulong(option + 19, &v))
+                       return 0;
+               big_file_threshold = v;
+       } else if (!prefixcmp(option, "depth=")) {
+               option_depth(option + 6);
+       } else if (!prefixcmp(option, "active-branches=")) {
+               option_active_branches(option + 16);
+       } else if (!prefixcmp(option, "export-pack-edges=")) {
+               option_export_pack_edges(option + 18);
+       } else if (!prefixcmp(option, "quiet")) {
+               show_stats = 0;
+       } else if (!prefixcmp(option, "stats")) {
+               show_stats = 1;
+       } else {
+               return 0;
        }
-       fclose(f);
+
+       return 1;
+}
+
+static int parse_one_feature(const char *feature, int from_stream)
+{
+       if (!prefixcmp(feature, "date-format=")) {
+               option_date_format(feature + 12);
+       } else if (!prefixcmp(feature, "import-marks=")) {
+               option_import_marks(feature + 13, from_stream);
+       } else if (!prefixcmp(feature, "export-marks=")) {
+               option_export_marks(feature + 13);
+       } else if (!prefixcmp(feature, "relative-marks")) {
+               relative_marks_paths = 1;
+       } else if (!prefixcmp(feature, "no-relative-marks")) {
+               relative_marks_paths = 0;
+       } else if (!prefixcmp(feature, "force")) {
+               force_update = 1;
+       } else {
+               return 0;
+       }
+
+       return 1;
+}
+
+static void parse_feature(void)
+{
+       char *feature = command_buf.buf + 8;
+
+       if (seen_data_command)
+               die("Got feature command '%s' after data command", feature);
+
+       if (parse_one_feature(feature, 1))
+               return;
+
+       die("This version of fast-import does not support feature %s.", feature);
+}
+
+static void parse_option(void)
+{
+       char *option = command_buf.buf + 11;
+
+       if (seen_data_command)
+               die("Got option command '%s' after data command", option);
+
+       if (parse_one_option(option))
+               return;
+
+       die("This version of fast-import does not support option: %s", option);
 }
 
 static int git_pack_config(const char *k, const char *v, void *cb)
@@ -2473,15 +2891,45 @@ static int git_pack_config(const char *k, const char *v, void *cb)
                pack_compression_seen = 1;
                return 0;
        }
+       if (!strcmp(k, "core.bigfilethreshold")) {
+               long n = git_config_int(k, v);
+               big_file_threshold = 0 < n ? n : 0;
+       }
        return git_default_config(k, v, cb);
 }
 
 static const char fast_import_usage[] =
-"git fast-import [--date-format=f] [--max-pack-size=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";
+"git fast-import [--date-format=f] [--max-pack-size=n] [--big-file-threshold=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";
+
+static void parse_argv(void)
+{
+       unsigned int i;
+
+       for (i = 1; i < global_argc; i++) {
+               const char *a = global_argv[i];
+
+               if (*a != '-' || !strcmp(a, "--"))
+                       break;
+
+               if (parse_one_option(a + 2))
+                       continue;
+
+               if (parse_one_feature(a + 2, 0))
+                       continue;
+
+               die("unknown option %s", a);
+       }
+       if (i != global_argc)
+               usage(fast_import_usage);
+
+       seen_data_command = 1;
+       if (import_marks_file)
+               read_marks();
+}
 
 int main(int argc, const char **argv)
 {
-       unsigned int i, show_stats = 1;
+       unsigned int i;
 
        git_extract_argv0_path(argv[0]);
 
@@ -2500,52 +2948,8 @@ int main(int argc, const char **argv)
        avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
        marks = pool_calloc(1, sizeof(struct mark_set));
 
-       for (i = 1; i < argc; i++) {
-               const char *a = argv[i];
-
-               if (*a != '-' || !strcmp(a, "--"))
-                       break;
-               else if (!prefixcmp(a, "--date-format=")) {
-                       const char *fmt = a + 14;
-                       if (!strcmp(fmt, "raw"))
-                               whenspec = WHENSPEC_RAW;
-                       else if (!strcmp(fmt, "rfc2822"))
-                               whenspec = WHENSPEC_RFC2822;
-                       else if (!strcmp(fmt, "now"))
-                               whenspec = WHENSPEC_NOW;
-                       else
-                               die("unknown --date-format argument %s", fmt);
-               }
-               else if (!prefixcmp(a, "--max-pack-size="))
-                       max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024;
-               else if (!prefixcmp(a, "--depth=")) {
-                       max_depth = strtoul(a + 8, NULL, 0);
-                       if (max_depth > MAX_DEPTH)
-                               die("--depth cannot exceed %u", MAX_DEPTH);
-               }
-               else if (!prefixcmp(a, "--active-branches="))
-                       max_active_branches = strtoul(a + 18, NULL, 0);
-               else if (!prefixcmp(a, "--import-marks="))
-                       import_marks(a + 15);
-               else if (!prefixcmp(a, "--export-marks="))
-                       mark_file = a + 15;
-               else if (!prefixcmp(a, "--export-pack-edges=")) {
-                       if (pack_edges)
-                               fclose(pack_edges);
-                       pack_edges = fopen(a + 20, "a");
-                       if (!pack_edges)
-                               die_errno("Cannot open '%s'", a + 20);
-               } else if (!strcmp(a, "--force"))
-                       force_update = 1;
-               else if (!strcmp(a, "--quiet"))
-                       show_stats = 0;
-               else if (!strcmp(a, "--stats"))
-                       show_stats = 1;
-               else
-                       die("unknown option %s", a);
-       }
-       if (i != argc)
-               usage(fast_import_usage);
+       global_argc = argc;
+       global_argv = argv;
 
        rc_free = pool_alloc(cmd_save * sizeof(*rc_free));
        for (i = 0; i < (cmd_save - 1); i++)
@@ -2568,9 +2972,20 @@ int main(int argc, const char **argv)
                        parse_checkpoint();
                else if (!prefixcmp(command_buf.buf, "progress "))
                        parse_progress();
+               else if (!prefixcmp(command_buf.buf, "feature "))
+                       parse_feature();
+               else if (!prefixcmp(command_buf.buf, "option git "))
+                       parse_option();
+               else if (!prefixcmp(command_buf.buf, "option "))
+                       /* ignore non-git options*/;
                else
                        die("Unsupported command: %s", command_buf.buf);
        }
+
+       /* argv hasn't been parsed yet, do so */
+       if (!seen_data_command)
+               parse_argv();
+
        end_packfile();
 
        dump_branches();
index 4838cdb9ede8765b37ab663aa59fb1b011a30b4c..c8b9cbb101f1319b6efd78ff6ae9c63efcdc991b 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -30,6 +30,7 @@ skip            skip the current patch
 abort           restore the original branch and abort the patching operation.
 committer-date-is-author-date    lie about committer date
 ignore-date     use current timestamp for author date
+rerere-autoupdate update the index with reused conflict resolution if possible
 rebasing*       (internal use for git-rebase)"
 
 . git-sh-setup
@@ -135,7 +136,7 @@ It does not apply to blobs recorded in its index."
            export GIT_MERGE_VERBOSITY=0
     fi
     git-merge-recursive $orig_tree -- HEAD $his_tree || {
-           git rerere
+           git rerere $allow_rerere_autoupdate
            echo Failed to merge in the changes.
            exit 1
     }
@@ -204,7 +205,8 @@ check_patch_format () {
                        # discarding the indented remainder of folded lines,
                        # and see if it looks like that they all begin with the
                        # header field names...
-                       sed -n -e '/^$/q' -e '/^[       ]/d' -e p "$1" |
+                       tr -d '\015' <"$1" |
+                       sed -n -e '/^$/q' -e '/^[       ]/d' -e p |
                        sane_egrep -v '^[!-9;-~]+:' >/dev/null ||
                        patch_format=mbox
                fi
@@ -293,6 +295,7 @@ resolvemsg= resume= scissors= no_inbody_headers=
 git_apply_opt=
 committer_date_is_author_date=
 ignore_date=
+allow_rerere_autoupdate=
 
 while test $# != 0
 do
@@ -340,6 +343,8 @@ do
                committer_date_is_author_date=t ;;
        --ignore-date)
                ignore_date=t ;;
+       --rerere-autoupdate|--no-rerere-autoupdate)
+               allow_rerere_autoupdate="$1" ;;
        -q|--quiet)
                GIT_QUIET=t ;;
        --)
index 5c596875c2e78a92031915948444971126d91a78..a3c45373669cd8482c04d5815862ed36a153572d 100644 (file)
@@ -96,6 +96,7 @@
 #include <sys/poll.h>
 #include <sys/socket.h>
 #include <sys/ioctl.h>
+#include <termios.h>
 #ifndef NO_SYS_SELECT_H
 #include <sys/select.h>
 #endif
@@ -177,6 +178,9 @@ extern char *gitbasename(char *);
 #ifdef __GNUC__
 #define NORETURN __attribute__((__noreturn__))
 #define NORETURN_PTR __attribute__((__noreturn__))
+#elif defined(_MSC_VER)
+#define NORETURN __declspec(noreturn)
+#define NORETURN_PTR
 #else
 #define NORETURN
 #define NORETURN_PTR
@@ -198,7 +202,7 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)))
 extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
 
 extern int prefixcmp(const char *str, const char *prefix);
-extern time_t tm_to_time_t(const struct tm *tm);
+extern int suffixcmp(const char *str, const char *suffix);
 
 static inline const char *skip_prefix(const char *str, const char *prefix)
 {
@@ -344,6 +348,7 @@ extern void release_pack_memory(size_t, int);
 
 extern char *xstrdup(const char *str);
 extern void *xmalloc(size_t size);
+extern void *xmallocz(size_t size);
 extern void *xmemdupz(const void *data, size_t len);
 extern char *xstrndup(const char *str, size_t len);
 extern void *xrealloc(void *ptr, size_t size);
index a7d215c8aa479bc6b456ec5c3792e931598ac365..4853bf7a0d2d3cb99dee3f3fc371a48209c9efe2 100755 (executable)
@@ -609,16 +609,16 @@ $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
 my %index; # holds filenames of one index per branch
 
 unless (-d $git_dir) {
-       system("git-init");
+       system(qw(git init));
        die "Cannot init the GIT db at $git_tree: $?\n" if $?;
-       system("git-read-tree");
+       system(qw(git read-tree));
        die "Cannot init an empty tree: $?\n" if $?;
 
        $last_branch = $opt_o;
        $orig_branch = "";
 } else {
-       open(F, "git-symbolic-ref HEAD |") or
-               die "Cannot run git-symbolic-ref: $!\n";
+       open(F, "-|", qw(git symbolic-ref HEAD)) or
+               die "Cannot run git symbolic-ref: $!\n";
        chomp ($last_branch = <F>);
        $last_branch = basename($last_branch);
        close(F);
@@ -627,12 +627,12 @@ unless (-d $git_dir) {
                $last_branch = "master";
        }
        $orig_branch = $last_branch;
-       $tip_at_start = `git-rev-parse --verify HEAD`;
+       $tip_at_start = `git rev-parse --verify HEAD`;
 
        # Get the last import timestamps
        my $fmt = '($ref, $author) = (%(refname), %(author));';
-       open(H, "git-for-each-ref --perl --format='$fmt' $remote |") or
-               die "Cannot run git-for-each-ref: $!\n";
+       my @cmd = ('git', 'for-each-ref', '--perl', "--format=$fmt", $remote);
+       open(H, "-|", @cmd) or die "Cannot run git for-each-ref: $!\n";
        while (defined(my $entry = <H>)) {
                my ($ref, $author);
                eval($entry) || die "cannot eval refs list: $@";
@@ -687,7 +687,7 @@ unless ($opt_P) {
            print $cvspsfh $_;
        }
        close CVSPS;
-       $? == 0 or die "git-cvsimport: fatal: cvsps reported error\n";
+       $? == 0 or die "git cvsimport: fatal: cvsps reported error\n";
        close $cvspsfh;
 } else {
        $cvspsfile = munge_user_filename($opt_P);
@@ -716,27 +716,27 @@ my $state = 0;
 sub update_index (\@\@) {
        my $old = shift;
        my $new = shift;
-       open(my $fh, '|-', qw(git-update-index -z --index-info))
-               or die "unable to open git-update-index: $!";
+       open(my $fh, '|-', qw(git update-index -z --index-info))
+               or die "unable to open git update-index: $!";
        print $fh
                (map { "0 0000000000000000000000000000000000000000\t$_\0" }
                        @$old),
                (map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" }
                        @$new)
-               or die "unable to write to git-update-index: $!";
+               or die "unable to write to git update-index: $!";
        close $fh
-               or die "unable to write to git-update-index: $!";
-       $? and die "git-update-index reported error: $?";
+               or die "unable to write to git update-index: $!";
+       $? and die "git update-index reported error: $?";
 }
 
 sub write_tree () {
-       open(my $fh, '-|', qw(git-write-tree))
-               or die "unable to open git-write-tree: $!";
+       open(my $fh, '-|', qw(git write-tree))
+               or die "unable to open git write-tree: $!";
        chomp(my $tree = <$fh>);
        is_sha1($tree)
                or die "Cannot get tree id ($tree): $!";
        close($fh)
-               or die "Error running git-write-tree: $?\n";
+               or die "Error running git write-tree: $?\n";
        print "Tree ID $tree\n" if $opt_v;
        return $tree;
 }
@@ -751,7 +751,7 @@ sub commit {
        if ($branch eq $opt_o && !$index{branch} &&
                !get_headref("$remote/$branch")) {
            # looks like an initial commit
-           # use the index primed by git-init
+           # use the index primed by git init
            $ENV{GIT_INDEX_FILE} = "$git_dir/index";
            $index{$branch} = "$git_dir/index";
        } else {
@@ -761,9 +761,9 @@ sub commit {
                $index{$branch} = tmpnam();
                $ENV{GIT_INDEX_FILE} = $index{$branch};
                if ($ancestor) {
-                   system("git-read-tree", "$remote/$ancestor");
+                   system("git", "read-tree", "$remote/$ancestor");
                } else {
-                   system("git-read-tree", "$remote/$branch");
+                   system("git", "read-tree", "$remote/$branch");
                }
                die "read-tree failed: $?\n" if $?;
            }
@@ -798,7 +798,7 @@ sub commit {
        $ENV{GIT_COMMITTER_EMAIL} = $author_email;
        $ENV{GIT_COMMITTER_DATE} = $commit_date;
        my $pid = open2(my $commit_read, my $commit_write,
-               'git-commit-tree', $tree, @commit_args);
+               'git', 'commit-tree', $tree, @commit_args);
 
        # compatibility with git2cvs
        substr($logmsg,32767) = "" if length($logmsg) > 32767;
@@ -811,7 +811,7 @@ sub commit {
        }
 
        print($commit_write "$logmsg\n") && close($commit_write)
-               or die "Error writing to git-commit-tree: $!\n";
+               or die "Error writing to git commit-tree: $!\n";
 
        print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v;
        chomp(my $cid = <$commit_read>);
@@ -820,9 +820,9 @@ sub commit {
        close($commit_read);
 
        waitpid($pid,0);
-       die "Error running git-commit-tree: $?\n" if $?;
+       die "Error running git commit-tree: $?\n" if $?;
 
-       system('git-update-ref', "$remote/$branch", $cid) == 0
+       system('git' , 'update-ref', "$remote/$branch", $cid) == 0
                or die "Cannot write branch $branch for update: $!\n";
 
        if ($tag) {
@@ -832,7 +832,7 @@ sub commit {
                $xtag =~ s/[\/]/$opt_s/g;
                $xtag =~ s/\[//g;
 
-               system('git-tag', '-f', $xtag, $cid) == 0
+               system('git' , 'tag', '-f', $xtag, $cid) == 0
                        or die "Cannot create tag $xtag: $!\n";
 
                print "Created tag '$xtag' on '$branch'\n" if $opt_v;
@@ -969,7 +969,7 @@ while (<CVS>) {
                        my $pid = open(my $F, '-|');
                        die $! unless defined $pid;
                        if (!$pid) {
-                           exec("git-hash-object", "-w", $tmpname)
+                           exec("git", "hash-object", "-w", $tmpname)
                                or die "Cannot create object: $!\n";
                        }
                        my $sha = <$F>;
@@ -993,7 +993,7 @@ while (<CVS>) {
                }
                commit();
                if (($commitcount & 1023) == 0) {
-                       system("git repack -a -d");
+                       system(qw(git repack -a -d));
                }
                $state = 1;
        } elsif ($state == 11 and /^-+$/) {
@@ -1013,11 +1013,11 @@ unless ($opt_P) {
 # The heuristic of repacking every 1024 commits can leave a
 # lot of unpacked data.  If there is more than 1MB worth of
 # not-packed objects, repack once more.
-my $line = `git-count-objects`;
+my $line = `git count-objects`;
 if ($line =~ /^(\d+) objects, (\d+) kilobytes$/) {
   my ($n_objects, $kb) = ($1, $2);
   1024 < $kb
-    and system("git repack -a -d");
+    and system(qw(git repack -a -d));
 }
 
 foreach my $git_index (values %index) {
@@ -1038,28 +1038,28 @@ if ($orig_branch) {
        if ($opt_i) {
                exit 0;
        }
-       my $tip_at_end = `git-rev-parse --verify HEAD`;
+       my $tip_at_end = `git rev-parse --verify HEAD`;
        if ($tip_at_start ne $tip_at_end) {
                for ($tip_at_start, $tip_at_end) { chomp; }
                print "Fetched into the current branch.\n" if $opt_v;
-               system(qw(git-read-tree -u -m),
+               system(qw(git read-tree -u -m),
                       $tip_at_start, $tip_at_end);
                die "Fast-forward update failed: $?\n" if $?;
        }
        else {
-               system(qw(git-merge cvsimport HEAD), "$remote/$opt_o");
+               system(qw(git merge cvsimport HEAD), "$remote/$opt_o");
                die "Could not merge $opt_o into the current branch.\n" if $?;
        }
 } else {
        $orig_branch = "master";
        print "DONE; creating $orig_branch branch\n" if $opt_v;
-       system("git-update-ref", "refs/heads/master", "$remote/$opt_o")
+       system("git", "update-ref", "refs/heads/master", "$remote/$opt_o")
                unless defined get_headref('refs/heads/master');
-       system("git-symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
+       system("git", "symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
                if ($opt_r && $opt_o ne 'HEAD');
-       system('git-update-ref', 'HEAD', "$orig_branch");
+       system('git', 'update-ref', 'HEAD', "$orig_branch");
        unless ($opt_i) {
-               system('git checkout -f');
+               system(qw(git checkout -f));
                die "checkout failed: $?\n" if $?;
        }
 }
index 6dc45f5d45a44c9bbe31987c1a10e4554f037d6c..13751db882dd2c40e2c78503eba0d3f941297644 100755 (executable)
@@ -104,6 +104,7 @@ $log->info("--------------- STARTING -----------------");
 my $usage =
     "Usage: git cvsserver [options] [pserver|server] [<directory> ...]\n".
     "    --base-path <path>  : Prepend to requested CVSROOT\n".
+    "                          Can be read from GIT_CVSSERVER_BASE_PATH\n".
     "    --strict-paths      : Don't allow recursing into subdirectories\n".
     "    --export-all        : Don't check for gitcvs.enabled in config\n".
     "    --version, -V       : Print version information and exit\n".
@@ -111,7 +112,8 @@ my $usage =
     "\n".
     "<directory> ... is a list of allowed directories. If no directories\n".
     "are given, all are allowed. This is an additional restriction, gitcvs\n".
-    "access still needs to be enabled by the gitcvs.enabled config option.\n";
+    "access still needs to be enabled by the gitcvs.enabled config option.\n".
+    "Alternately, one directory may be specified in GIT_CVSSERVER_ROOT.\n";
 
 my @opts = ( 'help|h|H', 'version|V',
             'base-path=s', 'strict-paths', 'export-all' );
@@ -148,6 +150,24 @@ if ($state->{'export-all'} && !@{$state->{allowed_roots}}) {
     die "--export-all can only be used together with an explicit whitelist\n";
 }
 
+# Environment handling for running under git-shell
+if (exists $ENV{GIT_CVSSERVER_BASE_PATH}) {
+    if ($state->{'base-path'}) {
+       die "Cannot specify base path both ways.\n";
+    }
+    my $base_path = $ENV{GIT_CVSSERVER_BASE_PATH};
+    $state->{'base-path'} = $base_path;
+    $log->debug("Picked up base path '$base_path' from environment.\n");
+}
+if (exists $ENV{GIT_CVSSERVER_ROOT}) {
+    if (@{$state->{allowed_roots}}) {
+       die "Cannot specify roots both ways: @ARGV\n";
+    }
+    my $allowed_root = $ENV{GIT_CVSSERVER_ROOT};
+    $state->{allowed_roots} = [ $allowed_root ];
+    $log->debug("Picked up allowed root '$allowed_root' from environment.\n");
+}
+
 # if we are called with a pserver argument,
 # deal with the authentication cat before entering the
 # main loop
@@ -388,7 +408,7 @@ sub req_Directory
     $state->{localdir} = $data;
     $state->{repository} = $repository;
     $state->{path} = $repository;
-    $state->{path} =~ s/^$state->{CVSROOT}\///;
+    $state->{path} =~ s/^\Q$state->{CVSROOT}\E\///;
     $state->{module} = $1 if ($state->{path} =~ s/^(.*?)(\/|$)//);
     $state->{path} .= "/" if ( $state->{path} =~ /\S/ );
 
@@ -981,6 +1001,8 @@ sub req_update
 
     #$log->debug("update state : " . Dumper($state));
 
+    my $last_dirname = "///";
+
     # foreach file specified on the command line ...
     foreach my $filename ( @{$state->{args}} )
     {
@@ -988,6 +1010,20 @@ sub req_update
 
         $log->debug("Processing file $filename");
 
+        unless ( $state->{globaloptions}{-Q} || $state->{globaloptions}{-q} )
+        {
+            my $cur_dirname = dirname($filename);
+            if ( $cur_dirname ne $last_dirname )
+            {
+                $last_dirname = $cur_dirname;
+                if ( $cur_dirname eq "" )
+                {
+                    $cur_dirname = ".";
+                }
+                print "E cvs update: Updating $cur_dirname\n";
+            }
+        }
+
         # if we have a -C we should pretend we never saw modified stuff
         if ( exists ( $state->{opt}{C} ) )
         {
index 57e8e3256d7b6578cda37815163d8945431aa6db..524f5ea8ab14bb44e5a8e6393e40c0ec5c1ddaa2 100755 (executable)
@@ -3,16 +3,16 @@
 # This script is typically launched by using the 'git difftool'
 # convenience command.
 #
-# Copyright (c) 2009 David Aguilar
+# Copyright (c) 2009, 2010 David Aguilar
 
-# Load common functions from git-mergetool--lib
 TOOL_MODE=diff
 . git-mergetool--lib
 
 # difftool.prompt controls the default prompt/no-prompt behavior
 # and is overridden with $GIT_DIFFTOOL*_PROMPT.
 should_prompt () {
-       prompt=$(git config --bool difftool.prompt || echo true)
+       prompt_merge=$(git config --bool mergetool.prompt || echo true)
+       prompt=$(git config --bool difftool.prompt || echo $prompt_merge)
        if test "$prompt" = true; then
                test -z "$GIT_DIFFTOOL_NO_PROMPT"
        else
@@ -20,7 +20,11 @@ should_prompt () {
        fi
 }
 
-# Sets up shell variables and runs a merge tool
+# Indicates that --extcmd=... was specified
+use_ext_cmd () {
+       test -n "$GIT_DIFFTOOL_EXTCMD"
+}
+
 launch_merge_tool () {
        # Merged is the filename as it appears in the work tree
        # Local is the contents of a/filename
@@ -35,20 +39,28 @@ launch_merge_tool () {
        # the user with the real $MERGED name before launching $merge_tool.
        if should_prompt; then
                printf "\nViewing: '$MERGED'\n"
-               printf "Hit return to launch '%s': " "$merge_tool"
+               if use_ext_cmd; then
+                       printf "Hit return to launch '%s': " \
+                               "$GIT_DIFFTOOL_EXTCMD"
+               else
+                       printf "Hit return to launch '%s': " "$merge_tool"
+               fi
                read ans
        fi
 
-       # Run the appropriate merge tool command
-       run_merge_tool "$merge_tool"
+       if use_ext_cmd; then
+               eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"'
+       else
+               run_merge_tool "$merge_tool"
+       fi
 }
 
-# Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values
-test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL"
-test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL"
-
-if test -z "$merge_tool"; then
-       merge_tool="$(get_merge_tool)" || exit
+if ! use_ext_cmd; then
+       if test -n "$GIT_DIFF_TOOL"; then
+               merge_tool="$GIT_DIFF_TOOL"
+       else
+               merge_tool="$(get_merge_tool)" || exit
+       fi
 fi
 
 # Launch the merge tool on each path provided by 'git diff'
index ba5e60a45e2c4a8b54e6a58e656b3978dcb45633..d975d072dbf0fab36266c3f3a71a69875206e4d8 100755 (executable)
@@ -1,5 +1,5 @@
 #!/usr/bin/env perl
-# Copyright (c) 2009 David Aguilar
+# Copyright (c) 2009, 2010 David Aguilar
 #
 # This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
 # git-difftool--helper script.
@@ -15,13 +15,17 @@ use warnings;
 use Cwd qw(abs_path);
 use File::Basename qw(dirname);
 
+require Git;
+
 my $DIR = abs_path(dirname($0));
 
 
 sub usage
 {
        print << 'USAGE';
-usage: git difftool [--tool=<tool>] [-y|--no-prompt] ["git diff" options]
+usage: git difftool [-t|--tool=<tool>] [-x|--extcmd=<cmd>]
+                    [-y|--no-prompt]   [-g|--gui]
+                    ['git diff' options]
 USAGE
        exit 1;
 }
@@ -63,6 +67,24 @@ sub generate_command
                        $ENV{GIT_DIFF_TOOL} = substr($arg, 7);
                        next;
                }
+               if ($arg eq '-x' || $arg eq '--extcmd') {
+                       usage() if $#ARGV <= $idx;
+                       $ENV{GIT_DIFFTOOL_EXTCMD} = $ARGV[$idx + 1];
+                       $skip_next = 1;
+                       next;
+               }
+               if ($arg =~ /^--extcmd=/) {
+                       $ENV{GIT_DIFFTOOL_EXTCMD} = substr($arg, 9);
+                       next;
+               }
+               if ($arg eq '-g' || $arg eq '--gui') {
+                       my $tool = Git::command_oneline('config',
+                                                       'diff.guitool');
+                       if (length($tool)) {
+                               $ENV{GIT_DIFF_TOOL} = $tool;
+                       }
+                       next;
+               }
                if ($arg eq '-y' || $arg eq '--no-prompt') {
                        $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
                        delete $ENV{GIT_DIFFTOOL_PROMPT};
index cb9d2022cc93f1f371264098828a52e0d6bf1d7c..e95845c0d6345ccc341d70d17431235197dfd967 100755 (executable)
@@ -259,7 +259,6 @@ test -s "$tempdir"/heads ||
 
 GIT_INDEX_FILE="$(pwd)/../index"
 export GIT_INDEX_FILE
-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"
@@ -332,7 +331,7 @@ while read commit parents; do
                        die "tree filter failed: $filter_tree"
 
                (
-                       git diff-index -r --name-only $commit &&
+                       git diff-index -r --name-only --ignore-submodules $commit &&
                        git ls-files --others
                ) > "$tempdir"/tree-state || exit
                git update-index --add --replace --remove --stdin \
@@ -463,11 +462,11 @@ if [ "$filter_tag_name" ]; then
                                                "$new_sha1" "$new_ref"
                                git cat-file tag "$ref" |
                                sed -n \
-                                   -e "1,/^$/{
+                                   -e '1,/^$/{
                                          /^object /d
                                          /^type /d
                                          /^tag /d
-                                       }" \
+                                       }' \
                                    -e '/^-----BEGIN PGP SIGNATURE-----/q' \
                                    -e 'p' ) |
                                git mktag) ||
index b8e6456208d8cccb669eb125cfb2ac677443db82..6a65f255cc63cc7a6d0ae0fc0ce4b65298a40e82 100755 (executable)
@@ -320,7 +320,7 @@ EOF
        else
                # plain-old CGI
                resolve_full_httpd
-               list_mods=$(echo "$full_httpd" | sed "s/-f$/-l/")
+               list_mods=$(echo "$full_httpd" | sed 's/-f$/-l/')
                $list_mods | sane_grep 'mod_cgi\.c' >/dev/null 2>&1 || \
                if test -f "$module_path/mod_cgi.so"
                then
index 5b6278572a48569a806a5a976cdd08eb7c117fb7..51dd0d67ba951873df4f26b1707a1e8a713082b9 100644 (file)
@@ -1,3 +1,4 @@
+#!/bin/sh
 # git-mergetool--lib is a library for common merge tool functions
 diff_mode() {
        test "$TOOL_MODE" = diff
index 9e69ada413bd81948591a9e99287f97499573673..38331a861106c63bf5f421dbe03f4aafe949812e 100755 (executable)
@@ -13,11 +13,33 @@ set_reflog_action "pull $*"
 require_work_tree
 cd_to_toplevel
 
-test -z "$(git ls-files -u)" ||
-       die "You are in the middle of a conflicted merge."
+
+die_conflict () {
+    git diff-index --cached --name-status -r --ignore-submodules HEAD --
+    if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
+       die "Pull is not possible because you have unmerged files.
+Please, fix them up in the work tree, and then use 'git add/rm <file>'
+as appropriate to mark resolution, or use 'git commit -a'."
+    else
+       die "Pull is not possible because you have unmerged files."
+    fi
+}
+
+die_merge () {
+    if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
+       die "You have not concluded your merge (MERGE_HEAD exists).
+Please, commit your changes before you can merge."
+    else
+       die "You have not concluded your merge (MERGE_HEAD exists)."
+    fi
+}
+
+test -z "$(git ls-files -u)" || die_conflict
+test -f "$GIT_DIR/MERGE_HEAD" && die_merge
 
 strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
 log_arg= verbosity=
+merge_args=
 curr_branch=$(git symbolic-ref -q HEAD)
 curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||")
 rebase=$(git config --bool branch.$curr_branch_short.rebase)
@@ -62,6 +84,18 @@ do
                esac
                strategy_args="${strategy_args}-s $strategy "
                ;;
+       -X*)
+               case "$#,$1" in
+               1,-X)
+                       usage ;;
+               *,-X)
+                       xx="-X $(git rev-parse --sq-quote "$2")"
+                       shift ;;
+               *,*)
+                       xx=$(git rev-parse --sq-quote "$1") ;;
+               esac
+               merge_args="$merge_args$xx "
+               ;;
        -r|--r|--re|--reb|--reba|--rebas|--rebase)
                rebase=true
                ;;
@@ -233,8 +267,15 @@ then
 fi
 
 merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
-test true = "$rebase" &&
-       exec git-rebase $diffstat $strategy_args --onto $merge_head \
-       ${oldremoteref:-$merge_head}
-exec git-merge $diffstat $no_commit $squash $no_ff $ff_only $log_arg $strategy_args \
-       "$merge_name" HEAD $merge_head $verbosity
+case "$rebase" in
+true)
+       eval="git-rebase $diffstat $strategy_args $merge_args"
+       eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
+       ;;
+*)
+       eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only"
+       eval="$eval  $log_arg $strategy_args $merge_args"
+       eval="$eval \"\$merge_name\" HEAD $merge_head $verbosity"
+       ;;
+esac
+eval "exec $eval"
index 6ed57e2664f72dde2d248a30b89dc66540837a32..3e4fd1456f1ebb4aabb61de6d7f13f820ae2abdc 100755 (executable)
@@ -28,24 +28,81 @@ abort              abort rebasing process and restore original branch
 skip               skip current patch and continue rebasing process
 no-verify          override pre-rebase hook from stopping the operation
 root               rebase all reachable commmits up to the root(s)
+autosquash         move commits that begin with squash!/fixup! under -i
 "
 
 . git-sh-setup
 require_work_tree
 
 DOTEST="$GIT_DIR/rebase-merge"
+
+# The file containing rebase commands, comments, and empty lines.
+# This file is created by "git rebase -i" then edited by the user.  As
+# the lines are processed, they are removed from the front of this
+# file and written to the tail of $DONE.
 TODO="$DOTEST"/git-rebase-todo
+
+# The rebase command lines that have already been processed.  A line
+# is moved here when it is first handled, before any associated user
+# actions.
 DONE="$DOTEST"/done
+
+# The commit message that is planned to be used for any changes that
+# need to be committed following a user interaction.
 MSG="$DOTEST"/message
+
+# The file into which is accumulated the suggested commit message for
+# squash/fixup commands.  When the first of a series of squash/fixups
+# is seen, the file is created and the commit message from the
+# previous commit and from the first squash/fixup commit are written
+# to it.  The commit message for each subsequent squash/fixup commit
+# is appended to the file as it is processed.
+#
+# The first line of the file is of the form
+#     # This is a combination of $COUNT commits.
+# where $COUNT is the number of commits whose messages have been
+# written to the file so far (including the initial "pick" commit).
+# Each time that a commit message is processed, this line is read and
+# updated.  It is deleted just before the combined commit is made.
 SQUASH_MSG="$DOTEST"/message-squash
+
+# If the current series of squash/fixups has not yet included a squash
+# command, then this file exists and holds the commit message of the
+# original "pick" commit.  (If the series ends without a "squash"
+# command, then this can be used as the commit message of the combined
+# commit without opening the editor.)
+FIXUP_MSG="$DOTEST"/message-fixup
+
+# $REWRITTEN is the name of a directory containing files for each
+# commit that is reachable by at least one merge base of $HEAD and
+# $UPSTREAM. They are not necessarily rewritten, but their children
+# might be.  This ensures that commits on merged, but otherwise
+# unrelated side branches are left alone. (Think "X" in the man page's
+# example.)
 REWRITTEN="$DOTEST"/rewritten
+
 DROPPED="$DOTEST"/dropped
+
+# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
+# GIT_AUTHOR_DATE that will be used for the commit that is currently
+# being rebased.
+AUTHOR_SCRIPT="$DOTEST"/author-script
+
+# When an "edit" rebase command is being processed, the SHA1 of the
+# commit to be edited is recorded in this file.  When "git rebase
+# --continue" is executed, if there are any staged changes then they
+# will be amended to the HEAD commit, but only provided the HEAD
+# commit is still the commit to be edited.  When any other rebase
+# command is processed, this file is deleted.
+AMEND="$DOTEST"/amend
+
 PRESERVE_MERGES=
 STRATEGY=
 ONTO=
 VERBOSE=
 OK_TO_SKIP_PRE_REBASE=
 REBASE_ROOT=
+AUTOSQUASH=
 
 GIT_CHERRY_PICK_HELP="  After resolving the conflicts,
 mark the corrected paths with 'git add <paths>', and
@@ -70,6 +127,11 @@ output () {
        esac
 }
 
+# Output the commit message for the specified commit.
+commit_message () {
+       git cat-file commit "$1" | sed "1,/^$/d"
+}
+
 run_pre_rebase_hook () {
        if test -z "$OK_TO_SKIP_PRE_REBASE" &&
           test -x "$GIT_DIR/hooks/pre-rebase"
@@ -129,10 +191,10 @@ make_patch () {
                echo "Root commit"
                ;;
        esac > "$DOTEST"/patch
-       test -f "$DOTEST"/message ||
-               git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message
-       test -f "$DOTEST"/author-script ||
-               get_author_ident_from_commit "$1" > "$DOTEST"/author-script
+       test -f "$MSG" ||
+               commit_message "$1" > "$MSG"
+       test -f "$AUTHOR_SCRIPT" ||
+               get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT"
 }
 
 die_with_patch () {
@@ -150,13 +212,22 @@ has_action () {
        sane_grep '^[^#]' "$1" >/dev/null
 }
 
+# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
+# GIT_AUTHOR_DATE exported from the current environment.
+do_with_author () {
+       (
+               export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+               "$@"
+       )
+}
+
 pick_one () {
        no_ff=
        case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
        output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
        test -d "$REWRITTEN" &&
                pick_one_preserving_merges "$@" && return
-       if test ! -z "$REBASE_ROOT"
+       if test -n "$REBASE_ROOT"
        then
                output git cherry-pick "$@"
                return
@@ -164,11 +235,10 @@ pick_one () {
        parent_sha1=$(git rev-parse --verify $sha1^) ||
                die "Could not get the parent of $sha1"
        current_sha1=$(git rev-parse --verify HEAD)
-       if test "$no_ff$current_sha1" = "$parent_sha1"; then
+       if test -z "$no_ff" && test "$current_sha1" = "$parent_sha1"
+       then
                output git reset --hard $sha1
-               test "a$1" = a-n && output git reset --soft $current_sha1
-               sha1=$(git rev-parse --short $sha1)
-               output warn Fast-forward to $sha1
+               output warn Fast-forward to $(git rev-parse --short $sha1)
        else
                output git cherry-pick "$@"
        fi
@@ -269,14 +339,11 @@ pick_one_preserving_merges () {
                        # redo merge
                        author_script=$(get_author_ident_from_commit $sha1)
                        eval "$author_script"
-                       msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')"
+                       msg="$(commit_message $sha1)"
                        # No point in merging the first parent, that's HEAD
                        new_parents=${new_parents# $first_parent}
-                       if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
-                               GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
-                               GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
-                               output git merge $STRATEGY -m "$msg" \
-                                       $new_parents
+                       if ! do_with_author output \
+                               git merge $STRATEGY -m "$msg" $new_parents
                        then
                                printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
                                die_with_patch $sha1 "Error redoing merge $sha1"
@@ -300,34 +367,66 @@ nth_string () {
        esac
 }
 
-make_squash_message () {
+update_squash_messages () {
        if test -f "$SQUASH_MSG"; then
-               COUNT=$(($(sed -n "s/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p" \
-                       < "$SQUASH_MSG" | sed -ne '$p')+1))
-               echo "# This is a combination of $COUNT commits."
-               sed -e 1d -e '2,/^./{
-                       /^$/d
-               }' <"$SQUASH_MSG"
+               mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit
+               COUNT=$(($(sed -n \
+                       -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \
+                       -e "q" < "$SQUASH_MSG".bak)+1))
+               {
+                       echo "# This is a combination of $COUNT commits."
+                       sed -e 1d -e '2,/^./{
+                               /^$/d
+                       }' <"$SQUASH_MSG".bak
+               } >"$SQUASH_MSG"
        else
+               commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG"
                COUNT=2
-               echo "# This is a combination of two commits."
-               echo "# The first commit's message is:"
-               echo
-               git cat-file commit HEAD | sed -e '1,/^$/d'
+               {
+                       echo "# This is a combination of 2 commits."
+                       echo "# The first commit's message is:"
+                       echo
+                       cat "$FIXUP_MSG"
+               } >"$SQUASH_MSG"
        fi
-       echo
-       echo "# This is the $(nth_string $COUNT) commit message:"
-       echo
-       git cat-file commit $1 | sed -e '1,/^$/d'
+       case $1 in
+       squash)
+               rm -f "$FIXUP_MSG"
+               echo
+               echo "# This is the $(nth_string $COUNT) commit message:"
+               echo
+               commit_message $2
+               ;;
+       fixup)
+               echo
+               echo "# The $(nth_string $COUNT) commit message will be skipped:"
+               echo
+               commit_message $2 | sed -e 's/^/#       /'
+               ;;
+       esac >>"$SQUASH_MSG"
 }
 
 peek_next_command () {
-       sed -n -e "/^#/d" -e "/^$/d" -e "s/ .*//p" -e "q" < "$TODO"
+       sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$TODO"
+}
+
+# A squash/fixup has failed.  Prepare the long version of the squash
+# commit message, then die_with_patch.  This code path requires the
+# user to edit the combined commit message for all commits that have
+# been squashed/fixedup so far.  So also erase the old squash
+# messages, effectively causing the combined commit to be used as the
+# new basis for any further squash/fixups.  Args: sha1 rest
+die_failed_squash() {
+       mv "$SQUASH_MSG" "$MSG" || exit
+       rm -f "$FIXUP_MSG"
+       cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit
+       warn
+       warn "Could not apply $1... $2"
+       die_with_patch $1 ""
 }
 
 do_next () {
-       rm -f "$DOTEST"/message "$DOTEST"/author-script \
-               "$DOTEST"/amend || exit
+       rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
        read command sha1 rest < "$TODO"
        case "$command" in
        '#'*|''|noop)
@@ -355,7 +454,7 @@ do_next () {
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
                make_patch $sha1
-               git rev-parse --verify HEAD > "$DOTEST"/amend
+               git rev-parse --verify HEAD > "$AMEND"
                warn "Stopped at $sha1... $rest"
                warn "You can amend the commit now, with"
                warn
@@ -367,52 +466,49 @@ do_next () {
                warn
                exit 0
                ;;
-       squash|s)
-               comment_for_reflog squash
+       squash|s|fixup|f)
+               case "$command" in
+               squash|s)
+                       squash_style=squash
+                       ;;
+               fixup|f)
+                       squash_style=fixup
+                       ;;
+               esac
+               comment_for_reflog $squash_style
 
                test -f "$DONE" && has_action "$DONE" ||
-                       die "Cannot 'squash' without a previous commit"
+                       die "Cannot '$squash_style' without a previous commit"
 
                mark_action_done
-               make_squash_message $sha1 > "$MSG"
-               failed=f
+               update_squash_messages $squash_style $sha1
                author_script=$(get_author_ident_from_commit HEAD)
+               echo "$author_script" > "$AUTHOR_SCRIPT"
+               eval "$author_script"
                output git reset --soft HEAD^
-               pick_one -n $sha1 || failed=t
+               pick_one -n $sha1 || die_failed_squash $sha1 "$rest"
                case "$(peek_next_command)" in
-               squash|s)
-                       USE_OUTPUT=output
-                       MSG_OPT=-F
-                       EDIT_OR_FILE="$MSG"
-                       cp "$MSG" "$SQUASH_MSG"
+               squash|s|fixup|f)
+                       # This is an intermediate commit; its message will only be
+                       # used in case of trouble.  So use the long version:
+                       do_with_author output git commit --no-verify -F "$SQUASH_MSG" ||
+                               die_failed_squash $sha1 "$rest"
                        ;;
                *)
-                       USE_OUTPUT=
-                       MSG_OPT=
-                       EDIT_OR_FILE=-e
-                       rm -f "$SQUASH_MSG" || exit
-                       cp "$MSG" "$GIT_DIR"/SQUASH_MSG
-                       rm -f "$GIT_DIR"/MERGE_MSG || exit
+                       # This is the final command of this squash/fixup group
+                       if test -f "$FIXUP_MSG"
+                       then
+                               do_with_author git commit --no-verify -F "$FIXUP_MSG" ||
+                                       die_failed_squash $sha1 "$rest"
+                       else
+                               cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit
+                               rm -f "$GIT_DIR"/MERGE_MSG
+                               do_with_author git commit --no-verify -e ||
+                                       die_failed_squash $sha1 "$rest"
+                       fi
+                       rm -f "$SQUASH_MSG" "$FIXUP_MSG"
                        ;;
                esac
-               echo "$author_script" > "$DOTEST"/author-script
-               if test $failed = f
-               then
-                       # This is like --amend, but with a different message
-                       eval "$author_script"
-                       GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
-                       GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
-                       GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
-                       $USE_OUTPUT git commit --no-verify \
-                               $MSG_OPT "$EDIT_OR_FILE" || failed=t
-               fi
-               if test $failed = t
-               then
-                       cp "$MSG" "$GIT_DIR"/MERGE_MSG
-                       warn
-                       warn "Could not apply $sha1... $rest"
-                       die_with_patch $sha1 ""
-               fi
                ;;
        *)
                warn "Unknown command: $command $sha1 $rest"
@@ -495,6 +591,56 @@ get_saved_options () {
        test -f "$DOTEST"/rebase-root && REBASE_ROOT=t
 }
 
+# Rearrange the todo list that has both "pick sha1 msg" and
+# "pick sha1 fixup!/squash! msg" appears in it so that the latter
+# comes immediately after the former, and change "pick" to
+# "fixup"/"squash".
+rearrange_squash () {
+       sed -n -e 's/^pick \([0-9a-f]*\) \(squash\)! /\1 \2 /p' \
+               -e 's/^pick \([0-9a-f]*\) \(fixup\)! /\1 \2 /p' \
+               "$1" >"$1.sq"
+       test -s "$1.sq" || return
+
+       used=
+       while read pick sha1 message
+       do
+               case " $used" in
+               *" $sha1 "*) continue ;;
+               esac
+               echo "$pick $sha1 $message"
+               while read squash action msg
+               do
+                       case "$message" in
+                       "$msg"*)
+                               echo "$action $squash $action! $msg"
+                               used="$used$squash "
+                               ;;
+                       esac
+               done <"$1.sq"
+       done >"$1.rearranged" <"$1"
+       cat "$1.rearranged" >"$1"
+       rm -f "$1.sq" "$1.rearranged"
+}
+
+LF='
+'
+parse_onto () {
+       case "$1" in
+       *...*)
+               if      left=${1%...*} right=${1#*...} &&
+                       onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
+               then
+                       case "$onto" in
+                       ?*"$LF"?* | '')
+                               exit 1 ;;
+                       esac
+                       echo "$onto"
+                       exit 0
+               fi
+       esac
+       git rev-parse --verify "$1^0"
+}
+
 while test $# != 0
 do
        case "$1" in
@@ -522,21 +668,20 @@ do
                then
                        : Nothing to commit -- skip this
                else
-                       . "$DOTEST"/author-script ||
+                       . "$AUTHOR_SCRIPT" ||
                                die "Cannot find the author identity"
                        amend=
-                       if test -f "$DOTEST"/amend
+                       if test -f "$AMEND"
                        then
                                amend=$(git rev-parse --verify HEAD)
-                               test "$amend" = $(cat "$DOTEST"/amend) ||
+                               test "$amend" = $(cat "$AMEND") ||
                                die "\
 You have uncommitted changes in your working tree. Please, commit them
 first and then run 'git rebase --continue' again."
                                git reset --soft HEAD^ ||
                                die "Cannot rewind the HEAD"
                        fi
-                       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE &&
-                       git commit --no-verify -F "$DOTEST"/message -e || {
+                       do_with_author git commit --no-verify -F "$MSG" -e || {
                                test -n "$amend" && git reset --soft $amend
                                die "Could not commit staged changes."
                        }
@@ -600,9 +745,12 @@ first and then run 'git rebase --continue' again."
        --root)
                REBASE_ROOT=t
                ;;
+       --autosquash)
+               AUTOSQUASH=t
+               ;;
        --onto)
                shift
-               ONTO=$(git rev-parse --verify "$1") ||
+               ONTO=$(parse_onto "$1") ||
                        die "Does not point to a valid commit: $1"
                ;;
        --)
@@ -660,13 +808,6 @@ first and then run 'git rebase --continue' again."
                test t = "$VERBOSE" && : > "$DOTEST"/verbose
                if test t = "$PRESERVE_MERGES"
                then
-                       # $REWRITTEN contains files for each commit that is
-                       # reachable by at least one merge base of $HEAD and
-                       # $UPSTREAM. They are not necessarily rewritten, but
-                       # their children might be.
-                       # This ensures that commits on merged, but otherwise
-                       # unrelated side branches are left alone. (Think "X"
-                       # in the man page's example.)
                        if test -z "$REBASE_ROOT"
                        then
                                mkdir "$REWRITTEN" &&
@@ -759,6 +900,7 @@ first and then run 'git rebase --continue' again."
                fi
 
                test -s "$TODO" || echo noop >> "$TODO"
+               test -n "$AUTOSQUASH" && rearrange_squash "$TODO"
                cat >> "$TODO" << EOF
 
 # Rebase $SHORTREVISIONS onto $SHORTONTO
@@ -768,6 +910,7 @@ first and then run 'git rebase --continue' again."
 #  r, reword = use commit, but edit the commit message
 #  e, edit = use commit, but stop for amending
 #  s, squash = use commit, but meld into previous commit
+#  f, fixup = like "squash", but discard this commit's log message
 #
 # If you remove a line here THAT COMMIT WILL BE LOST.
 # However, if you remove everything, the rebase will be aborted.
index b121f4537ccb173d9f289734f1fe2e89b28d7562..fb4fef7b1d6f7abb08fca562ecaad6e36f671768 100755 (executable)
@@ -34,6 +34,8 @@ set_reflog_action rebase
 require_work_tree
 cd_to_toplevel
 
+LF='
+'
 OK_TO_SKIP_PRE_REBASE=
 RESOLVEMSG="
 When you have resolved this problem run \"git rebase --continue\".
@@ -50,6 +52,7 @@ diffstat=$(git config --bool rebase.stat)
 git_am_opt=
 rebase_root=
 force_rebase=
+allow_rerere_autoupdate=
 
 continue_merge () {
        test -n "$prev_head" || die "prev_head must be defined"
@@ -82,10 +85,8 @@ continue_merge () {
                        printf "Already applied: %0${prec}d " $msgnum
                fi
        fi
-       if test -z "$GIT_QUIET"
-       then
-               git rev-list --pretty=oneline -1 "$cmt" | sed -e 's/^[^ ]* //'
-       fi
+       test -z "$GIT_QUIET" &&
+       GIT_PAGER='' git log --format=%s -1 "$cmt"
 
        prev_head=`git rev-parse HEAD^0`
        # save the resulting commit so we can read-tree on it later
@@ -118,7 +119,7 @@ call_merge () {
                return
                ;;
        1)
-               git rerere
+               git rerere $allow_rerere_autoupdate
                die "$RESOLVEMSG"
                ;;
        2)
@@ -349,6 +350,9 @@ do
        -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase)
                force_rebase=t
                ;;
+       --rerere-autoupdate|--no-rerere-autoupdate)
+               allow_rerere_autoupdate="$1"
+               ;;
        -*)
                usage
                ;;
@@ -417,7 +421,27 @@ fi
 
 # Make sure the branch to rebase onto is valid.
 onto_name=${newbase-"$upstream_name"}
-onto=$(git rev-parse --verify "${onto_name}^0") || exit
+case "$onto_name" in
+*...*)
+       if      left=${onto_name%...*} right=${onto_name#*...} &&
+               onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD})
+       then
+               case "$onto" in
+               ?*"$LF"?*)
+                       die "$onto_name: there are more than one merge bases"
+                       ;;
+               '')
+                       die "$onto_name: there is no merge base"
+                       ;;
+               esac
+       else
+               die "$onto_name: there is no merge base"
+       fi
+       ;;
+*)
+       onto=$(git rev-parse --verify "${onto_name}^0") || exit
+       ;;
+esac
 
 # If a hook exists, give it a chance to interrupt
 run_pre_rebase_hook "$upstream_arg" "$@"
index 319b5356713b02a48508ee9af2d14781d8760e94..e05455f74c7e23c28cae41b68fa80df87c633ce9 100755 (executable)
@@ -71,7 +71,7 @@ git send-email [options] <file | directory | rev-list options >
     --suppress-cc           <str>  * author, self, sob, cc, cccmd, body, bodycc, all.
     --[no-]signed-off-by-cc        * Send to Signed-off-by: addresses. Default on.
     --[no-]suppress-from           * Send to self. Default off.
-    --[no-]chain-reply-to          * Chain In-Reply-To: fields. Default on.
+    --[no-]chain-reply-to          * Chain In-Reply-To: fields. Default off.
     --[no-]thread                  * Use In-Reply-To: field. Default on.
 
   Administering:
@@ -221,10 +221,10 @@ sub chain_reply_to {
        if (defined $chain_reply_to &&
            $chain_reply_to eq $not_set_by_user) {
                print STDERR
-                   "In git 1.7.0, the default will be changed to --no-chain-reply-to\n" .
+                   "In git 1.7.0, the default has changed to --no-chain-reply-to\n" .
                    "Set sendemail.chainreplyto configuration variable to true if\n" .
                    "you want to keep --chain-reply-to as your default.\n";
-               $chain_reply_to = 1;
+               $chain_reply_to = 0;
        }
        return $chain_reply_to;
 }
index dfcb8078f508d6cc26d312d61b64e0185e90b74b..d56426dd396190a07de7d661cd22fb284b759ca8 100755 (executable)
@@ -120,20 +120,11 @@ is_bare_repository () {
 }
 
 cd_to_toplevel () {
-       cdup=$(git rev-parse --show-cdup)
-       if test ! -z "$cdup"
-       then
-               # The "-P" option says to follow "physical" directory
-               # structure instead of following symbolic links.  When cdup is
-               # "../", this means following the ".." entry in the current
-               # directory instead textually removing a symlink path element
-               # from the PWD shell variable.  The "-P" behavior is more
-               # consistent with the C-style chdir used by most of Git.
-               cd -P "$cdup" || {
-                       echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
-                       exit 1
-               }
-       fi
+       cdup=$(git rev-parse --show-toplevel) &&
+       cd "$cdup" || {
+               echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
+               exit 1
+       }
 }
 
 require_work_tree () {
index 77d223292c5b167a8a33b04b10b46426b68e0576..664f21721cb876eed7da167744066d834521c825 100755 (executable)
@@ -688,7 +688,11 @@ cmd_summary() {
                echo
        done |
        if test -n "$for_status"; then
-               echo "# Modified submodules:"
+               if [ -n "$files" ]; then
+                       echo "# Submodules changed but not updated:"
+               else
+                       echo "# Submodule changes to be committed:"
+               fi
                echo "#"
                sed -e 's|^|# |' -e 's|^# $|#|'
        else
index 650c9e5f02ead07351629d6572e82c3a9ac7ef92..265852f4596bfe5aeca12be06f78631320b8ebb4 100755 (executable)
@@ -26,6 +26,7 @@ if (! exists $ENV{SVN_SSH}) {
                $ENV{SVN_SSH} = $ENV{GIT_SSH};
                if ($^O eq 'msys') {
                        $ENV{SVN_SSH} =~ s/\\/\\\\/g;
+                       $ENV{SVN_SSH} =~ s/(.*)/"$1"/;
                }
        }
 }
@@ -115,6 +116,7 @@ my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared,
                  'use-svm-props' => sub { $icv{useSvmProps} = 1 },
                  'use-svnsync-props' => sub { $icv{useSvnsyncProps} = 1 },
                  'rewrite-root=s' => sub { $icv{rewriteRoot} = $_[1] },
+                 'rewrite-uuid=s' => sub { $icv{rewriteUUID} = $_[1] },
                   %remote_opts );
 my %cmt_opts = ( 'edit|e' => \$_edit,
                'rmdir' => \$SVN::Git::Editor::_rmdir,
@@ -155,12 +157,16 @@ my %cmd = (
                    { 'message|m=s' => \$_message,
                      'destination|d=s' => \$_branch_dest,
                      'dry-run|n' => \$_dry_run,
-                     'tag|t' => \$_tag } ],
+                     'tag|t' => \$_tag,
+                     'username=s' => \$Git::SVN::Prompt::_username,
+                     'commit-url=s' => \$_commit_url } ],
        tag => [ sub { $_tag = 1; cmd_branch(@_) },
                 'Create a tag in the SVN repository',
                 { 'message|m=s' => \$_message,
                   'destination|d=s' => \$_branch_dest,
-                  'dry-run|n' => \$_dry_run } ],
+                  'dry-run|n' => \$_dry_run,
+                  'username=s' => \$Git::SVN::Prompt::_username,
+                  'commit-url=s' => \$_commit_url } ],
        'set-tree' => [ \&cmd_set_tree,
                        "Set an SVN repository to a git tree-ish",
                        { 'stdin' => \$_stdin, %cmt_opts, %fc_opts, } ],
@@ -708,7 +714,21 @@ sub cmd_branch {
                }
        }
        my ($lft, $rgt) = @{ $glob->{path} }{qw/left right/};
-       my $dst = join '/', $remote->{url}, $lft, $branch_name, ($rgt || ());
+       my $url;
+       if (defined $_commit_url) {
+               $url = $_commit_url;
+       } else {
+               $url = eval { command_oneline('config', '--get',
+                       "svn-remote.$gs->{repo_id}.commiturl") };
+               if (!$url) {
+                       $url = $remote->{url};
+               }
+       }
+       my $dst = join '/', $url, $lft, $branch_name, ($rgt || ());
+
+       if ($dst =~ /^https:/ && $src =~ /^http:/) {
+               $src=~s/^http:/https:/;
+       }
 
        my $ctx = SVN::Client->new(
                auth    => Git::SVN::Ra::_auth_providers(),
@@ -1636,6 +1656,7 @@ use File::Path qw/mkpath/;
 use File::Copy qw/copy/;
 use IPC::Open3;
 use Memoize;  # core since 5.8.0, Jul 2002
+use Memoize::Storable;
 
 my ($_gc_nr, $_gc_period);
 
@@ -1806,8 +1827,8 @@ sub read_all_remotes {
                        my $rs = {
                            t => $t,
                            remote => $remote,
-                           path => Git::SVN::GlobSpec->new($local_ref),
-                           ref => Git::SVN::GlobSpec->new($remote_ref) };
+                           path => Git::SVN::GlobSpec->new($local_ref, 1),
+                           ref => Git::SVN::GlobSpec->new($remote_ref, 0) };
                        if (length($rs->{ref}->{right}) != 0) {
                                die "The '*' glob character must be the last ",
                                    "character of '$remote_ref'\n";
@@ -2189,6 +2210,10 @@ sub svnsync {
                die "Can't have both 'useSvnsyncProps' and 'rewriteRoot' ",
                    "options set!\n";
        }
+       if ($self->rewrite_uuid) {
+               die "Can't have both 'useSvnsyncProps' and 'rewriteUUID' ",
+                   "options set!\n";
+       }
 
        my $svnsync;
        # see if we have it in our config, first:
@@ -2470,6 +2495,20 @@ sub rewrite_root {
        $self->{-rewrite_root} = $rwr;
 }
 
+sub rewrite_uuid {
+       my ($self) = @_;
+       return $self->{-rewrite_uuid} if exists $self->{-rewrite_uuid};
+       my $k = "svn-remote.$self->{repo_id}.rewriteUUID";
+       my $rwid = eval { command_oneline(qw/config --get/, $k) };
+       if ($rwid) {
+               $rwid =~ s#/+$##;
+               if ($rwid !~ m#^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$#) {
+                       die "$rwid is not a valid UUID (key: $k)\n";
+               }
+       }
+       $self->{-rewrite_uuid} = $rwid;
+}
+
 sub metadata_url {
        my ($self) = @_;
        ($self->rewrite_root || $self->{url}) .
@@ -3052,12 +3091,65 @@ sub check_cherry_pick {
        for my $range ( @ranges ) {
                delete @commits{_rev_list($range)};
        }
+       for my $commit (keys %commits) {
+               if (has_no_changes($commit)) {
+                       delete $commits{$commit};
+               }
+       }
        return (keys %commits);
 }
 
-BEGIN {
-       memoize 'lookup_svn_merge';
-       memoize 'check_cherry_pick';
+sub has_no_changes {
+       my $commit = shift;
+
+       my @revs = split / /, command_oneline(
+               qw(rev-list --parents -1 -m), $commit);
+
+       # Commits with no parents, e.g. the start of a partial branch,
+       # have changes by definition.
+       return 1 if (@revs < 2);
+
+       # Commits with multiple parents, e.g a merge, have no changes
+       # by definition.
+       return 0 if (@revs > 2);
+
+       return (command_oneline("rev-parse", "$commit^{tree}") eq
+               command_oneline("rev-parse", "$commit~1^{tree}"));
+}
+
+# The GIT_DIR environment variable is not always set until after the command
+# line arguments are processed, so we can't memoize in a BEGIN block.
+{
+       my $memoized = 0;
+
+       sub memoize_svn_mergeinfo_functions {
+               return if $memoized;
+               $memoized = 1;
+
+               my $cache_path = "$ENV{GIT_DIR}/svn/.caches/";
+               mkpath([$cache_path]) unless -d $cache_path;
+
+               tie my %lookup_svn_merge_cache => 'Memoize::Storable',
+                   "$cache_path/lookup_svn_merge.db", 'nstore';
+               memoize 'lookup_svn_merge',
+                       SCALAR_CACHE => 'FAULT',
+                       LIST_CACHE => ['HASH' => \%lookup_svn_merge_cache],
+               ;
+
+               tie my %check_cherry_pick_cache => 'Memoize::Storable',
+                   "$cache_path/check_cherry_pick.db", 'nstore';
+               memoize 'check_cherry_pick',
+                       SCALAR_CACHE => 'FAULT',
+                       LIST_CACHE => ['HASH' => \%check_cherry_pick_cache],
+               ;
+
+               tie my %has_no_changes_cache => 'Memoize::Storable',
+                   "$cache_path/has_no_changes.db", 'nstore';
+               memoize 'has_no_changes',
+                       SCALAR_CACHE => ['HASH' => \%has_no_changes_cache],
+                       LIST_CACHE => 'FAULT',
+               ;
+       }
 }
 
 sub parents_exclude {
@@ -3101,6 +3193,8 @@ sub find_extra_svn_parents {
        my ($self, $ed, $mergeinfo, $parents) = @_;
        # aha!  svk:merge property changed...
 
+       memoize_svn_mergeinfo_functions();
+
        # We first search for merged tips which are not in our
        # history.  Then, we figure out which git revisions are in
        # that tip, but not this revision.  If all of those revisions
@@ -3134,10 +3228,21 @@ sub find_extra_svn_parents {
                my $ranges = $ranges{$merge_tip};
 
                # check out 'new' tips
-               my $merge_base = command_oneline(
-                       "merge-base",
-                       @$parents, $merge_tip,
-                      );
+               my $merge_base;
+               eval {
+                       $merge_base = command_oneline(
+                               "merge-base",
+                               @$parents, $merge_tip,
+                       );
+               };
+               if ($@) {
+                       die "An error occurred during merge-base"
+                               unless $@->isa("Git::Error::Command");
+
+                       warn "W: Cannot find common ancestor between ".
+                            "@$parents and $merge_tip. Ignoring merge info.\n";
+                       next;
+               }
 
                # double check that there are no missing non-merge commits
                my (@incomplete) = check_cherry_pick(
@@ -3253,6 +3358,10 @@ sub make_log_entry {
                        die "Can't have both 'useSvmProps' and 'rewriteRoot' ",
                            "options set!\n";
                }
+               if ($self->rewrite_uuid) {
+                       die "Can't have both 'useSvmProps' and 'rewriteUUID' ",
+                           "options set!\n";
+               }
                my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}i;
                # we don't want "SVM: initializing mirror for junk" ...
                return undef if $r == 0;
@@ -3283,10 +3392,10 @@ sub make_log_entry {
        } else {
                my $url = $self->metadata_url;
                remove_username($url);
-               $log_entry{metadata} = "$url\@$rev " .
-                                      $self->ra->get_uuid;
-               $email ||= "$author\@" . $self->ra->get_uuid;
-               $commit_email ||= "$author\@" . $self->ra->get_uuid;
+               my $uuid = $self->rewrite_uuid || $self->ra->get_uuid;
+               $log_entry{metadata} = "$url\@$rev " . $uuid;
+               $email ||= "$author\@" . $uuid;
+               $commit_email ||= "$author\@" . $uuid;
        }
        $log_entry{name} = $name;
        $log_entry{email} = $email;
@@ -3368,7 +3477,7 @@ sub rebuild {
                                '--');
        my $metadata_url = $self->metadata_url;
        remove_username($metadata_url);
-       my $svn_uuid = $self->ra_uuid;
+       my $svn_uuid = $self->rewrite_uuid || $self->ra_uuid;
        my $c;
        while (<$log>) {
                if ( m{^commit ($::sha1)$} ) {
@@ -5157,6 +5266,7 @@ sub match_globs {
                        next if (length $g->{path}->{right} &&
                                 ($self->check_path($p, $r) !=
                                  $SVN::Node::dir));
+                       next unless $p =~ /$g->{path}->{regex}/;
                        $exists->{$p} = Git::SVN->init($self->{url}, $p, undef,
                                         $g->{ref}->full_path($de), 1);
                }
@@ -5930,29 +6040,48 @@ use strict;
 use warnings;
 
 sub new {
-       my ($class, $glob) = @_;
+       my ($class, $glob, $pattern_ok) = @_;
        my $re = $glob;
        $re =~ s!/+$!!g; # no need for trailing slashes
-       $re =~ m!^([^*]*)(\*(?:/\*)*)(.*)$!;
-       my $temp = $re;
-       my ($left, $right) = ($1, $3);
-       $re = $2;
-       my $depth = $re =~ tr/*/*/;
-       if ($depth != $temp =~ tr/*/*/) {
-               die "Only one set of wildcard directories " .
-                       "(e.g. '*' or '*/*/*') is supported: '$glob'\n";
+       my (@left, @right, @patterns);
+       my $state = "left";
+       my $die_msg = "Only one set of wildcard directories " .
+                               "(e.g. '*' or '*/*/*') is supported: '$glob'\n";
+       for my $part (split(m|/|, $glob)) {
+               if ($part =~ /\*/ && $part ne "*") {
+                       die "Invalid pattern in '$glob': $part\n";
+               } elsif ($pattern_ok && $part =~ /[{}]/ &&
+                        $part !~ /^\{[^{}]+\}/) {
+                       die "Invalid pattern in '$glob': $part\n";
+               }
+               if ($part eq "*") {
+                       die $die_msg if $state eq "right";
+                       $state = "pattern";
+                       push(@patterns, "[^/]*");
+               } elsif ($pattern_ok && $part =~ /^\{(.*)\}$/) {
+                       die $die_msg if $state eq "right";
+                       $state = "pattern";
+                       my $p = quotemeta($1);
+                       $p =~ s/\\,/|/g;
+                       push(@patterns, "(?:$p)");
+               } else {
+                       if ($state eq "left") {
+                               push(@left, $part);
+                       } else {
+                               push(@right, $part);
+                               $state = "right";
+                       }
+               }
        }
+       my $depth = @patterns;
        if ($depth == 0) {
-               die "One '*' is needed for glob: '$glob'\n";
-       }
-       $re =~ s!\*!\[^/\]*!g;
-       $re = quotemeta($left) . "($re)" . quotemeta($right);
-       if (length $left && !($left =~ s!/+$!!g)) {
-               die "Missing trailing '/' on left side of: '$glob' ($left)\n";
-       }
-       if (length $right && !($right =~ s!^/+!!g)) {
-               die "Missing leading '/' on right side of: '$glob' ($right)\n";
+               die "One '*' is needed in glob: '$glob'\n";
        }
+       my $left = join('/', @left);
+       my $right = join('/', @right);
+       $re = join('/', @patterns);
+       $re = join('\/',
+                  grep(length, quotemeta($left), "($re)", quotemeta($right)));
        my $left_re = qr/^\/\Q$left\E(\/|$)/;
        bless { left => $left, right => $right, left_regex => $left_re,
                regex => qr/$re/, glob => $glob, depth => $depth }, $class;
diff --git a/git.c b/git.c
index 11544cdb4037715b1e9edc14689896d99d6e3284..4c3028c098c03c842d2a635d054d3c27653dd511 100644 (file)
--- a/git.c
+++ b/git.c
@@ -318,7 +318,9 @@ static void handle_internal_command(int argc, const char **argv)
                { "gc", cmd_gc, RUN_SETUP },
                { "get-tar-commit-id", cmd_get_tar_commit_id },
                { "grep", cmd_grep, RUN_SETUP | USE_PAGER },
+               { "hash-object", cmd_hash_object },
                { "help", cmd_help },
+               { "index-pack", cmd_index_pack },
                { "init", cmd_init_db },
                { "init-db", cmd_init_db },
                { "log", cmd_log, RUN_SETUP | USE_PAGER },
@@ -330,13 +332,20 @@ static void handle_internal_command(int argc, const char **argv)
                { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
                { "merge-base", cmd_merge_base, RUN_SETUP },
                { "merge-file", cmd_merge_file },
+               { "merge-index", cmd_merge_index, RUN_SETUP },
                { "merge-ours", cmd_merge_ours, RUN_SETUP },
                { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+               { "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+               { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
                { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+               { "merge-tree", cmd_merge_tree, RUN_SETUP },
+               { "mktag", cmd_mktag, RUN_SETUP },
                { "mktree", cmd_mktree, RUN_SETUP },
                { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
                { "name-rev", cmd_name_rev, RUN_SETUP },
                { "pack-objects", cmd_pack_objects, RUN_SETUP },
+               { "pack-redundant", cmd_pack_redundant, RUN_SETUP },
+               { "patch-id", cmd_patch_id },
                { "peek-remote", cmd_ls_remote },
                { "pickaxe", cmd_blame, RUN_SETUP },
                { "prune", cmd_prune, RUN_SETUP },
@@ -363,11 +372,13 @@ static void handle_internal_command(int argc, const char **argv)
                { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
                { "tag", cmd_tag, RUN_SETUP },
                { "tar-tree", cmd_tar_tree },
+               { "unpack-file", cmd_unpack_file, RUN_SETUP },
                { "unpack-objects", cmd_unpack_objects, RUN_SETUP },
                { "update-index", cmd_update_index, RUN_SETUP },
                { "update-ref", cmd_update_ref, RUN_SETUP },
                { "update-server-info", cmd_update_server_info, RUN_SETUP },
                { "upload-archive", cmd_upload_archive },
+               { "var", cmd_var },
                { "verify-tag", cmd_verify_tag, RUN_SETUP },
                { "version", cmd_version },
                { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
index ab224f7eae858912f1bc13b5dfba4181fe6cfdc6..ee74a5eed7758f1267441d85619969a121fb3cec 100644 (file)
@@ -98,6 +98,7 @@ BuildRequires:  perl(Error)
 Perl interface to Git
 
 %define path_settings ETC_GITCONFIG=/etc/gitconfig prefix=%{_prefix} mandir=%{_mandir} htmldir=%{_docdir}/%{name}-%{version}
+%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
 
 %prep
 %setup -q
@@ -112,6 +113,7 @@ rm -rf $RPM_BUILD_ROOT
 make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" DESTDIR=$RPM_BUILD_ROOT \
      %{path_settings} \
      INSTALLDIRS=vendor install %{!?_without_docs: install-doc}
+test ! -d $RPM_BUILD_ROOT%{python_sitelib} || rm -fr $RPM_BUILD_ROOT%{python_sitelib}
 find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
@@ -190,6 +192,12 @@ rm -rf $RPM_BUILD_ROOT
 # No files for you!
 
 %changelog
+* Sun Jan 31 2010 Junio C Hamano <gitster@pobox.com>
+- Do not use %define inside %{!?...} construct.
+
+* Sat Jan 30 2010 Junio C Hamano <gitster@pobox.com>
+- We don't ship Python bits until a real foreign scm interface comes.
+
 * Mon Feb 04 2009 David J. Mellor <dmellor@whistlingcat.com>
 - fixed broken git help -w after renaming the git-core package to git.
 
diff --git a/git_remote_helpers/.gitignore b/git_remote_helpers/.gitignore
new file mode 100644 (file)
index 0000000..2247d5f
--- /dev/null
@@ -0,0 +1,2 @@
+/build
+/dist
diff --git a/git_remote_helpers/Makefile b/git_remote_helpers/Makefile
new file mode 100644 (file)
index 0000000..c62dfd0
--- /dev/null
@@ -0,0 +1,35 @@
+#
+# Makefile for the git_remote_helpers python support modules
+#
+pysetupfile:=setup.py
+
+# Shell quote (do not use $(call) to accommodate ancient setups);
+DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+
+ifndef PYTHON_PATH
+       PYTHON_PATH = /usr/bin/python
+endif
+ifndef prefix
+       prefix = $(HOME)
+endif
+ifndef V
+       QUIET = @
+       QUIETSETUP = --quiet
+endif
+
+PYLIBDIR=$(shell $(PYTHON_PATH) -c \
+        "import sys; \
+        print 'lib/python%i.%i/site-packages' % sys.version_info[:2]")
+
+all: $(pysetupfile)
+       $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) build
+
+install: $(pysetupfile)
+       $(PYTHON_PATH) $(pysetupfile) install --prefix $(DESTDIR_SQ)$(prefix)
+
+instlibdir: $(pysetupfile)
+       @echo "$(DESTDIR_SQ)$(prefix)/$(PYLIBDIR)"
+
+clean:
+       $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) clean -a
+       $(RM) *.pyo *.pyc
diff --git a/git_remote_helpers/__init__.py b/git_remote_helpers/__init__.py
new file mode 100644 (file)
index 0000000..00f69cb
--- /dev/null
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+"""Support library package for git remote helpers.
+
+Git remote helpers are helper commands that interfaces with a non-git
+repository to provide automatic import of non-git history into a Git
+repository.
+
+This package provides the support library needed by these helpers..
+The following modules are included:
+
+- git.git - Interaction with Git repositories
+
+- util - General utility functionality use by the other modules in
+         this package, and also used directly by the helpers.
+"""
diff --git a/git_remote_helpers/git/__init__.py b/git_remote_helpers/git/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/git_remote_helpers/git/git.py b/git_remote_helpers/git/git.py
new file mode 100644 (file)
index 0000000..a383e6c
--- /dev/null
@@ -0,0 +1,678 @@
+#!/usr/bin/env python
+
+"""Functionality for interacting with Git repositories.
+
+This module provides classes for interfacing with a Git repository.
+"""
+
+import os
+import re
+import time
+from binascii import hexlify
+from cStringIO import StringIO
+import unittest
+
+from git_remote_helpers.util import debug, error, die, start_command, run_command
+
+
+def get_git_dir ():
+    """Return the path to the GIT_DIR for this repo."""
+    args = ("git", "rev-parse", "--git-dir")
+    exit_code, output, errors = run_command(args)
+    if exit_code:
+        die("Failed to retrieve git dir")
+    assert not errors
+    return output.strip()
+
+
+def parse_git_config ():
+    """Return a dict containing the parsed version of 'git config -l'."""
+    exit_code, output, errors = run_command(("git", "config", "-z", "-l"))
+    if exit_code:
+        die("Failed to retrieve git configuration")
+    assert not errors
+    return dict([e.split('\n', 1) for e in output.split("\0") if e])
+
+
+def git_config_bool (value):
+    """Convert the given git config string value to True or False.
+
+    Raise ValueError if the given string was not recognized as a
+    boolean value.
+
+    """
+    norm_value = str(value).strip().lower()
+    if norm_value in ("true", "1", "yes", "on", ""):
+        return True
+    if norm_value in ("false", "0", "no", "off", "none"):
+        return False
+    raise ValueError("Failed to parse '%s' into a boolean value" % (value))
+
+
+def valid_git_ref (ref_name):
+    """Return True iff the given ref name is a valid git ref name."""
+    # The following is a reimplementation of the git check-ref-format
+    # command.  The rules were derived from the git check-ref-format(1)
+    # manual page.  This code should be replaced by a call to
+    # check_ref_format() in the git library, when such is available.
+    if ref_name.endswith('/') or \
+       ref_name.startswith('.') or \
+       ref_name.count('/.') or \
+       ref_name.count('..') or \
+       ref_name.endswith('.lock'):
+        return False
+    for c in ref_name:
+        if ord(c) < 0x20 or ord(c) == 0x7f or c in " ~^:?*[":
+            return False
+    return True
+
+
+class GitObjectFetcher(object):
+
+    """Provide parsed access to 'git cat-file --batch'.
+
+    This provides a read-only interface to the Git object database.
+
+    """
+
+    def __init__ (self):
+        """Initiate a 'git cat-file --batch' session."""
+        self.queue = []  # List of object names to be submitted
+        self.in_transit = None  # Object name currently in transit
+
+        # 'git cat-file --batch' produces binary output which is likely
+        # to be corrupted by the default "rU"-mode pipe opened by
+        # start_command.  (Mode == "rU" does universal new-line
+        # conversion, which mangles carriage returns.) Therefore, we
+        # open an explicitly binary-safe pipe for transferring the
+        # output from 'git cat-file --batch'.
+        pipe_r_fd, pipe_w_fd = os.pipe()
+        pipe_r = os.fdopen(pipe_r_fd, "rb")
+        pipe_w = os.fdopen(pipe_w_fd, "wb")
+        self.proc = start_command(("git", "cat-file", "--batch"),
+                                  stdout = pipe_w)
+        self.f = pipe_r
+
+    def __del__ (self):
+        """Verify completed communication with 'git cat-file --batch'."""
+        assert not self.queue
+        assert self.in_transit is None
+        self.proc.stdin.close()
+        assert self.proc.wait() == 0  # Zero exit code
+        assert self.f.read() == ""  # No remaining output
+
+    def _submit_next_object (self):
+        """Submit queue items to the 'git cat-file --batch' process.
+
+        If there are items in the queue, and there is currently no item
+        currently in 'transit', then pop the first item off the queue,
+        and submit it.
+
+        """
+        if self.queue and self.in_transit is None:
+            self.in_transit = self.queue.pop(0)
+            print >> self.proc.stdin, self.in_transit[0]
+
+    def push (self, obj, callback):
+        """Push the given object name onto the queue.
+
+        The given callback function will at some point in the future
+        be called exactly once with the following arguments:
+        - self - this GitObjectFetcher instance
+        - obj  - the object name provided to push()
+        - sha1 - the SHA1 of the object, if 'None' obj is missing
+        - t    - the type of the object (tag/commit/tree/blob)
+        - size - the size of the object in bytes
+        - data - the object contents
+
+        """
+        self.queue.append((obj, callback))
+        self._submit_next_object()  # (Re)start queue processing
+
+    def process_next_entry (self):
+        """Read the next entry off the queue and invoke callback."""
+        obj, cb = self.in_transit
+        self.in_transit = None
+        header = self.f.readline()
+        if header == "%s missing\n" % (obj):
+            cb(self, obj, None, None, None, None)
+            return
+        sha1, t, size = header.split(" ")
+        assert len(sha1) == 40
+        assert t in ("tag", "commit", "tree", "blob")
+        assert size.endswith("\n")
+        size = int(size.strip())
+        data = self.f.read(size)
+        assert self.f.read(1) == "\n"
+        cb(self, obj, sha1, t, size, data)
+        self._submit_next_object()
+
+    def process (self):
+        """Process the current queue until empty."""
+        while self.in_transit is not None:
+            self.process_next_entry()
+
+    # High-level convenience methods:
+
+    def get_sha1 (self, objspec):
+        """Return the SHA1 of the object specified by 'objspec'.
+
+        Return None if 'objspec' does not specify an existing object.
+
+        """
+        class _ObjHandler(object):
+            """Helper class for getting the returned SHA1."""
+            def __init__ (self, parser):
+                self.parser = parser
+                self.sha1 = None
+
+            def __call__ (self, parser, obj, sha1, t, size, data):
+                # FIXME: Many unused arguments. Could this be cheaper?
+                assert parser == self.parser
+                self.sha1 = sha1
+
+        handler = _ObjHandler(self)
+        self.push(objspec, handler)
+        self.process()
+        return handler.sha1
+
+    def open_obj (self, objspec):
+        """Return a file object wrapping the contents of a named object.
+
+        The caller is responsible for calling .close() on the returned
+        file object.
+
+        Raise KeyError if 'objspec' does not exist in the repo.
+
+        """
+        class _ObjHandler(object):
+            """Helper class for parsing the returned git object."""
+            def __init__ (self, parser):
+                """Set up helper."""
+                self.parser = parser
+                self.contents = StringIO()
+                self.err = None
+
+            def __call__ (self, parser, obj, sha1, t, size, data):
+                """Git object callback (see GitObjectFetcher documentation)."""
+                assert parser == self.parser
+                if not sha1:  # Missing object
+                    self.err = "Missing object '%s'" % obj
+                else:
+                    assert size == len(data)
+                    self.contents.write(data)
+
+        handler = _ObjHandler(self)
+        self.push(objspec, handler)
+        self.process()
+        if handler.err:
+            raise KeyError(handler.err)
+        handler.contents.seek(0)
+        return handler.contents
+
+    def walk_tree (self, tree_objspec, callback, prefix = ""):
+        """Recursively walk the given Git tree object.
+
+        Recursively walk all subtrees of the given tree object, and
+        invoke the given callback passing three arguments:
+        (path, mode, data) with the path, permission bits, and contents
+        of all the blobs found in the entire tree structure.
+
+        """
+        class _ObjHandler(object):
+            """Helper class for walking a git tree structure."""
+            def __init__ (self, parser, cb, path, mode = None):
+                """Set up helper."""
+                self.parser = parser
+                self.cb = cb
+                self.path = path
+                self.mode = mode
+                self.err = None
+
+            def parse_tree (self, treedata):
+                """Parse tree object data, yield tree entries.
+
+                Each tree entry is a 3-tuple (mode, sha1, path)
+
+                self.path is prepended to all paths yielded
+                from this method.
+
+                """
+                while treedata:
+                    mode = int(treedata[:6], 10)
+                    # Turn 100xxx into xxx
+                    if mode > 100000:
+                        mode -= 100000
+                    assert treedata[6] == " "
+                    i = treedata.find("\0", 7)
+                    assert i > 0
+                    path = treedata[7:i]
+                    sha1 = hexlify(treedata[i + 1: i + 21])
+                    yield (mode, sha1, self.path + path)
+                    treedata = treedata[i + 21:]
+
+            def __call__ (self, parser, obj, sha1, t, size, data):
+                """Git object callback (see GitObjectFetcher documentation)."""
+                assert parser == self.parser
+                if not sha1:  # Missing object
+                    self.err = "Missing object '%s'" % (obj)
+                    return
+                assert size == len(data)
+                if t == "tree":
+                    if self.path:
+                        self.path += "/"
+                    # Recurse into all blobs and subtrees
+                    for m, s, p in self.parse_tree(data):
+                        parser.push(s,
+                                    self.__class__(self.parser, self.cb, p, m))
+                elif t == "blob":
+                    self.cb(self.path, self.mode, data)
+                else:
+                    raise ValueError("Unknown object type '%s'" % (t))
+
+        self.push(tree_objspec, _ObjHandler(self, callback, prefix))
+        self.process()
+
+
+class GitRefMap(object):
+
+    """Map Git ref names to the Git object names they currently point to.
+
+    Behaves like a dictionary of Git ref names -> Git object names.
+
+    """
+
+    def __init__ (self, obj_fetcher):
+        """Create a new Git ref -> object map."""
+        self.obj_fetcher = obj_fetcher
+        self._cache = {}  # dict: refname -> objname
+
+    def _load (self, ref):
+        """Retrieve the object currently bound to the given ref.
+
+        The name of the object pointed to by the given ref is stored
+        into this mapping, and also returned.
+
+        """
+        if ref not in self._cache:
+            self._cache[ref] = self.obj_fetcher.get_sha1(ref)
+        return self._cache[ref]
+
+    def __contains__ (self, refname):
+        """Return True if the given refname is present in this cache."""
+        return bool(self._load(refname))
+
+    def __getitem__ (self, refname):
+        """Return the git object name pointed to by the given refname."""
+        commit = self._load(refname)
+        if commit is None:
+            raise KeyError("Unknown ref '%s'" % (refname))
+        return commit
+
+    def get (self, refname, default = None):
+        """Return the git object name pointed to by the given refname."""
+        commit = self._load(refname)
+        if commit is None:
+            return default
+        return commit
+
+
+class GitFICommit(object):
+
+    """Encapsulate the data in a Git fast-import commit command."""
+
+    SHA1RE = re.compile(r'^[0-9a-f]{40}$')
+
+    @classmethod
+    def parse_mode (cls, mode):
+        """Verify the given git file mode, and return it as a string."""
+        assert mode in (644, 755, 100644, 100755, 120000)
+        return "%i" % (mode)
+
+    @classmethod
+    def parse_objname (cls, objname):
+        """Return the given object name (or mark number) as a string."""
+        if isinstance(objname, int):  # Object name is a mark number
+            assert objname > 0
+            return ":%i" % (objname)
+
+        # No existence check is done, only checks for valid format
+        assert cls.SHA1RE.match(objname)  # Object name is valid SHA1
+        return objname
+
+    @classmethod
+    def quote_path (cls, path):
+        """Return a quoted version of the given path."""
+        path = path.replace("\\", "\\\\")
+        path = path.replace("\n", "\\n")
+        path = path.replace('"', '\\"')
+        return '"%s"' % (path)
+
+    @classmethod
+    def parse_path (cls, path):
+        """Verify that the given path is valid, and quote it, if needed."""
+        assert not isinstance(path, int)  # Cannot be a mark number
+
+        # These checks verify the rules on the fast-import man page
+        assert not path.count("//")
+        assert not path.endswith("/")
+        assert not path.startswith("/")
+        assert not path.count("/./")
+        assert not path.count("/../")
+        assert not path.endswith("/.")
+        assert not path.endswith("/..")
+        assert not path.startswith("./")
+        assert not path.startswith("../")
+
+        if path.count('"') + path.count('\n') + path.count('\\'):
+            return cls.quote_path(path)
+        return path
+
+    def __init__ (self, name, email, timestamp, timezone, message):
+        """Create a new Git fast-import commit, with the given metadata."""
+        self.name = name
+        self.email = email
+        self.timestamp = timestamp
+        self.timezone = timezone
+        self.message = message
+        self.pathops = []  # List of path operations in this commit
+
+    def modify (self, mode, blobname, path):
+        """Add a file modification to this Git fast-import commit."""
+        self.pathops.append(("M",
+                             self.parse_mode(mode),
+                             self.parse_objname(blobname),
+                             self.parse_path(path)))
+
+    def delete (self, path):
+        """Add a file deletion to this Git fast-import commit."""
+        self.pathops.append(("D", self.parse_path(path)))
+
+    def copy (self, path, newpath):
+        """Add a file copy to this Git fast-import commit."""
+        self.pathops.append(("C",
+                             self.parse_path(path),
+                             self.parse_path(newpath)))
+
+    def rename (self, path, newpath):
+        """Add a file rename to this Git fast-import commit."""
+        self.pathops.append(("R",
+                             self.parse_path(path),
+                             self.parse_path(newpath)))
+
+    def note (self, blobname, commit):
+        """Add a note object to this Git fast-import commit."""
+        self.pathops.append(("N",
+                             self.parse_objname(blobname),
+                             self.parse_objname(commit)))
+
+    def deleteall (self):
+        """Delete all files in this Git fast-import commit."""
+        self.pathops.append("deleteall")
+
+
+class TestGitFICommit(unittest.TestCase):
+
+    """GitFICommit selftests."""
+
+    def test_basic (self):
+        """GitFICommit basic selftests."""
+
+        def expect_fail (method, data):
+            """Verify that the method(data) raises an AssertionError."""
+            try:
+                method(data)
+            except AssertionError:
+                return
+            raise AssertionError("Failed test for invalid data '%s(%s)'" %
+                                 (method.__name__, repr(data)))
+
+    def test_parse_mode (self):
+        """GitFICommit.parse_mode() selftests."""
+        self.assertEqual(GitFICommit.parse_mode(644), "644")
+        self.assertEqual(GitFICommit.parse_mode(755), "755")
+        self.assertEqual(GitFICommit.parse_mode(100644), "100644")
+        self.assertEqual(GitFICommit.parse_mode(100755), "100755")
+        self.assertEqual(GitFICommit.parse_mode(120000), "120000")
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, 0)
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, 123)
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, 600)
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, "644")
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, "abc")
+
+    def test_parse_objname (self):
+        """GitFICommit.parse_objname() selftests."""
+        self.assertEqual(GitFICommit.parse_objname(1), ":1")
+        self.assertRaises(AssertionError, GitFICommit.parse_objname, 0)
+        self.assertRaises(AssertionError, GitFICommit.parse_objname, -1)
+        self.assertEqual(GitFICommit.parse_objname("0123456789" * 4),
+                         "0123456789" * 4)
+        self.assertEqual(GitFICommit.parse_objname("2468abcdef" * 4),
+                         "2468abcdef" * 4)
+        self.assertRaises(AssertionError, GitFICommit.parse_objname,
+                          "abcdefghij" * 4)
+
+    def test_parse_path (self):
+        """GitFICommit.parse_path() selftests."""
+        self.assertEqual(GitFICommit.parse_path("foo/bar"), "foo/bar")
+        self.assertEqual(GitFICommit.parse_path("path/with\n and \" in it"),
+                         '"path/with\\n and \\" in it"')
+        self.assertRaises(AssertionError, GitFICommit.parse_path, 1)
+        self.assertRaises(AssertionError, GitFICommit.parse_path, 0)
+        self.assertRaises(AssertionError, GitFICommit.parse_path, -1)
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo//bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "/foo/bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/./bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/../bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/.")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/..")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "./foo/bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "../foo/bar")
+
+
+class GitFastImport(object):
+
+    """Encapsulate communication with git fast-import."""
+
+    def __init__ (self, f, obj_fetcher, last_mark = 0):
+        """Set up self to communicate with a fast-import process through f."""
+        self.f = f  # File object where fast-import stream is written
+        self.obj_fetcher = obj_fetcher  # GitObjectFetcher instance
+        self.next_mark = last_mark + 1  # Next mark number
+        self.refs = set()  # Keep track of the refnames we've seen
+
+    def comment (self, s):
+        """Write the given comment in the fast-import stream."""
+        assert "\n" not in s, "Malformed comment: '%s'" % (s)
+        self.f.write("# %s\n" % (s))
+
+    def commit (self, ref, commitdata):
+        """Make a commit on the given ref, with the given GitFICommit.
+
+        Return the mark number identifying this commit.
+
+        """
+        self.f.write("""\
+commit %(ref)s
+mark :%(mark)i
+committer %(name)s <%(email)s> %(timestamp)i %(timezone)s
+data %(msgLength)i
+%(msg)s
+""" % {
+    'ref': ref,
+    'mark': self.next_mark,
+    'name': commitdata.name,
+    'email': commitdata.email,
+    'timestamp': commitdata.timestamp,
+    'timezone': commitdata.timezone,
+    'msgLength': len(commitdata.message),
+    'msg': commitdata.message,
+})
+
+        if ref not in self.refs:
+            self.refs.add(ref)
+            parent = ref + "^0"
+            if self.obj_fetcher.get_sha1(parent):
+                self.f.write("from %s\n" % (parent))
+
+        for op in commitdata.pathops:
+            self.f.write(" ".join(op))
+            self.f.write("\n")
+        self.f.write("\n")
+        retval = self.next_mark
+        self.next_mark += 1
+        return retval
+
+    def blob (self, data):
+        """Import the given blob.
+
+        Return the mark number identifying this blob.
+
+        """
+        self.f.write("blob\nmark :%i\ndata %i\n%s\n" %
+                     (self.next_mark, len(data), data))
+        retval = self.next_mark
+        self.next_mark += 1
+        return retval
+
+    def reset (self, ref, objname):
+        """Reset the given ref to point at the given Git object."""
+        self.f.write("reset %s\nfrom %s\n\n" %
+                     (ref, GitFICommit.parse_objname(objname)))
+        if ref not in self.refs:
+            self.refs.add(ref)
+
+
+class GitNotes(object):
+
+    """Encapsulate access to Git notes.
+
+    Simulates a dictionary of object name (SHA1) -> Git note mappings.
+
+    """
+
+    def __init__ (self, notes_ref, obj_fetcher):
+        """Create a new Git notes interface, bound to the given notes ref."""
+        self.notes_ref = notes_ref
+        self.obj_fetcher = obj_fetcher  # Used to get objects from repo
+        self.imports = []  # list: (objname, note data blob name) tuples
+
+    def __del__ (self):
+        """Verify that self.commit_notes() was called before destruction."""
+        if self.imports:
+            error("Missing call to self.commit_notes().")
+            error("%i notes are not committed!", len(self.imports))
+
+    def _load (self, objname):
+        """Return the note data associated with the given git object.
+
+        The note data is returned in string form. If no note is found
+        for the given object, None is returned.
+
+        """
+        try:
+            f = self.obj_fetcher.open_obj("%s:%s" % (self.notes_ref, objname))
+            ret = f.read()
+            f.close()
+        except KeyError:
+            ret = None
+        return ret
+
+    def __getitem__ (self, objname):
+        """Return the note contents associated with the given object.
+
+        Raise KeyError if given object has no associated note.
+
+        """
+        blobdata = self._load(objname)
+        if blobdata is None:
+            raise KeyError("Object '%s' has no note" % (objname))
+        return blobdata
+
+    def get (self, objname, default = None):
+        """Return the note contents associated with the given object.
+
+        Return given default if given object has no associated note.
+
+        """
+        blobdata = self._load(objname)
+        if blobdata is None:
+            return default
+        return blobdata
+
+    def import_note (self, objname, data, gfi):
+        """Tell git fast-import to store data as a note for objname.
+
+        This method uses the given GitFastImport object to create a
+        blob containing the given note data.  Also an entry mapping the
+        given object name to the created blob is stored until
+        commit_notes() is called.
+
+        Note that this method only works if it is later followed by a
+        call to self.commit_notes() (which produces the note commit
+        that refers to the blob produced here).
+
+        """
+        if not data.endswith("\n"):
+            data += "\n"
+        gfi.comment("Importing note for object %s" % (objname))
+        mark = gfi.blob(data)
+        self.imports.append((objname, mark))
+
+    def commit_notes (self, gfi, author, message):
+        """Produce a git fast-import note commit for the imported notes.
+
+        This method uses the given GitFastImport object to create a
+        commit on the notes ref, introducing the notes previously
+        submitted to import_note().
+
+        """
+        if not self.imports:
+            return
+        commitdata = GitFICommit(author[0], author[1],
+                                 time.time(), "0000", message)
+        for objname, blobname in self.imports:
+            assert isinstance(objname, int) and objname > 0
+            assert isinstance(blobname, int) and blobname > 0
+            commitdata.note(blobname, objname)
+        gfi.commit(self.notes_ref, commitdata)
+        self.imports = []
+
+
+class GitCachedNotes(GitNotes):
+
+    """Encapsulate access to Git notes (cached version).
+
+    Only use this class if no caching is done at a higher level.
+
+    Simulates a dictionary of object name (SHA1) -> Git note mappings.
+
+    """
+
+    def __init__ (self, notes_ref, obj_fetcher):
+        """Set up a caching wrapper around GitNotes."""
+        GitNotes.__init__(self, notes_ref, obj_fetcher)
+        self._cache = {}  # Cache: object name -> note data
+
+    def __del__ (self):
+        """Verify that GitNotes' destructor is called."""
+        GitNotes.__del__(self)
+
+    def _load (self, objname):
+        """Extend GitNotes._load() with a local objname -> note cache."""
+        if objname not in self._cache:
+            self._cache[objname] = GitNotes._load(self, objname)
+        return self._cache[objname]
+
+    def import_note (self, objname, data, gfi):
+        """Extend GitNotes.import_note() with a local objname -> note cache."""
+        if not data.endswith("\n"):
+            data += "\n"
+        assert objname not in self._cache
+        self._cache[objname] = data
+        GitNotes.import_note(self, objname, data, gfi)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/git_remote_helpers/setup.py b/git_remote_helpers/setup.py
new file mode 100644 (file)
index 0000000..4d434b6
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+"""Distutils build/install script for the git_remote_helpers package."""
+
+from distutils.core import setup
+
+setup(
+    name = 'git_remote_helpers',
+    version = '0.1.0',
+    description = 'Git remote helper program for non-git repositories',
+    license = 'GPLv2',
+    author = 'The Git Community',
+    author_email = 'git@vger.kernel.org',
+    url = 'http://www.git-scm.com/',
+    package_dir = {'git_remote_helpers': ''},
+    packages = ['git_remote_helpers', 'git_remote_helpers.git'],
+)
diff --git a/git_remote_helpers/util.py b/git_remote_helpers/util.py
new file mode 100644 (file)
index 0000000..dce83e6
--- /dev/null
@@ -0,0 +1,194 @@
+#!/usr/bin/env python
+
+"""Misc. useful functionality used by the rest of this package.
+
+This module provides common functionality used by the other modules in
+this package.
+
+"""
+
+import sys
+import os
+import subprocess
+
+
+# Whether or not to show debug messages
+DEBUG = False
+
+def notify(msg, *args):
+    """Print a message to stderr."""
+    print >> sys.stderr, msg % args
+
+def debug (msg, *args):
+    """Print a debug message to stderr when DEBUG is enabled."""
+    if DEBUG:
+        print >> sys.stderr, msg % args
+
+def error (msg, *args):
+    """Print an error message to stderr."""
+    print >> sys.stderr, "ERROR:", msg % args
+
+def warn(msg, *args):
+    """Print a warning message to stderr."""
+    print >> sys.stderr, "warning:", msg % args
+
+def die (msg, *args):
+    """Print as error message to stderr and exit the program."""
+    error(msg, *args)
+    sys.exit(1)
+
+
+class ProgressIndicator(object):
+
+    """Simple progress indicator.
+
+    Displayed as a spinning character by default, but can be customized
+    by passing custom messages that overrides the spinning character.
+
+    """
+
+    States = ("|", "/", "-", "\\")
+
+    def __init__ (self, prefix = "", f = sys.stdout):
+        """Create a new ProgressIndicator, bound to the given file object."""
+        self.n = 0  # Simple progress counter
+        self.f = f  # Progress is written to this file object
+        self.prev_len = 0  # Length of previous msg (to be overwritten)
+        self.prefix = prefix  # Prefix prepended to each progress message
+        self.prefix_lens = [] # Stack of prefix string lengths
+
+    def pushprefix (self, prefix):
+        """Append the given prefix onto the prefix stack."""
+        self.prefix_lens.append(len(self.prefix))
+        self.prefix += prefix
+
+    def popprefix (self):
+        """Remove the last prefix from the prefix stack."""
+        prev_len = self.prefix_lens.pop()
+        self.prefix = self.prefix[:prev_len]
+
+    def __call__ (self, msg = None, lf = False):
+        """Indicate progress, possibly with a custom message."""
+        if msg is None:
+            msg = self.States[self.n % len(self.States)]
+        msg = self.prefix + msg
+        print >> self.f, "\r%-*s" % (self.prev_len, msg),
+        self.prev_len = len(msg.expandtabs())
+        if lf:
+            print >> self.f
+            self.prev_len = 0
+        self.n += 1
+
+    def finish (self, msg = "done", noprefix = False):
+        """Finalize progress indication with the given message."""
+        if noprefix:
+            self.prefix = ""
+        self(msg, True)
+
+
+def start_command (args, cwd = None, shell = False, add_env = None,
+                   stdin = subprocess.PIPE, stdout = subprocess.PIPE,
+                   stderr = subprocess.PIPE):
+    """Start the given command, and return a subprocess object.
+
+    This provides a simpler interface to the subprocess module.
+
+    """
+    env = None
+    if add_env is not None:
+        env = os.environ.copy()
+        env.update(add_env)
+    return subprocess.Popen(args, bufsize = 1, stdin = stdin, stdout = stdout,
+                            stderr = stderr, cwd = cwd, shell = shell,
+                            env = env, universal_newlines = True)
+
+
+def run_command (args, cwd = None, shell = False, add_env = None,
+                 flag_error = True):
+    """Run the given command to completion, and return its results.
+
+    This provides a simpler interface to the subprocess module.
+
+    The results are formatted as a 3-tuple: (exit_code, output, errors)
+
+    If flag_error is enabled, Error messages will be produced if the
+    subprocess terminated with a non-zero exit code and/or stderr
+    output.
+
+    The other arguments are passed on to start_command().
+
+    """
+    process = start_command(args, cwd, shell, add_env)
+    (output, errors) = process.communicate()
+    exit_code = process.returncode
+    if flag_error and errors:
+        error("'%s' returned errors:\n---\n%s---", " ".join(args), errors)
+    if flag_error and exit_code:
+        error("'%s' returned exit code %i", " ".join(args), exit_code)
+    return (exit_code, output, errors)
+
+
+def file_reader_method (missing_ok = False):
+    """Decorator for simplifying reading of files.
+
+    If missing_ok is True, a failure to open a file for reading will
+    not raise the usual IOError, but instead the wrapped method will be
+    called with f == None.  The method must in this case properly
+    handle f == None.
+
+    """
+    def _wrap (method):
+        """Teach given method to handle both filenames and file objects.
+
+        The given method must take a file object as its second argument
+        (the first argument being 'self', of course).  This decorator
+        will take a filename given as the second argument and promote
+        it to a file object.
+
+        """
+        def _wrapped_method (self, filename, *args, **kwargs):
+            if isinstance(filename, file):
+                f = filename
+            else:
+                try:
+                    f = open(filename, 'r')
+                except IOError:
+                    if missing_ok:
+                        f = None
+                    else:
+                        raise
+            try:
+                return method(self, f, *args, **kwargs)
+            finally:
+                if not isinstance(filename, file) and f:
+                    f.close()
+        return _wrapped_method
+    return _wrap
+
+
+def file_writer_method (method):
+    """Decorator for simplifying writing of files.
+
+    Enables the given method to handle both filenames and file objects.
+
+    The given method must take a file object as its second argument
+    (the first argument being 'self', of course).  This decorator will
+    take a filename given as the second argument and promote it to a
+    file object.
+
+    """
+    def _new_method (self, filename, *args, **kwargs):
+        if isinstance(filename, file):
+            f = filename
+        else:
+            # Make sure the containing directory exists
+            parent_dir = os.path.dirname(filename)
+            if not os.path.isdir(parent_dir):
+                os.makedirs(parent_dir)
+            f = open(filename, 'w')
+        try:
+            return method(self, f, *args, **kwargs)
+        finally:
+            if not isinstance(filename, file):
+                f.close()
+    return _new_method
index 364c7a84cbcf923deb72c2a91b4ec5f5d75bf4c3..1f36a3e815865fcc72b171b497f5c4e341e148ee 100644 (file)
@@ -130,7 +130,7 @@ proc unmerged_files {files} {
 }
 
 proc parseviewargs {n arglist} {
-    global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
+    global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs env
 
     set vdatemode($n) 0
     set vmergeonly($n) 0
@@ -210,6 +210,9 @@ proc parseviewargs {n arglist} {
                # git rev-parse doesn't understand --merge
                lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
            }
+           "--no-replace-objects" {
+               set env(GIT_NO_REPLACE_OBJECTS) "1"
+           }
            "-*" {
                # Other flag arguments including -<n>
                if {[string is digit -strict [string range $arg 1 end]]} {
@@ -2085,7 +2088,7 @@ proc makewindow {} {
     set sha1entry .tf.bar.sha1
     set entries $sha1entry
     set sha1but .tf.bar.sha1label
-    button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
+    button $sha1but -text "[mc "SHA1 ID:"] " -state disabled -relief flat \
        -command gotocommit -width 8
     $sha1but conf -disabledforeground [$sha1but cget -foreground]
     pack .tf.bar.sha1label -side left
@@ -2779,7 +2782,7 @@ proc about {} {
     message $w.m -text [mc "
 Gitk - a commit viewer for git
 
-Copyright \u00a9 2005-2009 Paul Mackerras
+Copyright © 2005-2009 Paul Mackerras
 
 Use and redistribute under the terms of the GNU General Public License"] \
            -justify center -aspect 400 -border 2 -bg white -relief groove
@@ -7686,8 +7689,10 @@ proc getblobdiffline {bdf ids} {
            makediffhdr $fname $ids
            $ctext insert end "\n$line\n" filesep
        } elseif {![string compare -length 3 "  >" $line]} {
+           set line [encoding convertfrom $diffencoding $line]
            $ctext insert end "$line\n" dresult
        } elseif {![string compare -length 3 "  <" $line]} {
+           set line [encoding convertfrom $diffencoding $line]
            $ctext insert end "$line\n" d0
        } elseif {$diffinhdr} {
            if {![string compare -length 12 "rename from " $line]} {
@@ -8698,6 +8703,11 @@ proc mktag {} {
     ${NS}::label $top.tlab -text [mc "Tag name:"]
     ${NS}::entry $top.tag -width 60
     grid $top.tlab $top.tag -sticky w
+    ${NS}::label $top.op -text [mc "Tag message is optional"]
+    grid $top.op -columnspan 2 -sticky we
+    ${NS}::label $top.mlab -text [mc "Tag message:"]
+    ${NS}::entry $top.msg -width 60
+    grid $top.mlab $top.msg -sticky w
     ${NS}::frame $top.buts
     ${NS}::button $top.buts.gen -text [mc "Create"] -command mktaggo
     ${NS}::button $top.buts.can -text [mc "Cancel"] -command mktagcan
@@ -8715,6 +8725,7 @@ proc domktag {} {
 
     set id [$mktagtop.sha1 get]
     set tag [$mktagtop.tag get]
+    set msg [$mktagtop.msg get]
     if {$tag == {}} {
        error_popup [mc "No tag name specified"] $mktagtop
        return 0
@@ -8724,7 +8735,11 @@ proc domktag {} {
        return 0
     }
     if {[catch {
-       exec git tag $tag $id
+       if {$msg != {}} {
+           exec git tag -a -m $msg $tag $id
+       } else {
+           exec git tag $tag $id
+       }
     } err]} {
        error_popup "[mc "Error creating tag:"] $err" $mktagtop
        return 0
@@ -10480,7 +10495,7 @@ proc showtag {tag isnew} {
     set linknum 0
     if {![info exists tagcontents($tag)]} {
        catch {
-           set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
+           set tagcontents($tag) [exec git cat-file tag $tag]
        }
     }
     if {[info exists tagcontents($tag)]} {
index 53ef0d635941b54ddc9590f9df309536631114f8..c79aa9cbc813dfe13bb39db2eacd1cc7ab49441c 100644 (file)
@@ -8,33 +8,33 @@ msgid ""
 msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-05-12 21:55+0200\n"
-"PO-Revision-Date: 2009-05-12 22:18+0200\n"
+"POT-Creation-Date: 2010-01-27 20:21+0100\n"
+"PO-Revision-Date: 2010-01-27 20:27+0100\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: gitk:113
+#: gitk:115
 msgid "Couldn't get list of unmerged files:"
 msgstr "Liste der nicht zusammengeführten Dateien nicht gefunden:"
 
-#: gitk:268
+#: gitk:274
 msgid "Error parsing revisions:"
 msgstr "Fehler beim Laden der Versionen:"
 
-#: gitk:323
+#: gitk:329
 msgid "Error executing --argscmd command:"
 msgstr "Fehler beim Ausführen des --argscmd-Kommandos:"
 
-#: gitk:336
+#: gitk:342
 msgid "No files selected: --merge specified but no files are unmerged."
 msgstr ""
 "Keine Dateien ausgewählt: Es wurde --merge angegeben, aber es existieren "
 "keine nicht zusammengeführten Dateien."
 
-#: gitk:339
+#: gitk:345
 msgid ""
 "No files selected: --merge specified but no unmerged files are within file "
 "limit."
@@ -42,719 +42,801 @@ msgstr ""
 "Keine Dateien ausgewählt: Es wurde --merge angegeben, aber es sind keine "
 "nicht zusammengeführten Dateien in der Dateiauswahl."
 
-#: gitk:361 gitk:508
+#: gitk:367 gitk:514
 msgid "Error executing git log:"
 msgstr "Fehler beim Ausführen von »git log«:"
 
-#: gitk:379 gitk:524
+#: gitk:385 gitk:530
 msgid "Reading"
 msgstr "Lesen"
 
-#: gitk:439 gitk:4061
+#: gitk:445 gitk:4261
 msgid "Reading commits..."
 msgstr "Versionen werden gelesen ..."
 
-#: gitk:442 gitk:1560 gitk:4064
+#: gitk:448 gitk:1578 gitk:4264
 msgid "No commits selected"
 msgstr "Keine Versionen ausgewählt"
 
-#: gitk:1436
+#: gitk:1454
 msgid "Can't parse git log output:"
 msgstr "Ausgabe von »git log« kann nicht erkannt werden:"
 
-#: gitk:1656
+#: gitk:1674
 msgid "No commit information available"
 msgstr "Keine Versionsinformation verfügbar"
 
-#: gitk:1791 gitk:1815 gitk:3854 gitk:8714 gitk:10250 gitk:10422
+#: gitk:1816
+msgid "mc"
+msgstr "mc"
+
+#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804
 msgid "OK"
 msgstr "Ok"
 
-#: gitk:1817 gitk:3856 gitk:8311 gitk:8385 gitk:8495 gitk:8544 gitk:8716
-#: gitk:10251 gitk:10423
+#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046
+#: gitk:10586 gitk:10805
 msgid "Cancel"
 msgstr "Abbrechen"
 
-#: gitk:1917
+#: gitk:1975
 msgid "Update"
 msgstr "Aktualisieren"
 
-#: gitk:1918
+#: gitk:1976
 msgid "Reload"
 msgstr "Neu laden"
 
-#: gitk:1919
+#: gitk:1977
 msgid "Reread references"
 msgstr "Zweige neu laden"
 
-#: gitk:1920
+#: gitk:1978
 msgid "List references"
 msgstr "Zweige/Markierungen auflisten"
 
-#: gitk:1922
+#: gitk:1980
 msgid "Start git gui"
 msgstr "»git gui« starten"
 
-#: gitk:1924
+#: gitk:1982
 msgid "Quit"
 msgstr "Beenden"
 
-#: gitk:1916
+#: gitk:1974
 msgid "File"
 msgstr "Datei"
 
-#: gitk:1928
+#: gitk:1986
 msgid "Preferences"
 msgstr "Einstellungen"
 
-#: gitk:1927
+#: gitk:1985
 msgid "Edit"
 msgstr "Bearbeiten"
 
-#: gitk:1932
+#: gitk:1990
 msgid "New view..."
 msgstr "Neue Ansicht ..."
 
-#: gitk:1933
+#: gitk:1991
 msgid "Edit view..."
 msgstr "Ansicht bearbeiten ..."
 
-#: gitk:1934
+#: gitk:1992
 msgid "Delete view"
 msgstr "Ansicht entfernen"
 
-#: gitk:1936
+#: gitk:1994
 msgid "All files"
 msgstr "Alle Dateien"
 
-#: gitk:1931 gitk:3666
+#: gitk:1989 gitk:3808
 msgid "View"
 msgstr "Ansicht"
 
-#: gitk:1941 gitk:1951 gitk:2650
+#: gitk:1999 gitk:2009 gitk:2780
 msgid "About gitk"
 msgstr "Über gitk"
 
-#: gitk:1942 gitk:1956
+#: gitk:2000 gitk:2014
 msgid "Key bindings"
 msgstr "Tastenkürzel"
 
-#: gitk:1940 gitk:1955
+#: gitk:1998 gitk:2013
 msgid "Help"
 msgstr "Hilfe"
 
-#: gitk:2016
-msgid "SHA1 ID: "
-msgstr "SHA1:"
+#: gitk:2091 gitk:8110
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
 
-#: gitk:2047
+#: gitk:2122
 msgid "Row"
 msgstr "Zeile"
 
-#: gitk:2078
+#: gitk:2160
 msgid "Find"
 msgstr "Suche"
 
-#: gitk:2079
+#: gitk:2161
 msgid "next"
 msgstr "nächste"
 
-#: gitk:2080
+#: gitk:2162
 msgid "prev"
 msgstr "vorige"
 
-#: gitk:2081
+#: gitk:2163
 msgid "commit"
 msgstr "Version nach"
 
-#: gitk:2084 gitk:2086 gitk:4222 gitk:4245 gitk:4269 gitk:6210 gitk:6282
-#: gitk:6366
+#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482
+#: gitk:6566
 msgid "containing:"
 msgstr "Beschreibung:"
 
-#: gitk:2087 gitk:3158 gitk:3163 gitk:4297
+#: gitk:2169 gitk:3290 gitk:3295 gitk:4497
 msgid "touching paths:"
 msgstr "Dateien:"
 
-#: gitk:2088 gitk:4302
+#: gitk:2170 gitk:4502
 msgid "adding/removing string:"
 msgstr "Änderungen:"
 
-#: gitk:2097 gitk:2099
+#: gitk:2179 gitk:2181
 msgid "Exact"
 msgstr "Exakt"
 
-#: gitk:2099 gitk:4377 gitk:6178
+#: gitk:2181 gitk:4577 gitk:6378
 msgid "IgnCase"
 msgstr "Kein Groß/Klein"
 
-#: gitk:2099 gitk:4271 gitk:4375 gitk:6174
+#: gitk:2181 gitk:4471 gitk:4575 gitk:6374
 msgid "Regexp"
 msgstr "Regexp"
 
-#: gitk:2101 gitk:2102 gitk:4396 gitk:4426 gitk:4433 gitk:6302 gitk:6370
+#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570
 msgid "All fields"
 msgstr "Alle Felder"
 
-#: gitk:2102 gitk:4394 gitk:4426 gitk:6241
+#: gitk:2184 gitk:4594 gitk:4626 gitk:6441
 msgid "Headline"
 msgstr "Überschrift"
 
-#: gitk:2103 gitk:4394 gitk:6241 gitk:6370 gitk:6804
+#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003
 msgid "Comments"
 msgstr "Beschreibung"
 
-#: gitk:2103 gitk:4394 gitk:4398 gitk:4433 gitk:6241 gitk:6739 gitk:7991
-#: gitk:8006
+#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285
+#: gitk:8300
 msgid "Author"
 msgstr "Autor"
 
-#: gitk:2103 gitk:4394 gitk:6241 gitk:6741
+#: gitk:2185 gitk:4594 gitk:6441 gitk:6940
 msgid "Committer"
 msgstr "Eintragender"
 
-#: gitk:2132
+#: gitk:2216
 msgid "Search"
 msgstr "Suchen"
 
-#: gitk:2139
+#: gitk:2224
 msgid "Diff"
 msgstr "Vergleich"
 
-#: gitk:2141
+#: gitk:2226
 msgid "Old version"
 msgstr "Alte Version"
 
-#: gitk:2143
+#: gitk:2228
 msgid "New version"
 msgstr "Neue Version"
 
-#: gitk:2145
+#: gitk:2230
 msgid "Lines of context"
 msgstr "Kontextzeilen"
 
-#: gitk:2155
+#: gitk:2240
 msgid "Ignore space change"
 msgstr "Leerzeichenänderungen ignorieren"
 
-#: gitk:2213
+#: gitk:2299
 msgid "Patch"
 msgstr "Patch"
 
-#: gitk:2215
+#: gitk:2301
 msgid "Tree"
 msgstr "Baum"
 
-#: gitk:2359 gitk:2376
+#: gitk:2456 gitk:2473
 msgid "Diff this -> selected"
 msgstr "Vergleich: diese -> gewählte"
 
-#: gitk:2360 gitk:2377
+#: gitk:2457 gitk:2474
 msgid "Diff selected -> this"
 msgstr "Vergleich: gewählte -> diese"
 
-#: gitk:2361 gitk:2378
+#: gitk:2458 gitk:2475
 msgid "Make patch"
 msgstr "Patch erstellen"
 
-#: gitk:2362 gitk:8369
+#: gitk:2459 gitk:8692
 msgid "Create tag"
 msgstr "Markierung erstellen"
 
-#: gitk:2363 gitk:8475
+#: gitk:2460 gitk:8808
 msgid "Write commit to file"
 msgstr "Version in Datei schreiben"
 
-#: gitk:2364 gitk:8532
+#: gitk:2461 gitk:8865
 msgid "Create new branch"
 msgstr "Neuen Zweig erstellen"
 
-#: gitk:2365
+#: gitk:2462
 msgid "Cherry-pick this commit"
 msgstr "Diese Version pflücken"
 
-#: gitk:2366
+#: gitk:2463
 msgid "Reset HEAD branch to here"
 msgstr "HEAD-Zweig auf diese Version zurücksetzen"
 
-#: gitk:2367
+#: gitk:2464
 msgid "Mark this commit"
 msgstr "Lesezeichen setzen"
 
-#: gitk:2368
+#: gitk:2465
 msgid "Return to mark"
 msgstr "Zum Lesezeichen"
 
-#: gitk:2369
+#: gitk:2466
 msgid "Find descendant of this and mark"
 msgstr "Abkömmling von Lesezeichen und dieser Version finden"
 
-#: gitk:2370
+#: gitk:2467
 msgid "Compare with marked commit"
 msgstr "Mit Lesezeichen vergleichen"
 
-#: gitk:2384
+#: gitk:2481
 msgid "Check out this branch"
 msgstr "Auf diesen Zweig umstellen"
 
-#: gitk:2385
+#: gitk:2482
 msgid "Remove this branch"
 msgstr "Zweig löschen"
 
-#: gitk:2392
+#: gitk:2489
 msgid "Highlight this too"
 msgstr "Diesen auch hervorheben"
 
-#: gitk:2393
+#: gitk:2490
 msgid "Highlight this only"
 msgstr "Nur diesen hervorheben"
 
-#: gitk:2394
+#: gitk:2491
 msgid "External diff"
 msgstr "Externes Diff-Programm"
 
-#: gitk:2395
+#: gitk:2492
 msgid "Blame parent commit"
 msgstr "Annotieren der Elternversion"
 
-#: gitk:2402
+#: gitk:2499
 msgid "Show origin of this line"
 msgstr "Herkunft dieser Zeile anzeigen"
 
-#: gitk:2403
+#: gitk:2500
 msgid "Run git gui blame on this line"
 msgstr "Diese Zeile annotieren (»git gui blame«)"
 
-#: gitk:2652
+#: gitk:2782
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright ©9 2005-2009 Paul Mackerras\n"
 "\n"
 "Use and redistribute under the terms of the GNU General Public License"
 msgstr ""
 "\n"
 "Gitk - eine Visualisierung der Git-Historie\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright © 2005-2009 Paul Mackerras\n"
 "\n"
-"Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public "
-"License"
+"Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public License"
 
-#: gitk:2660 gitk:2722 gitk:8897
+#: gitk:2790 gitk:2854 gitk:9230
 msgid "Close"
 msgstr "Schließen"
 
-#: gitk:2679
+#: gitk:2811
 msgid "Gitk key bindings"
 msgstr "Gitk-Tastaturbelegung"
 
-#: gitk:2682
+#: gitk:2814
 msgid "Gitk key bindings:"
 msgstr "Gitk-Tastaturbelegung:"
 
-#: gitk:2684
+#: gitk:2816
 #, tcl-format
 msgid "<%s-Q>\t\tQuit"
 msgstr "<%s-Q>\t\tBeenden"
 
-#: gitk:2685
+#: gitk:2817
 msgid "<Home>\t\tMove to first commit"
 msgstr "<Pos1>\t\tZur neuesten Version springen"
 
-#: gitk:2686
+#: gitk:2818
 msgid "<End>\t\tMove to last commit"
 msgstr "<Ende>\t\tZur ältesten Version springen"
 
-#: gitk:2687
+#: gitk:2819
 msgid "<Up>, p, i\tMove up one commit"
 msgstr "<Hoch>, p, i\tNächste neuere Version"
 
-#: gitk:2688
+#: gitk:2820
 msgid "<Down>, n, k\tMove down one commit"
 msgstr "<Runter>, n, k\tNächste ältere Version"
 
-#: gitk:2689
+#: gitk:2821
 msgid "<Left>, z, j\tGo back in history list"
 msgstr "<Links>, z, j\tEine Version zurückgehen"
 
-#: gitk:2690
+#: gitk:2822
 msgid "<Right>, x, l\tGo forward in history list"
 msgstr "<Rechts>, x, l\tEine Version weitergehen"
 
-#: gitk:2691
+#: gitk:2823
 msgid "<PageUp>\tMove up one page in commit list"
 msgstr "<BildHoch>\tEine Seite nach oben blättern"
 
-#: gitk:2692
+#: gitk:2824
 msgid "<PageDown>\tMove down one page in commit list"
 msgstr "<BildRunter>\tEine Seite nach unten blättern"
 
-#: gitk:2693
+#: gitk:2825
 #, tcl-format
 msgid "<%s-Home>\tScroll to top of commit list"
 msgstr "<%s-Pos1>\tZum oberen Ende der Versionsliste blättern"
 
-#: gitk:2694
+#: gitk:2826
 #, tcl-format
 msgid "<%s-End>\tScroll to bottom of commit list"
 msgstr "<%s-Ende>\tZum unteren Ende der Versionsliste blättern"
 
-#: gitk:2695
+#: gitk:2827
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
 msgstr "<%s-Hoch>\tVersionsliste eine Zeile nach oben blättern"
 
-#: gitk:2696
+#: gitk:2828
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
 msgstr "<%s-Runter>\tVersionsliste eine Zeile nach unten blättern"
 
-#: gitk:2697
+#: gitk:2829
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
 msgstr "<%s-BildHoch>\tVersionsliste eine Seite nach oben blättern"
 
-#: gitk:2698
+#: gitk:2830
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
 msgstr "<%s-BildRunter>\tVersionsliste eine Seite nach unten blättern"
 
-#: gitk:2699
+#: gitk:2831
 msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
 msgstr "<Umschalt-Hoch>\tRückwärts suchen (nach oben; neuere Versionen)"
 
-#: gitk:2700
+#: gitk:2832
 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
 msgstr "<Umschalt-Runter> Suchen (nach unten; ältere Versionen)"
 
-#: gitk:2701
+#: gitk:2833
 msgid "<Delete>, b\tScroll diff view up one page"
 msgstr "<Entf>, b\t\tVergleich eine Seite nach oben blättern"
 
-#: gitk:2702
+#: gitk:2834
 msgid "<Backspace>\tScroll diff view up one page"
 msgstr "<Löschtaste>\tVergleich eine Seite nach oben blättern"
 
-#: gitk:2703
+#: gitk:2835
 msgid "<Space>\t\tScroll diff view down one page"
 msgstr "<Leertaste>\tVergleich eine Seite nach unten blättern"
 
-#: gitk:2704
+#: gitk:2836
 msgid "u\t\tScroll diff view up 18 lines"
 msgstr "u\t\tVergleich um 18 Zeilen nach oben blättern"
 
-#: gitk:2705
+#: gitk:2837
 msgid "d\t\tScroll diff view down 18 lines"
 msgstr "d\t\tVergleich um 18 Zeilen nach unten blättern"
 
-#: gitk:2706
+#: gitk:2838
 #, tcl-format
 msgid "<%s-F>\t\tFind"
 msgstr "<%s-F>\t\tSuchen"
 
-#: gitk:2707
+#: gitk:2839
 #, tcl-format
 msgid "<%s-G>\t\tMove to next find hit"
 msgstr "<%s-G>\t\tWeitersuchen"
 
-#: gitk:2708
+#: gitk:2840
 msgid "<Return>\tMove to next find hit"
 msgstr "<Eingabetaste>\tWeitersuchen"
 
-#: gitk:2709
+#: gitk:2841
 msgid "/\t\tFocus the search box"
 msgstr "/\t\tTastaturfokus ins Suchfeld"
 
-#: gitk:2710
+#: gitk:2842
 msgid "?\t\tMove to previous find hit"
 msgstr "?\t\tRückwärts weitersuchen"
 
-#: gitk:2711
+#: gitk:2843
 msgid "f\t\tScroll diff view to next file"
 msgstr "f\t\tVergleich zur nächsten Datei blättern"
 
-#: gitk:2712
+#: gitk:2844
 #, tcl-format
 msgid "<%s-S>\t\tSearch for next hit in diff view"
 msgstr "<%s-S>\t\tWeitersuchen im Vergleich"
 
-#: gitk:2713
+#: gitk:2845
 #, tcl-format
 msgid "<%s-R>\t\tSearch for previous hit in diff view"
 msgstr "<%s-R>\t\tRückwärts weitersuchen im Vergleich"
 
-#: gitk:2714
+#: gitk:2846
 #, tcl-format
 msgid "<%s-KP+>\tIncrease font size"
 msgstr "<%s-Nummerblock-Plus>\tSchrift vergrößern"
 
-#: gitk:2715
+#: gitk:2847
 #, tcl-format
 msgid "<%s-plus>\tIncrease font size"
 msgstr "<%s-Plus>\tSchrift vergrößern"
 
-#: gitk:2716
+#: gitk:2848
 #, tcl-format
 msgid "<%s-KP->\tDecrease font size"
 msgstr "<%s-Nummernblock-Minus> Schrift verkleinern"
 
-#: gitk:2717
+#: gitk:2849
 #, tcl-format
 msgid "<%s-minus>\tDecrease font size"
 msgstr "<%s-Minus>\tSchrift verkleinern"
 
-#: gitk:2718
+#: gitk:2850
 msgid "<F5>\t\tUpdate"
 msgstr "<F5>\t\tAktualisieren"
 
-#: gitk:3173
-#, tcl-format
-msgid "Error getting \"%s\" from %s:"
-msgstr "Fehler beim Holen von »%s« von »%s«:"
-
-#: gitk:3230 gitk:3239
+#: gitk:3305 gitk:3314
 #, tcl-format
 msgid "Error creating temporary directory %s:"
 msgstr "Fehler beim Erzeugen des temporären Verzeichnisses »%s«:"
 
-#: gitk:3251
+#: gitk:3327
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Fehler beim Holen von »%s« von »%s«:"
+
+#: gitk:3390
 msgid "command failed:"
 msgstr "Kommando fehlgeschlagen:"
 
-#: gitk:3397
+#: gitk:3539
 msgid "No such commit"
 msgstr "Version nicht gefunden"
 
-#: gitk:3411
+#: gitk:3553
 msgid "git gui blame: command failed:"
 msgstr "git gui blame: Kommando fehlgeschlagen:"
 
-#: gitk:3442
+#: gitk:3584
 #, tcl-format
 msgid "Couldn't read merge head: %s"
 msgstr "Zusammenführungs-Spitze konnte nicht gelesen werden: %s"
 
-#: gitk:3450
+#: gitk:3592
 #, tcl-format
 msgid "Error reading index: %s"
 msgstr "Fehler beim Lesen der Bereitstellung (»index«): %s"
 
-#: gitk:3475
+#: gitk:3617
 #, tcl-format
 msgid "Couldn't start git blame: %s"
 msgstr "»git blame« konnte nicht gestartet werden: %s"
 
-#: gitk:3478 gitk:6209
+#: gitk:3620 gitk:6409
 msgid "Searching"
 msgstr "Suchen"
 
-#: gitk:3510
+#: gitk:3652
 #, tcl-format
 msgid "Error running git blame: %s"
 msgstr "Fehler beim Ausführen von »git blame«: %s"
 
-#: gitk:3538
+#: gitk:3680
 #, tcl-format
 msgid "That line comes from commit %s,  which is not in this view"
 msgstr ""
 "Diese Zeile stammt aus Version %s, die nicht in dieser Ansicht gezeigt wird"
 
-#: gitk:3552
+#: gitk:3694
 msgid "External diff viewer failed:"
 msgstr "Externes Diff-Programm fehlgeschlagen:"
 
-#: gitk:3670
+#: gitk:3812
 msgid "Gitk view definition"
 msgstr "Gitk-Ansichten"
 
-#: gitk:3674
+#: gitk:3816
 msgid "Remember this view"
 msgstr "Diese Ansicht speichern"
 
-#: gitk:3675
-msgid "Commits to include (arguments to git log):"
-msgstr "Versionen anzeigen (Argumente von git log):"
+#: gitk:3817
+msgid "References (space separated list):"
+msgstr "Zweige/Markierungen (durch Leerzeichen getrennte Liste):"
 
-#: gitk:3676
-msgid "Use all refs"
-msgstr "Alle Zweige verwenden"
+#: gitk:3818
+msgid "Branches & tags:"
+msgstr "Zweige/Markierungen:"
 
-#: gitk:3677
-msgid "Strictly sort by date"
-msgstr "Streng nach Datum sortieren"
+#: gitk:3819
+msgid "All refs"
+msgstr "Alle Markierungen und Zweige"
 
-#: gitk:3678
-msgid "Mark branch sides"
-msgstr "Zweig-Seiten markieren"
+#: gitk:3820
+msgid "All (local) branches"
+msgstr "Alle (lokalen) Zweige"
 
-#: gitk:3679
-msgid "Since date:"
-msgstr "Von Datum:"
+#: gitk:3821
+msgid "All tags"
+msgstr "Alle Markierungen"
 
-#: gitk:3680
-msgid "Until date:"
-msgstr "Bis Datum:"
+#: gitk:3822
+msgid "All remote-tracking branches"
+msgstr "Alle Übernahmezweige"
+
+#: gitk:3823
+msgid "Commit Info (regular expressions):"
+msgstr "Versionsinformationen (reguläre Ausdrücke):"
+
+#: gitk:3824
+msgid "Author:"
+msgstr "Autor:"
+
+#: gitk:3825
+msgid "Committer:"
+msgstr "Eintragender:"
+
+#: gitk:3826
+msgid "Commit Message:"
+msgstr "Versionsbeschreibung:"
 
-#: gitk:3681
-msgid "Max count:"
-msgstr "Max. Anzahl:"
+#: gitk:3827
+msgid "Matches all Commit Info criteria"
+msgstr "Alle Versionsinformationen-Kriterien erfüllen"
 
-#: gitk:3682
-msgid "Skip:"
+#: gitk:3828
+msgid "Changes to Files:"
+msgstr "Dateien:"
+
+#: gitk:3829
+msgid "Fixed String"
+msgstr "Zeichenkette"
+
+#: gitk:3830
+msgid "Regular Expression"
+msgstr "Regulärer Ausdruck"
+
+#: gitk:3831
+msgid "Search string:"
+msgstr "Suchausdruck:"
+
+#: gitk:3832
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr "Datum (»2 weeks ago«, »2009-03-17 15:27:38«, »March 17, 2009 15:27:38«)"
+
+#: gitk:3833
+msgid "Since:"
+msgstr "Von:"
+
+#: gitk:3834
+msgid "Until:"
+msgstr "Bis:"
+
+#: gitk:3835
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Versionsanzahl begrenzen oder einige überspringen (ganzzahliger Wert):"
+
+#: gitk:3836
+msgid "Number to show:"
+msgstr "Anzeigen:"
+
+#: gitk:3837
+msgid "Number to skip:"
 msgstr "Überspringen:"
 
-#: gitk:3683
+#: gitk:3838
+msgid "Miscellaneous options:"
+msgstr "Sonstiges:"
+
+#: gitk:3839
+msgid "Strictly sort by date"
+msgstr "Streng nach Datum sortieren"
+
+#: gitk:3840
+msgid "Mark branch sides"
+msgstr "Zweig-Seiten markieren"
+
+#: gitk:3841
 msgid "Limit to first parent"
 msgstr "Auf erste Elternversion beschränken"
 
-#: gitk:3684
+#: gitk:3842
+msgid "Simple history"
+msgstr "Einfache Historie"
+
+#: gitk:3843
+msgid "Additional arguments to git log:"
+msgstr "Zusätzliche Argumente für »git log«:"
+
+#: gitk:3844
+msgid "Enter files and directories to include, one per line:"
+msgstr "Folgende Dateien und Verzeichnisse anzeigen (eine pro Zeile):"
+
+#: gitk:3845
 msgid "Command to generate more commits to include:"
 msgstr "Versionsliste durch folgendes Kommando erzeugen lassen:"
 
-#: gitk:3780
+#: gitk:3967
 msgid "Gitk: edit view"
 msgstr "Gitk: Ansicht bearbeiten"
 
-#: gitk:3793
-msgid "Name"
-msgstr "Name"
+#: gitk:3975
+msgid "-- criteria for selecting revisions"
+msgstr "-- Auswahl der angezeigten Versionen"
 
-#: gitk:3841
-msgid "Enter files and directories to include, one per line:"
-msgstr "Folgende Dateien und Verzeichnisse anzeigen (eine pro Zeile):"
+#: gitk:3980
+msgid "View Name"
+msgstr "Ansichtsname"
 
-#: gitk:3855
+#: gitk:4055
 msgid "Apply (F5)"
 msgstr "Anwenden (F5)"
 
-#: gitk:3893
+#: gitk:4093
 msgid "Error in commit selection arguments:"
 msgstr "Fehler in den ausgewählten Versionen:"
 
-#: gitk:3946 gitk:3998 gitk:4446 gitk:4460 gitk:5721 gitk:11114 gitk:11115
+#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535
 msgid "None"
 msgstr "Keine"
 
-#: gitk:4394 gitk:6241 gitk:7993 gitk:8008
+#: gitk:4594 gitk:6441 gitk:8287 gitk:8302
 msgid "Date"
 msgstr "Datum"
 
-#: gitk:4394 gitk:6241
+#: gitk:4594 gitk:6441
 msgid "CDate"
 msgstr "Eintragedatum"
 
-#: gitk:4543 gitk:4548
+#: gitk:4743 gitk:4748
 msgid "Descendant"
 msgstr "Abkömmling"
 
-#: gitk:4544
+#: gitk:4744
 msgid "Not descendant"
 msgstr "Kein Abkömmling"
 
-#: gitk:4551 gitk:4556
+#: gitk:4751 gitk:4756
 msgid "Ancestor"
 msgstr "Vorgänger"
 
-#: gitk:4552
+#: gitk:4752
 msgid "Not ancestor"
 msgstr "Kein Vorgänger"
 
-#: gitk:4842
+#: gitk:5042
 msgid "Local changes checked in to index but not committed"
 msgstr "Lokale Änderungen bereitgestellt, aber nicht eingetragen"
 
-#: gitk:4878
+#: gitk:5078
 msgid "Local uncommitted changes, not checked in to index"
 msgstr "Lokale Änderungen, nicht bereitgestellt"
 
-#: gitk:6559
+#: gitk:6759
 msgid "many"
 msgstr "viele"
 
-#: gitk:6743
+#: gitk:6942
 msgid "Tags:"
 msgstr "Markierungen:"
 
-#: gitk:6760 gitk:6766 gitk:7986
+#: gitk:6959 gitk:6965 gitk:8280
 msgid "Parent"
 msgstr "Eltern"
 
-#: gitk:6771
+#: gitk:6970
 msgid "Child"
 msgstr "Kind"
 
-#: gitk:6780
+#: gitk:6979
 msgid "Branch"
 msgstr "Zweig"
 
-#: gitk:6783
+#: gitk:6982
 msgid "Follows"
 msgstr "Folgt auf"
 
-#: gitk:6786
+#: gitk:6985
 msgid "Precedes"
 msgstr "Vorgänger von"
 
-#: gitk:7279
+#: gitk:7522
 #, tcl-format
 msgid "Error getting diffs: %s"
 msgstr "Fehler beim Laden des Vergleichs: %s"
 
-#: gitk:7819
+#: gitk:8108
 msgid "Goto:"
 msgstr "Gehe zu:"
 
-#: gitk:7821
-msgid "SHA1 ID:"
-msgstr "SHA1-Hashwert:"
-
-#: gitk:7840
+#: gitk:8129
 #, tcl-format
 msgid "Short SHA1 id %s is ambiguous"
 msgstr "Kurzer SHA1-Hashwert »%s« ist mehrdeutig"
 
-#: gitk:7852
+#: gitk:8136
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "Version »%s« ist unbekannt"
+
+#: gitk:8146
 #, tcl-format
 msgid "SHA1 id %s is not known"
 msgstr "SHA1-Hashwert »%s« ist unbekannt"
 
-#: gitk:7854
+#: gitk:8148
 #, tcl-format
-msgid "Tag/Head %s is not known"
-msgstr "Markierung/Zweig »%s« ist unbekannt"
+msgid "Revision %s is not in the current view"
+msgstr "Version »%s« wird in der aktuellen Ansicht nicht angezeigt"
 
-#: gitk:7996
+#: gitk:8290
 msgid "Children"
 msgstr "Kinder"
 
-#: gitk:8053
+#: gitk:8348
 #, tcl-format
 msgid "Reset %s branch to here"
 msgstr "Zweig »%s« hierher zurücksetzen"
 
-#: gitk:8055
+#: gitk:8350
 msgid "Detached head: can't reset"
 msgstr "Zweigspitze ist abgetrennt: Zurücksetzen nicht möglich"
 
-#: gitk:8164 gitk:8170
+#: gitk:8459 gitk:8465
 msgid "Skipping merge commit "
 msgstr "Überspringe Zusammenführungs-Version "
 
-#: gitk:8179 gitk:8184
+#: gitk:8474 gitk:8479
 msgid "Error getting patch ID for "
 msgstr "Fehler beim Holen der Patch-ID für "
 
-#: gitk:8180 gitk:8185
+#: gitk:8475 gitk:8480
 msgid " - stopping\n"
 msgstr " - Abbruch.\n"
 
-#: gitk:8190 gitk:8193 gitk:8201 gitk:8211 gitk:8220
+#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519
 msgid "Commit "
 msgstr "Version "
 
-#: gitk:8194
+#: gitk:8489
 msgid ""
 " is the same patch as\n"
 "       "
@@ -762,7 +844,7 @@ msgstr ""
 " ist das gleiche Patch wie\n"
 "       "
 
-#: gitk:8202
+#: gitk:8497
 msgid ""
 " differs from\n"
 "       "
@@ -770,117 +852,138 @@ msgstr ""
 " ist unterschiedlich von\n"
 "       "
 
-#: gitk:8204
-msgid "- stopping\n"
-msgstr "- Abbruch.\n"
+#: gitk:8499
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr ""
+"Vergleich der Versionen:\n"
+"\n"
 
-#: gitk:8212 gitk:8221
+#: gitk:8511 gitk:8520
 #, tcl-format
 msgid " has %s children - stopping\n"
 msgstr " hat %s Kinder. Abbruch\n"
 
-#: gitk:8252
+#: gitk:8539
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "Fehler beim Schreiben der Version in Datei: %s"
+
+#: gitk:8545
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "Fehler beim Vergleichen der Versionen: %s"
+
+#: gitk:8575
 msgid "Top"
 msgstr "Oben"
 
-#: gitk:8253
+#: gitk:8576
 msgid "From"
 msgstr "Von"
 
-#: gitk:8258
+#: gitk:8581
 msgid "To"
 msgstr "bis"
 
-#: gitk:8282
+#: gitk:8605
 msgid "Generate patch"
 msgstr "Patch erstellen"
 
-#: gitk:8284
+#: gitk:8607
 msgid "From:"
 msgstr "Von:"
 
-#: gitk:8293
+#: gitk:8616
 msgid "To:"
 msgstr "bis:"
 
-#: gitk:8302
+#: gitk:8625
 msgid "Reverse"
 msgstr "Umgekehrt"
 
-#: gitk:8304 gitk:8489
+#: gitk:8627 gitk:8822
 msgid "Output file:"
 msgstr "Ausgabedatei:"
 
-#: gitk:8310
+#: gitk:8633
 msgid "Generate"
 msgstr "Erzeugen"
 
-#: gitk:8348
+#: gitk:8671
 msgid "Error creating patch:"
 msgstr "Fehler beim Erzeugen des Patches:"
 
-#: gitk:8371 gitk:8477 gitk:8534
+#: gitk:8694 gitk:8810 gitk:8867
 msgid "ID:"
 msgstr "ID:"
 
-#: gitk:8380
+#: gitk:8703
 msgid "Tag name:"
 msgstr "Markierungsname:"
 
-#: gitk:8384 gitk:8543
+#: gitk:8706
+msgid "Tag message is optional"
+msgstr "Eine Markierungsbeschreibung ist optional"
+
+#: gitk:8708
+msgid "Tag message:"
+msgstr "Markierungsbeschreibung:"
+
+#: gitk:8712 gitk:8876
 msgid "Create"
 msgstr "Erstellen"
 
-#: gitk:8401
+#: gitk:8730
 msgid "No tag name specified"
 msgstr "Kein Markierungsname angegeben"
 
-#: gitk:8405
+#: gitk:8734
 #, tcl-format
 msgid "Tag \"%s\" already exists"
 msgstr "Markierung »%s« existiert bereits."
 
-#: gitk:8411
+#: gitk:8744
 msgid "Error creating tag:"
 msgstr "Fehler beim Erstellen der Markierung:"
 
-#: gitk:8486
+#: gitk:8819
 msgid "Command:"
 msgstr "Kommando:"
 
-#: gitk:8494
+#: gitk:8827
 msgid "Write"
 msgstr "Schreiben"
 
-#: gitk:8512
+#: gitk:8845
 msgid "Error writing commit:"
 msgstr "Fehler beim Schreiben der Version:"
 
-#: gitk:8539
+#: gitk:8872
 msgid "Name:"
 msgstr "Name:"
 
-#: gitk:8562
+#: gitk:8895
 msgid "Please specify a name for the new branch"
 msgstr "Bitte geben Sie einen Namen für den neuen Zweig an."
 
-#: gitk:8567
+#: gitk:8900
 #, tcl-format
 msgid "Branch '%s' already exists. Overwrite?"
 msgstr "Zweig »%s« existiert bereits. Soll er überschrieben werden?"
 
-#: gitk:8633
+#: gitk:8966
 #, tcl-format
 msgid "Commit %s is already included in branch %s -- really re-apply it?"
 msgstr ""
-"Version »%s« ist bereits im Zweig »%s« enthalten -- trotzdem erneut "
-"eintragen?"
+"Version »%s« ist bereits im Zweig »%s« enthalten -- trotzdem erneut eintragen?"
 
-#: gitk:8638
+#: gitk:8971
 msgid "Cherry-picking"
 msgstr "Version pflücken"
 
-#: gitk:8647
+#: gitk:8980
 #, tcl-format
 msgid ""
 "Cherry-pick failed because of local changes to file '%s'.\n"
@@ -890,7 +993,7 @@ msgstr ""
 "vorliegen. Bitte diese Änderungen eintragen, zurücksetzen oder\n"
 "zwischenspeichern (»git stash«) und dann erneut versuchen."
 
-#: gitk:8653
+#: gitk:8986
 msgid ""
 "Cherry-pick failed because of merge conflict.\n"
 "Do you wish to run git citool to resolve it?"
@@ -899,34 +1002,34 @@ msgstr ""
 "ist. Soll das Zusammenführungs-Werkzeug (»git citool«) aufgerufen\n"
 "werden, um diesen Konflikt aufzulösen?"
 
-#: gitk:8669
+#: gitk:9002
 msgid "No changes committed"
 msgstr "Keine Änderungen eingetragen"
 
-#: gitk:8695
+#: gitk:9028
 msgid "Confirm reset"
 msgstr "Zurücksetzen bestätigen"
 
-#: gitk:8697
+#: gitk:9030
 #, tcl-format
 msgid "Reset branch %s to %s?"
 msgstr "Zweig »%s« auf »%s« zurücksetzen?"
 
-#: gitk:8701
+#: gitk:9032
 msgid "Reset type:"
 msgstr "Art des Zurücksetzens:"
 
-#: gitk:8705
+#: gitk:9035
 msgid "Soft: Leave working tree and index untouched"
 msgstr "Harmlos: Arbeitskopie und Bereitstellung unverändert"
 
-#: gitk:8708
+#: gitk:9038
 msgid "Mixed: Leave working tree untouched, reset index"
 msgstr ""
 "Gemischt: Arbeitskopie unverändert,\n"
 "Bereitstellung zurückgesetzt"
 
-#: gitk:8711
+#: gitk:9041
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
@@ -934,21 +1037,21 @@ msgstr ""
 "Hart: Arbeitskopie und Bereitstellung\n"
 "(Alle lokalen Änderungen werden gelöscht)"
 
-#: gitk:8728
+#: gitk:9058
 msgid "Resetting"
 msgstr "Zurücksetzen"
 
-#: gitk:8785
+#: gitk:9118
 msgid "Checking out"
 msgstr "Umstellen"
 
-#: gitk:8838
+#: gitk:9171
 msgid "Cannot delete the currently checked-out branch"
 msgstr ""
 "Der Zweig, auf den die Arbeitskopie momentan umgestellt ist, kann nicht "
 "gelöscht werden."
 
-#: gitk:8844
+#: gitk:9177
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
@@ -957,16 +1060,16 @@ msgstr ""
 "Die Versionen auf Zweig »%s« existieren auf keinem anderen Zweig.\n"
 "Zweig »%s« trotzdem löschen?"
 
-#: gitk:8875
+#: gitk:9208
 #, tcl-format
 msgid "Tags and heads: %s"
 msgstr "Markierungen und Zweige: %s"
 
-#: gitk:8890
+#: gitk:9223
 msgid "Filter"
 msgstr "Filtern"
 
-#: gitk:9185
+#: gitk:9518
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
@@ -974,182 +1077,202 @@ msgstr ""
 "Fehler beim Lesen der Strukturinformationen; Zweige und Informationen zu "
 "Vorgänger/Nachfolger werden unvollständig sein."
 
-#: gitk:10171
+#: gitk:10504
 msgid "Tag"
 msgstr "Markierung"
 
-#: gitk:10171
+#: gitk:10504
 msgid "Id"
 msgstr "Id"
 
-#: gitk:10219
+#: gitk:10554
 msgid "Gitk font chooser"
 msgstr "Gitk-Schriften wählen"
 
-#: gitk:10236
+#: gitk:10571
 msgid "B"
 msgstr "F"
 
-#: gitk:10239
+#: gitk:10574
 msgid "I"
 msgstr "K"
 
-#: gitk:10334
+#: gitk:10692
 msgid "Gitk preferences"
 msgstr "Gitk-Einstellungen"
 
-#: gitk:10336
+#: gitk:10694
 msgid "Commit list display options"
 msgstr "Anzeige der Versionsliste"
 
-#: gitk:10339
+#: gitk:10697
 msgid "Maximum graph width (lines)"
 msgstr "Maximale Graphenbreite (Zeilen)"
 
-#: gitk:10343
+#: gitk:10700
 #, tcl-format
 msgid "Maximum graph width (% of pane)"
 msgstr "Maximale Graphenbreite (% des Fensters)"
 
-#: gitk:10347
+#: gitk:10703
 msgid "Show local changes"
 msgstr "Lokale Änderungen anzeigen"
 
-#: gitk:10350
+#: gitk:10706
 msgid "Auto-select SHA1"
 msgstr "SHA1-Hashwert automatisch auswählen"
 
-#: gitk:10354
+#: gitk:10709
+msgid "Hide remote refs"
+msgstr "Entfernte Zweige/Markierungen ausblenden"
+
+#: gitk:10713
 msgid "Diff display options"
 msgstr "Anzeige des Vergleichs"
 
-#: gitk:10356
+#: gitk:10715
 msgid "Tab spacing"
 msgstr "Tabulatorbreite"
 
-#: gitk:10359
+#: gitk:10718
 msgid "Display nearby tags"
 msgstr "Naheliegende Markierungen anzeigen"
 
-#: gitk:10362
+#: gitk:10721
 msgid "Limit diffs to listed paths"
 msgstr "Vergleich nur für angezeigte Pfade"
 
-#: gitk:10365
+#: gitk:10724
 msgid "Support per-file encodings"
 msgstr "Zeichenkodierung pro Datei ermitteln"
 
-#: gitk:10371 gitk:10436
+#: gitk:10730 gitk:10819
 msgid "External diff tool"
 msgstr "Externes Diff-Programm"
 
-#: gitk:10373
+#: gitk:10731
 msgid "Choose..."
 msgstr "Wählen ..."
 
-#: gitk:10378
+#: gitk:10736
+msgid "General options"
+msgstr "Allgemeine Optionen"
+
+#: gitk:10739
+msgid "Use themed widgets"
+msgstr "Aussehen der Benutzeroberfläche durch Thema bestimmen"
+
+#: gitk:10741
+msgid "(change requires restart)"
+msgstr "(Änderungen werden erst nach Neustart wirksam)"
+
+#: gitk:10743
+msgid "(currently unavailable)"
+msgstr "(Momentan nicht verfügbar)"
+
+#: gitk:10747
 msgid "Colors: press to choose"
 msgstr "Farben: Klicken zum Wählen"
 
-#: gitk:10381
+#: gitk:10750
+msgid "Interface"
+msgstr "Benutzeroberfläche"
+
+#: gitk:10751
+msgid "interface"
+msgstr "Benutzeroberfläche"
+
+#: gitk:10754
 msgid "Background"
 msgstr "Hintergrund"
 
-#: gitk:10382 gitk:10412
+#: gitk:10755 gitk:10785
 msgid "background"
 msgstr "Hintergrund"
 
-#: gitk:10385
+#: gitk:10758
 msgid "Foreground"
 msgstr "Vordergrund"
 
-#: gitk:10386
+#: gitk:10759
 msgid "foreground"
 msgstr "Vordergrund"
 
-#: gitk:10389
+#: gitk:10762
 msgid "Diff: old lines"
 msgstr "Vergleich: Alte Zeilen"
 
-#: gitk:10390
+#: gitk:10763
 msgid "diff old lines"
 msgstr "Vergleich - Alte Zeilen"
 
-#: gitk:10394
+#: gitk:10767
 msgid "Diff: new lines"
 msgstr "Vergleich: Neue Zeilen"
 
-#: gitk:10395
+#: gitk:10768
 msgid "diff new lines"
 msgstr "Vergleich - Neue Zeilen"
 
-#: gitk:10399
+#: gitk:10772
 msgid "Diff: hunk header"
 msgstr "Vergleich: Änderungstitel"
 
-#: gitk:10401
+#: gitk:10774
 msgid "diff hunk header"
 msgstr "Vergleich - Änderungstitel"
 
-#: gitk:10405
+#: gitk:10778
 msgid "Marked line bg"
 msgstr "Hintergrund für markierte Zeile"
 
-#: gitk:10407
+#: gitk:10780
 msgid "marked line background"
 msgstr "Hintergrund für markierte Zeile"
 
-#: gitk:10411
+#: gitk:10784
 msgid "Select bg"
 msgstr "Hintergrundfarbe auswählen"
 
-#: gitk:10415
+#: gitk:10788
 msgid "Fonts: press to choose"
 msgstr "Schriftart: Klicken zum Wählen"
 
-#: gitk:10417
+#: gitk:10790
 msgid "Main font"
 msgstr "Programmschriftart"
 
-#: gitk:10418
+#: gitk:10791
 msgid "Diff display font"
 msgstr "Schriftart für Vergleich"
 
-#: gitk:10419
+#: gitk:10792
 msgid "User interface font"
 msgstr "Beschriftungen"
 
-#: gitk:10446
+#: gitk:10829
 #, tcl-format
 msgid "Gitk: choose color for %s"
 msgstr "Gitk: Farbe wählen für %s"
 
-#: gitk:10893
-msgid ""
-"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
-" Gitk requires at least Tcl/Tk 8.4."
-msgstr ""
-"Gitk läuft nicht mit dieser Version von Tcl/Tk.\n"
-"Gitk benötigt mindestens Tcl/Tk 8.4."
-
-#: gitk:11020
+#: gitk:11433
 msgid "Cannot find a git repository here."
 msgstr "Kein Git-Projektarchiv gefunden."
 
-#: gitk:11024
+#: gitk:11437
 #, tcl-format
 msgid "Cannot find the git directory \"%s\"."
 msgstr "Git-Verzeichnis »%s« wurde nicht gefunden."
 
-#: gitk:11071
+#: gitk:11484
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
 msgstr "Mehrdeutige Angabe »%s«: Sowohl Version als auch Dateiname existiert."
 
-#: gitk:11083
+#: gitk:11496
 msgid "Bad arguments to gitk:"
 msgstr "Falsche Kommandozeilen-Parameter für gitk:"
 
-#: gitk:11167
+#: gitk:11587
 msgid "Command line"
 msgstr "Kommandozeile"
diff --git a/gitk-git/po/fr.po b/gitk-git/po/fr.po
new file mode 100644 (file)
index 0000000..cb0e1ed
--- /dev/null
@@ -0,0 +1,1254 @@
+# French translation of the gitk package
+# Copyright (C) 2005-2008 Paul Mackerras.  All rights reserved.
+# This file is distributed under the same license as the gitk package.
+# Translators:
+# Emmanuel Trillaud <etrillaud@gmail.com>
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: gitk\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-10-05 15:16+0200\n"
+"PO-Revision-Date: 2009-11-19 22:13+0100\n"
+"Last-Translator: Emmanuel Trillaud <etrillaud@gmail.com>\n"
+"Language-Team: git@vger.kernel.org\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: French\n"
+"X-Poedit-Country: FRANCE\n"
+
+#: gitk:113
+msgid "Couldn't get list of unmerged files:"
+msgstr "Impossible de récupérer la liste des fichiers non fusionnés :"
+
+#: gitk:269
+msgid "Error parsing revisions:"
+msgstr "Erreur lors du parcours des révisions :"
+
+#: gitk:324
+msgid "Error executing --argscmd command:"
+msgstr "Erreur à l'exécution de la commande --argscmd :"
+
+#: gitk:337
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"Aucun fichier sélectionné : --merge précisé, mais tous les fichiers sont "
+"fusionnés."
+
+# FIXME : améliorer la traduction de 'file limite'
+#: gitk:340
+#, fuzzy
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"Aucun fichier sélectionné : --merge précisé mais aucun fichier non fusionné "
+"n'est dans la limite des fichiers."
+
+#: gitk:362 gitk:509
+msgid "Error executing git log:"
+msgstr "Erreur à l'exécution de git log :"
+
+#: gitk:380 gitk:525
+msgid "Reading"
+msgstr "Lecture en cours"
+
+#: gitk:440 gitk:4123
+msgid "Reading commits..."
+msgstr "Lecture des commits..."
+
+#: gitk:443 gitk:1561 gitk:4126
+msgid "No commits selected"
+msgstr "Aucun commit sélectionné"
+
+#: gitk:1437
+msgid "Can't parse git log output:"
+msgstr "Impossible de lire la sortie de git log :"
+
+#: gitk:1657
+msgid "No commit information available"
+msgstr "Aucune information disponible sur le commit"
+
+#: gitk:1793 gitk:1817 gitk:3916 gitk:8786 gitk:10322 gitk:10498
+msgid "OK"
+msgstr "OK"
+
+#: gitk:1819 gitk:3918 gitk:8383 gitk:8457 gitk:8567 gitk:8616 gitk:8788
+#: gitk:10323 gitk:10499
+msgid "Cancel"
+msgstr "Annuler"
+
+#: gitk:1919
+msgid "Update"
+msgstr "Mise à jour"
+
+#: gitk:1920
+msgid "Reload"
+msgstr "Recharger"
+
+#: gitk:1921
+msgid "Reread references"
+msgstr "Relire les références"
+
+#: gitk:1922
+msgid "List references"
+msgstr "Lister les références"
+
+#: gitk:1924
+msgid "Start git gui"
+msgstr "Démarrer git gui"
+
+#: gitk:1926
+msgid "Quit"
+msgstr "Quitter"
+
+#: gitk:1918
+msgid "File"
+msgstr "Fichier"
+
+#: gitk:1930
+msgid "Preferences"
+msgstr "Préférences"
+
+#: gitk:1929
+msgid "Edit"
+msgstr "Éditer"
+
+#: gitk:1934
+msgid "New view..."
+msgstr "Nouvelle vue..."
+
+#: gitk:1935
+msgid "Edit view..."
+msgstr "Éditer la vue..."
+
+#: gitk:1936
+msgid "Delete view"
+msgstr "Supprimer la vue"
+
+#: gitk:1938
+msgid "All files"
+msgstr "Tous les fichiers"
+
+#: gitk:1933 gitk:3670
+msgid "View"
+msgstr "Vue"
+
+#: gitk:1943 gitk:1953 gitk:2654
+msgid "About gitk"
+msgstr "À propos de gitk"
+
+#: gitk:1944 gitk:1958
+msgid "Key bindings"
+msgstr "Raccourcis clavier"
+
+#: gitk:1942 gitk:1957
+msgid "Help"
+msgstr "Aide"
+
+#: gitk:2018
+msgid "SHA1 ID: "
+msgstr "ID SHA1 :"
+
+#: gitk:2049
+msgid "Row"
+msgstr "Colonne"
+
+#: gitk:2080
+msgid "Find"
+msgstr "Recherche"
+
+#: gitk:2081
+msgid "next"
+msgstr "suivant"
+
+#: gitk:2082
+msgid "prev"
+msgstr "précédent"
+
+#: gitk:2083
+msgid "commit"
+msgstr "commit"
+
+#: gitk:2086 gitk:2088 gitk:4284 gitk:4307 gitk:4331 gitk:6272 gitk:6344
+#: gitk:6428
+msgid "containing:"
+msgstr "contient :"
+
+#: gitk:2089 gitk:3162 gitk:3167 gitk:4359
+msgid "touching paths:"
+msgstr "chemins modifiés :"
+
+#: gitk:2090 gitk:4364
+msgid "adding/removing string:"
+msgstr "ajoute/supprime la chaîne :"
+
+#: gitk:2099 gitk:2101
+msgid "Exact"
+msgstr "Exact"
+
+#: gitk:2101 gitk:4439 gitk:6240
+msgid "IgnCase"
+msgstr "Ignorer la casse"
+
+#: gitk:2101 gitk:4333 gitk:4437 gitk:6236
+msgid "Regexp"
+msgstr "Expression régulière"
+
+#: gitk:2103 gitk:2104 gitk:4458 gitk:4488 gitk:4495 gitk:6364 gitk:6432
+msgid "All fields"
+msgstr "Tous les champs"
+
+#: gitk:2104 gitk:4456 gitk:4488 gitk:6303
+msgid "Headline"
+msgstr "Surligner"
+
+#: gitk:2105 gitk:4456 gitk:6303 gitk:6432 gitk:6866
+msgid "Comments"
+msgstr "Commentaires"
+
+#: gitk:2105 gitk:4456 gitk:4460 gitk:4495 gitk:6303 gitk:6801 gitk:8063
+#: gitk:8078
+msgid "Author"
+msgstr "Auteur"
+
+#: gitk:2105 gitk:4456 gitk:6303 gitk:6803
+msgid "Committer"
+msgstr "Auteur du commit"
+
+#: gitk:2134
+msgid "Search"
+msgstr "Rechercher"
+
+#: gitk:2141
+msgid "Diff"
+msgstr "Diff"
+
+#: gitk:2143
+msgid "Old version"
+msgstr "Ancienne version"
+
+#: gitk:2145
+msgid "New version"
+msgstr "Nouvelle version"
+
+#: gitk:2147
+msgid "Lines of context"
+msgstr "Lignes de contexte"
+
+#: gitk:2157
+msgid "Ignore space change"
+msgstr "Ignorer les modifications d'espace"
+
+#: gitk:2215
+msgid "Patch"
+msgstr "Patch"
+
+#: gitk:2217
+msgid "Tree"
+msgstr "Arbre"
+
+#: gitk:2361 gitk:2378
+msgid "Diff this -> selected"
+msgstr "Diff entre ceci et la sélection"
+
+#: gitk:2362 gitk:2379
+msgid "Diff selected -> this"
+msgstr "Diff entre sélection et ceci"
+
+#: gitk:2363 gitk:2380
+msgid "Make patch"
+msgstr "Créer patch"
+
+#: gitk:2364 gitk:8441
+msgid "Create tag"
+msgstr "Créer tag"
+
+#: gitk:2365 gitk:8547
+msgid "Write commit to file"
+msgstr "Écrire le commit dans un fichier"
+
+#: gitk:2366 gitk:8604
+msgid "Create new branch"
+msgstr "Créer une nouvelle branche"
+
+#: gitk:2367
+msgid "Cherry-pick this commit"
+msgstr "Cueillir (cherry-pick) ce commit"
+
+#: gitk:2368
+msgid "Reset HEAD branch to here"
+msgstr "Réinitialiser la branche HEAD vers cet état"
+
+#: gitk:2369
+msgid "Mark this commit"
+msgstr "Marquer ce commit"
+
+#: gitk:2370
+msgid "Return to mark"
+msgstr "Retourner à la marque"
+
+#: gitk:2371
+msgid "Find descendant of this and mark"
+msgstr "Chercher le descendant de ceci et le marquer"
+
+#: gitk:2372
+msgid "Compare with marked commit"
+msgstr "Comparer avec le commit marqué"
+
+#: gitk:2386
+msgid "Check out this branch"
+msgstr "Récupérer cette branche"
+
+#: gitk:2387
+msgid "Remove this branch"
+msgstr "Supprimer cette branche"
+
+#: gitk:2394
+msgid "Highlight this too"
+msgstr "Surligner également ceci"
+
+#: gitk:2395
+msgid "Highlight this only"
+msgstr "Surligner seulement ceci"
+
+#: gitk:2396
+msgid "External diff"
+msgstr "Diff externe"
+
+#: gitk:2397
+msgid "Blame parent commit"
+msgstr "Blâmer le commit parent"
+
+#: gitk:2404
+msgid "Show origin of this line"
+msgstr "Montrer l'origine de cette ligne"
+
+#: gitk:2405
+msgid "Run git gui blame on this line"
+msgstr "Exécuter git gui blame sur cette ligne"
+
+#: gitk:2656
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright © 2005-2008 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - visualisateur de commit pour git\n"
+"\n"
+"Copyright © 2005-2008 Paul Mackerras\n"
+"\n"
+"Utilisation et redistribution soumises aux termes de la GNU General Public "
+"License"
+
+#: gitk:2664 gitk:2726 gitk:8969
+msgid "Close"
+msgstr "Fermer"
+
+#: gitk:2683
+msgid "Gitk key bindings"
+msgstr "Raccourcis clavier de Gitk"
+
+#: gitk:2686
+msgid "Gitk key bindings:"
+msgstr "Raccourcis clavier de Gitk :"
+
+#: gitk:2688
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tQuitter"
+
+#: gitk:2689
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Début>\t\tAller au premier commit"
+
+#: gitk:2690
+msgid "<End>\t\tMove to last commit"
+msgstr "<Fin>\t\tAller au dernier commit"
+
+#: gitk:2691
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Haut>, p, i\t Aller au commit suivant"
+
+#: gitk:2692
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Bas>, n, k\t Aller au commit précédent"
+
+#: gitk:2693
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Gauche>, z, j\tReculer dans l'historique"
+
+#: gitk:2694
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Droite>, x, l\tAvancer dans l'historique"
+
+#: gitk:2695
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\tMonter d'une page dans la liste des commits"
+
+#: gitk:2696
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\tDescendre d'une page dans la liste des commits"
+
+#: gitk:2697
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Début>\tAller en haut de la liste des commits"
+
+#: gitk:2698
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\tAller en bas de la liste des commits"
+
+#: gitk:2699
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Up>\tMonter d'une ligne dans la liste des commits"
+
+#: gitk:2700
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Down>\tDescendre d'une ligne dans la liste des commits"
+
+#: gitk:2701
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\tMonter d'une page dans la liste des commits"
+
+#: gitk:2702
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\tDescendre d'une page dans la liste des commits"
+
+#: gitk:2703
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr ""
+"<Shift-Up>\tRecherche en arrière (vers l'avant, commits les plus anciens)"
+
+#: gitk:2704
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr ""
+"<Shift-Down>\tRecherche en avant (vers l'arrière, commit les plus récents)"
+
+#: gitk:2705
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Supprimer>, b\tMonter d'une page dans la vue des diff"
+
+#: gitk:2706
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Backspace>\tMonter d'une page dans la vue des diff"
+
+#: gitk:2707
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Espace>\t\tDescendre d'une page dans la vue des diff"
+
+#: gitk:2708
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tMonter de 18 lignes dans la vue des diff"
+
+#: gitk:2709
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tDescendre de 18 lignes dans la vue des diff"
+
+#: gitk:2710
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tRechercher"
+
+#: gitk:2711
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tAller au résultat de recherche suivant"
+
+#: gitk:2712
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\t\tAller au résultat de recherche suivant"
+
+#: gitk:2713
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tFocus sur la zone de recherche"
+
+#: gitk:2714
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tAller au résultat de recherche précédent"
+
+#: gitk:2715
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tAller au prochain fichier dans la vue des diff"
+
+#: gitk:2716
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tAller au résultat suivant dans la vue des diff"
+
+#: gitk:2717
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tAller au résultat précédent dans la vue des diff"
+
+#: gitk:2718
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-KP+>\tAugmenter la taille de la police"
+
+#: gitk:2719
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-plus>\tAugmenter la taille de la police"
+
+#: gitk:2720
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-KP->\tDiminuer la taille de la police"
+
+#: gitk:2721
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-minus>\tDiminuer la taille de la police"
+
+#: gitk:2722
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tMise à jour"
+
+#: gitk:3177
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Erreur en obtenant \"%s\" de %s:"
+
+#: gitk:3234 gitk:3243
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Erreur lors de la création du répertoire temporaire %s :"
+
+#: gitk:3255
+msgid "command failed:"
+msgstr "échec de la commande :"
+
+#: gitk:3401
+msgid "No such commit"
+msgstr "Commit inexistant"
+
+#: gitk:3415
+msgid "git gui blame: command failed:"
+msgstr "git gui blame : échec de la commande :"
+
+#: gitk:3446
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Impossible de lire le head de la fusion : %s"
+
+#: gitk:3454
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Erreur à la lecture de l'index : %s"
+
+#: gitk:3479
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Impossible de démarrer git blame : %s"
+
+#: gitk:3482 gitk:6271
+msgid "Searching"
+msgstr "Recherche en cours"
+
+#: gitk:3514
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Erreur à l'exécution de git blame : %s"
+
+#: gitk:3542
+#, tcl-format
+msgid "That line comes from commit %s,  which is not in this view"
+msgstr "Cette ligne est issue du commit %s, qui n'est pas dans cette vue"
+
+#: gitk:3556
+msgid "External diff viewer failed:"
+msgstr "Échec de l'outil externe de visualisation des diff"
+
+#: gitk:3674
+msgid "Gitk view definition"
+msgstr "Définition des vues de Gitk"
+
+#: gitk:3678
+msgid "Remember this view"
+msgstr "Se souvenir de cette vue"
+
+#: gitk:3679
+msgid "References (space separated list):"
+msgstr "Références (liste d'éléments séparés par des espaces) :"
+
+#: gitk:3680
+msgid "Branches & tags:"
+msgstr "Branches & tags :"
+
+#: gitk:3681
+msgid "All refs"
+msgstr "Toutes les références"
+
+#: gitk:3682
+msgid "All (local) branches"
+msgstr "Toutes les branches (locales)"
+
+#: gitk:3683
+msgid "All tags"
+msgstr "Tous les tags"
+
+#: gitk:3684
+msgid "All remote-tracking branches"
+msgstr "Toutes les branches de suivi à distance"
+
+#: gitk:3685
+msgid "Commit Info (regular expressions):"
+msgstr "Info sur les commits (expressions régulières) :"
+
+#: gitk:3686
+msgid "Author:"
+msgstr "Auteur :"
+
+#: gitk:3687
+msgid "Committer:"
+msgstr "Commiteur :"
+
+#: gitk:3688
+msgid "Commit Message:"
+msgstr "Message de commit :"
+
+#: gitk:3689
+msgid "Matches all Commit Info criteria"
+msgstr "Correspond à tous les critères d'Info sur les commits"
+
+#: gitk:3690
+msgid "Changes to Files:"
+msgstr "Changements des fichiers :"
+
+#: gitk:3691
+msgid "Fixed String"
+msgstr "Chaîne Figée"
+
+#: gitk:3692
+msgid "Regular Expression"
+msgstr "Expression Régulière"
+
+#: gitk:3693
+msgid "Search string:"
+msgstr "Recherche de la chaîne :"
+
+#: gitk:3694
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"Dates des commits (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, "
+"2009 15:27:38\") :"
+
+#: gitk:3695
+msgid "Since:"
+msgstr "De :"
+
+#: gitk:3696
+msgid "Until:"
+msgstr "Jusqu'au :"
+
+#: gitk:3697
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Limiter et/ou sauter un certain nombre (entier positif) de révisions :"
+
+#: gitk:3698
+msgid "Number to show:"
+msgstr "Nombre à afficher :"
+
+#: gitk:3699
+msgid "Number to skip:"
+msgstr "Nombre à sauter :"
+
+#: gitk:3700
+msgid "Miscellaneous options:"
+msgstr "Options diverses :"
+
+#: gitk:3701
+msgid "Strictly sort by date"
+msgstr "Trier par date"
+
+# FIXME : traduction de "branch sides"
+#: gitk:3702
+#, fuzzy
+msgid "Mark branch sides"
+msgstr "Marquer les extrémités des branches"
+
+#: gitk:3703
+msgid "Limit to first parent"
+msgstr "Limiter au premier ancêtre"
+
+#: gitk:3704
+msgid "Simple history"
+msgstr "Historique simple"
+
+#: gitk:3705
+msgid "Additional arguments to git log:"
+msgstr "Arguments supplémentaires de git log :"
+
+#: gitk:3706
+msgid "Enter files and directories to include, one per line:"
+msgstr "Saisir les fichiers et répertoires à inclure, un par ligne :"
+
+#: gitk:3707
+msgid "Command to generate more commits to include:"
+msgstr "Commande pour générer plus de commits à inclure :"
+
+#: gitk:3829
+msgid "Gitk: edit view"
+msgstr "Gitk : éditer la vue"
+
+#: gitk:3837
+msgid "-- criteria for selecting revisions"
+msgstr "-- critère pour la sélection des révisions"
+
+#: gitk:3842
+msgid "View Name:"
+msgstr "Nom de la vue :"
+
+#: gitk:3917
+msgid "Apply (F5)"
+msgstr "Appliquer (F5)"
+
+#: gitk:3955
+msgid "Error in commit selection arguments:"
+msgstr "Erreur dans les arguments de sélection des commits :"
+
+#: gitk:4008 gitk:4060 gitk:4508 gitk:4522 gitk:5783 gitk:11196 gitk:11197
+msgid "None"
+msgstr "Aucun"
+
+#: gitk:4456 gitk:6303 gitk:8065 gitk:8080
+msgid "Date"
+msgstr "Date"
+
+#: gitk:4456 gitk:6303
+msgid "CDate"
+msgstr "CDate"
+
+#: gitk:4605 gitk:4610
+msgid "Descendant"
+msgstr "Descendant"
+
+#: gitk:4606
+msgid "Not descendant"
+msgstr "Pas un descendant"
+
+#: gitk:4613 gitk:4618
+msgid "Ancestor"
+msgstr "Ancêtre"
+
+#: gitk:4614
+msgid "Not ancestor"
+msgstr "Pas un ancêtre"
+
+#: gitk:4904
+msgid "Local changes checked in to index but not committed"
+msgstr "Modifications locales enregistrées dans l'index mais non commitées"
+
+#: gitk:4940
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Modifications locales non enregistrées dans l'index et non commitées"
+
+#: gitk:6621
+msgid "many"
+msgstr "nombreux"
+
+#: gitk:6805
+msgid "Tags:"
+msgstr "Tags :"
+
+#: gitk:6822 gitk:6828 gitk:8058
+msgid "Parent"
+msgstr "Parent"
+
+#: gitk:6833
+msgid "Child"
+msgstr "Enfant"
+
+#: gitk:6842
+msgid "Branch"
+msgstr "Branche"
+
+#: gitk:6845
+msgid "Follows"
+msgstr "Suit"
+
+#: gitk:6848
+msgid "Precedes"
+msgstr "Précède"
+
+#: gitk:7346
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Erreur lors de la récupération des diff : %s"
+
+#: gitk:7886
+msgid "Goto:"
+msgstr "Aller à :"
+
+#: gitk:7888
+msgid "SHA1 ID:"
+msgstr "Id SHA1 :"
+
+#: gitk:7907
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "Id SHA1 court %s est ambigu"
+
+#: gitk:7914
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "Id SHA1 %s est inconnu"
+
+#: gitk:7924
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "Id SHA1 %s est inconnu"
+
+#: gitk:7926
+#, tcl-format
+msgid "Revision %s is not in the current view"
+msgstr "La révision %s n'est pas dans la vue courante"
+
+#: gitk:8068
+msgid "Children"
+msgstr "Enfants"
+
+#: gitk:8125
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Réinitialiser la branche %s vers cet état"
+
+#: gitk:8127
+msgid "Detached head: can't reset"
+msgstr "Head détaché : impossible de réinitialiser"
+
+#: gitk:8236 gitk:8242
+msgid "Skipping merge commit "
+msgstr "Éviter le commit de la fusion "
+
+#: gitk:8251 gitk:8256
+msgid "Error getting patch ID for "
+msgstr "Erreur à l'obtention de l'ID du patch pour "
+
+#: gitk:8252 gitk:8257
+msgid " - stopping\n"
+msgstr " - arrêt en cours\n"
+
+#: gitk:8262 gitk:8265 gitk:8273 gitk:8283 gitk:8292
+msgid "Commit "
+msgstr "Commit "
+
+#: gitk:8266
+msgid ""
+" is the same patch as\n"
+"       "
+msgstr ""
+"est le même patch que \n"
+"       "
+
+#: gitk:8274
+msgid ""
+" differs from\n"
+"       "
+msgstr ""
+" diffère de\n"
+"       "
+
+#: gitk:8276
+msgid "- stopping\n"
+msgstr "- arrêt en cours\n"
+
+#: gitk:8284 gitk:8293
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr "a %s enfants - arrêt en cours\n"
+
+#: gitk:8324
+msgid "Top"
+msgstr "Haut"
+
+#: gitk:8325
+msgid "From"
+msgstr "De"
+
+#: gitk:8330
+msgid "To"
+msgstr "À"
+
+#: gitk:8354
+msgid "Generate patch"
+msgstr "Générer le patch"
+
+#: gitk:8356
+msgid "From:"
+msgstr "De :"
+
+#: gitk:8365
+msgid "To:"
+msgstr "À :"
+
+#: gitk:8374
+msgid "Reverse"
+msgstr "Inverser"
+
+#: gitk:8376 gitk:8561
+msgid "Output file:"
+msgstr "Fichier de sortie :"
+
+#: gitk:8382
+msgid "Generate"
+msgstr "Générer"
+
+#: gitk:8420
+msgid "Error creating patch:"
+msgstr "Erreur à la création du patch :"
+
+#: gitk:8443 gitk:8549 gitk:8606
+msgid "ID:"
+msgstr "ID :"
+
+#: gitk:8452
+msgid "Tag name:"
+msgstr "Nom du Tag :"
+
+#: gitk:8456 gitk:8615
+msgid "Create"
+msgstr "Créer"
+
+#: gitk:8473
+msgid "No tag name specified"
+msgstr "Aucun nom de tag spécifié"
+
+#: gitk:8477
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "Le tag \"%s\" existe déjà"
+
+#: gitk:8483
+msgid "Error creating tag:"
+msgstr "Erreur à la création du tag :"
+
+#: gitk:8558
+msgid "Command:"
+msgstr "Commande :"
+
+#: gitk:8566
+msgid "Write"
+msgstr "Écrire"
+
+#: gitk:8584
+msgid "Error writing commit:"
+msgstr "Erreur à l'ecriture du commit :"
+
+#: gitk:8611
+msgid "Name:"
+msgstr "Nom :"
+
+#: gitk:8634
+msgid "Please specify a name for the new branch"
+msgstr "Veuillez spécifier un nom pour la nouvelle branche"
+
+#: gitk:8639
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "La branche '%s' existe déjà. Écraser?"
+
+#: gitk:8705
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr ""
+"Le Commit %s est déjà inclus dans la branche %s -- le ré-appliquer malgré "
+"tout?"
+
+#: gitk:8710
+msgid "Cherry-picking"
+msgstr "Cueillir (Cherry-picking)"
+
+#: gitk:8719
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"La cueillette (cherry-pick) a échouée à cause de modifications locales du "
+"fichier '%s'.\n"
+"Veuillez commiter, réinitialiser ou stasher vos changements et essayer de "
+"nouveau."
+
+#: gitk:8725
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"La cueillette (cherry-pick) a échouée à cause d'un conflit lors d'une "
+"fusion.\n"
+"Souhaitez-vous exécuter git citool pour le résoudre ?"
+
+#: gitk:8741
+msgid "No changes committed"
+msgstr "Aucun changement commité"
+
+#: gitk:8767
+msgid "Confirm reset"
+msgstr "Confirmer la réinitialisation"
+
+#: gitk:8769
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "Réinitialiser la branche %s à %s?"
+
+#: gitk:8773
+msgid "Reset type:"
+msgstr "Type de réinitialisation :"
+
+#: gitk:8777
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Douce : Laisse le répertoire de travail et l'index intacts"
+
+#: gitk:8780
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr ""
+"Hybride : Laisse le répertoire de travail dans son état courant, "
+"réinitialise l'index"
+
+#: gitk:8783
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Dure : Réinitialise le répertoire de travail et l'index\n"
+"(abandonne TOUS les changements locaux)"
+
+#: gitk:8800
+msgid "Resetting"
+msgstr "Réinitialisation"
+
+# Fixme: Récupération est-il vraiment une mauvaise traduction?
+#: gitk:8857
+#, fuzzy
+msgid "Checking out"
+msgstr "Récupération"
+
+#: gitk:8910
+msgid "Cannot delete the currently checked-out branch"
+msgstr "Impossible de supprimer la branche en cours"
+
+#: gitk:8916
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"Les commits de la branche %s ne sont dans aucune autre branche.\n"
+"Voulez-vous vraiment supprimer cette branche %s ?"
+
+#: gitk:8947
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Tags et heads : %s"
+
+#: gitk:8962
+msgid "Filter"
+msgstr "Filtrer"
+
+#: gitk:9257
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Erreur à la lecture des informations sur la topologie des commits, les "
+"informations sur les branches et les tags précédents/suivants seront "
+"incomplètes."
+
+#: gitk:10243
+msgid "Tag"
+msgstr "Tag"
+
+#: gitk:10243
+msgid "Id"
+msgstr "Id"
+
+#: gitk:10291
+msgid "Gitk font chooser"
+msgstr "Sélecteur de police de Gitk"
+
+#: gitk:10308
+msgid "B"
+msgstr "B"
+
+#: gitk:10311
+msgid "I"
+msgstr "I"
+
+#: gitk:10407
+msgid "Gitk preferences"
+msgstr "Préférences de Gitk"
+
+#: gitk:10409
+msgid "Commit list display options"
+msgstr "Options d'affichage de la liste des commits"
+
+#: gitk:10412
+msgid "Maximum graph width (lines)"
+msgstr "Longueur maximum du graphe (lignes)"
+
+# FIXME : Traduction standard de "pane"?
+#: gitk:10416
+#, fuzzy, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Longueur maximum du graphe (% du panneau)"
+
+#: gitk:10420
+msgid "Show local changes"
+msgstr "Montrer les changements locaux"
+
+#: gitk:10423
+msgid "Auto-select SHA1"
+msgstr "Sélection auto. du SHA1"
+
+#: gitk:10427
+msgid "Diff display options"
+msgstr "Options d'affichage des diff"
+
+#: gitk:10429
+msgid "Tab spacing"
+msgstr "Taille des tabulations"
+
+#: gitk:10432
+msgid "Display nearby tags"
+msgstr "Afficher les tags les plus proches"
+
+#: gitk:10435
+msgid "Hide remote refs"
+msgstr "Cacher les refs distantes"
+
+#: gitk:10438
+msgid "Limit diffs to listed paths"
+msgstr "Limiter les différences aux chemins listés"
+
+#: gitk:10441
+msgid "Support per-file encodings"
+msgstr "Support pour un encodage des caractères par fichier"
+
+#: gitk:10447 gitk:10512
+msgid "External diff tool"
+msgstr "Outil diff externe"
+
+#: gitk:10449
+msgid "Choose..."
+msgstr "Choisir..."
+
+#: gitk:10454
+msgid "Colors: press to choose"
+msgstr "Couleurs : cliquer pour choisir"
+
+#: gitk:10457
+msgid "Background"
+msgstr "Arrière-plan"
+
+#: gitk:10458 gitk:10488
+msgid "background"
+msgstr "arrière-plan"
+
+#: gitk:10461
+msgid "Foreground"
+msgstr "Premier plan"
+
+#: gitk:10462
+msgid "foreground"
+msgstr "premier plan"
+
+#: gitk:10465
+msgid "Diff: old lines"
+msgstr "Diff : anciennes lignes"
+
+#: gitk:10466
+msgid "diff old lines"
+msgstr "diff anciennes lignes"
+
+#: gitk:10470
+msgid "Diff: new lines"
+msgstr "Diff : nouvelles lignes"
+
+#: gitk:10471
+msgid "diff new lines"
+msgstr "diff nouvelles lignes"
+
+#: gitk:10475
+msgid "Diff: hunk header"
+msgstr "Diff : entête du hunk"
+
+#: gitk:10477
+msgid "diff hunk header"
+msgstr "diff : entête du hunk"
+
+#: gitk:10481
+msgid "Marked line bg"
+msgstr "Arrière-plan de la ligne marquée"
+
+#: gitk:10483
+msgid "marked line background"
+msgstr "Arrière-plan de la ligne marquée"
+
+#: gitk:10487
+msgid "Select bg"
+msgstr "Sélectionner l'arrière-plan"
+
+#: gitk:10491
+msgid "Fonts: press to choose"
+msgstr "Polices : cliquer pour choisir"
+
+#: gitk:10493
+msgid "Main font"
+msgstr "Police principale"
+
+#: gitk:10494
+msgid "Diff display font"
+msgstr "Police d'affichage des diff"
+
+#: gitk:10495
+msgid "User interface font"
+msgstr "Police de l'interface utilisateur"
+
+#: gitk:10522
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk : choisir la couleur de %s"
+
+#: gitk:10973
+msgid ""
+"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+" Gitk requires at least Tcl/Tk 8.4."
+msgstr ""
+"Désolé, gitk ne peut être exécuté avec cette version de Tcl/Tk.\n"
+" Gitk requiert Tcl/Tk version 8.4 ou supérieur."
+
+#: gitk:11101
+msgid "Cannot find a git repository here."
+msgstr "Impossible de trouver un dépôt git ici."
+
+#: gitk:11105
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "Impossible de trouver le répertoire git \"%s\"."
+
+#: gitk:11152
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "Argument '%s' ambigu : à la fois une révision et un nom de fichier"
+
+#: gitk:11164
+msgid "Bad arguments to gitk:"
+msgstr "Arguments invalides pour gitk :"
+
+#: gitk:11249
+msgid "Command line"
+msgstr "Ligne de commande"
diff --git a/gitk-git/po/hu.po b/gitk-git/po/hu.po
new file mode 100644 (file)
index 0000000..1df212e
--- /dev/null
@@ -0,0 +1,1295 @@
+# Translation of gitk to Hungarian.
+# Copyright (C) 2007 Paul Mackerras.
+# This file is distributed under the same license as the gitk package.
+#
+# Laszlo Papp <laszlo.papp@arhungary.hu>, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: git-gui\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-12-14 13:33+0100\n"
+"PO-Revision-Date: 2009-12-14 14:04+0100\n"
+"Last-Translator: Laszlo Papp <djszapi@archlinux.us>\n"
+"Language-Team: Hungarian\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: gitk:115
+msgid "Couldn't get list of unmerged files:"
+msgstr "Nem sikerült letölteni az unmerged fájl listát:"
+
+#: gitk:271
+msgid "Error parsing revisions:"
+msgstr "Hiba történt értelmezés közben:"
+
+#: gitk:326
+msgid "Error executing --argscmd command:"
+msgstr "Hiba történt a végrehajtáskor --argscmd parancs:"
+
+#: gitk:339
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"Nincsen fájl kiválasztva: --merge megadve, de egyetlen fájl sem unmerged."
+
+#: gitk:342
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"Nincsen fájl kiválasztva: --merge megadva, de nincsenek unmerged fájlok a "
+"fájlon belül limit."
+
+#: gitk:364 gitk:511
+msgid "Error executing git log:"
+msgstr "Hiba történt a git log végrehajtása közben:"
+
+#: gitk:382 gitk:527
+msgid "Reading"
+msgstr "Olvasás"
+
+#: gitk:442 gitk:4258
+msgid "Reading commits..."
+msgstr "Commitok olvasása ..."
+
+#: gitk:445 gitk:1575 gitk:4261
+msgid "No commits selected"
+msgstr "Nincsen commit kiválasztva"
+
+#: gitk:1451
+msgid "Can't parse git log output:"
+msgstr "Nem lehet értelmezni a git log kimenetét:"
+
+#: gitk:1671
+msgid "No commit information available"
+msgstr "Nincsen elérhető commit információ"
+
+#: gitk:1813
+msgid "mc"
+msgstr "mc"
+
+#: gitk:1848 gitk:4051 gitk:9029 gitk:10570 gitk:10789
+msgid "OK"
+msgstr "OK"
+
+#: gitk:1850 gitk:4053 gitk:8629 gitk:8703 gitk:8813 gitk:8862 gitk:9031
+#: gitk:10571 gitk:10790
+msgid "Cancel"
+msgstr "Visszavonás"
+
+#: gitk:1972
+msgid "Update"
+msgstr "Frissités"
+
+#: gitk:1973
+msgid "Reload"
+msgstr "Újratöltés"
+
+#: gitk:1974
+msgid "Reread references"
+msgstr "Referenciák újraolvasása"
+
+#: gitk:1975
+msgid "List references"
+msgstr "Referenciák listázása"
+
+#: gitk:1977
+msgid "Start git gui"
+msgstr "Git gui indítása"
+
+#: gitk:1979
+msgid "Quit"
+msgstr "Kilépés"
+
+#: gitk:1971
+msgid "File"
+msgstr "Fájl"
+
+#: gitk:1983
+msgid "Preferences"
+msgstr "Beállítások"
+
+#: gitk:1982
+msgid "Edit"
+msgstr "Szerkesztés"
+
+#: gitk:1987
+msgid "New view..."
+msgstr "Új nézet ..."
+
+#: gitk:1988
+msgid "Edit view..."
+msgstr "Nézet szerkesztése ..."
+
+#: gitk:1989
+msgid "Delete view"
+msgstr "Nézet törlése"
+
+#: gitk:1991
+msgid "All files"
+msgstr "Minden fájl"
+
+#: gitk:1986 gitk:3805
+msgid "View"
+msgstr "Nézet"
+
+#: gitk:1996 gitk:2006 gitk:2777
+msgid "About gitk"
+msgstr "Gitk névjegy"
+
+#: gitk:1997 gitk:2011
+msgid "Key bindings"
+msgstr "Billentyűkombináció"
+
+#: gitk:1995 gitk:2010
+msgid "Help"
+msgstr "Segítség"
+
+#: gitk:2088
+msgid "SHA1 ID: "
+msgstr "SHA1 ID: "
+
+#: gitk:2119
+msgid "Row"
+msgstr "Sor"
+
+#: gitk:2157
+msgid "Find"
+msgstr "Keresés"
+
+#: gitk:2158
+msgid "next"
+msgstr "következő"
+
+#: gitk:2159
+msgid "prev"
+msgstr "előző"
+
+#: gitk:2160
+msgid "commit"
+msgstr "commit"
+
+#: gitk:2163 gitk:2165 gitk:4419 gitk:4442 gitk:4466 gitk:6407 gitk:6479
+#: gitk:6563
+msgid "containing:"
+msgstr "tartalmazás:"
+
+#: gitk:2166 gitk:3287 gitk:3292 gitk:4494
+msgid "touching paths:"
+msgstr "érintendő útvonalak:"
+
+#: gitk:2167 gitk:4499
+msgid "adding/removing string:"
+msgstr "string hozzáadása/törlése:"
+
+#: gitk:2176 gitk:2178
+msgid "Exact"
+msgstr "Pontos"
+
+#: gitk:2178 gitk:4574 gitk:6375
+msgid "IgnCase"
+msgstr "Kis/nagy betű nem számít"
+
+#: gitk:2178 gitk:4468 gitk:4572 gitk:6371
+msgid "Regexp"
+msgstr "Regexp"
+
+#: gitk:2180 gitk:2181 gitk:4593 gitk:4623 gitk:4630 gitk:6499 gitk:6567
+msgid "All fields"
+msgstr "Minden mező"
+
+#: gitk:2181 gitk:4591 gitk:4623 gitk:6438
+msgid "Headline"
+msgstr "Főcím"
+
+#: gitk:2182 gitk:4591 gitk:6438 gitk:6567 gitk:7000
+msgid "Comments"
+msgstr "Megjegyzések"
+
+#: gitk:2182 gitk:4591 gitk:4595 gitk:4630 gitk:6438 gitk:6935 gitk:8280
+#: gitk:8295
+msgid "Author"
+msgstr "Szerző"
+
+#: gitk:2182 gitk:4591 gitk:6438 gitk:6937
+msgid "Committer"
+msgstr "Commitoló"
+
+#: gitk:2213
+msgid "Search"
+msgstr "Keresés"
+
+#: gitk:2221
+msgid "Diff"
+msgstr "Diff"
+
+#: gitk:2223
+msgid "Old version"
+msgstr "Régi verzió"
+
+#: gitk:2225
+msgid "New version"
+msgstr "Új verzió"
+
+#: gitk:2227
+msgid "Lines of context"
+msgstr "Tartalmi sorok"
+
+#: gitk:2237
+msgid "Ignore space change"
+msgstr "Space váltás mellőzése"
+
+#: gitk:2296
+msgid "Patch"
+msgstr "Patch"
+
+#: gitk:2298
+msgid "Tree"
+msgstr "Tree"
+
+#: gitk:2453 gitk:2470
+msgid "Diff this -> selected"
+msgstr "Diff ezeket -> kiválasztott"
+
+#: gitk:2454 gitk:2471
+msgid "Diff selected -> this"
+msgstr "Diff kiválasztottakat -> ezt"
+
+#: gitk:2455 gitk:2472
+msgid "Make patch"
+msgstr "Patch készítése"
+
+#: gitk:2456 gitk:8687
+msgid "Create tag"
+msgstr "Tag készítése"
+
+#: gitk:2457 gitk:8793
+msgid "Write commit to file"
+msgstr "Commit fáljba írása"
+
+#: gitk:2458 gitk:8850
+msgid "Create new branch"
+msgstr "Új branch készítése"
+
+#: gitk:2459
+msgid "Cherry-pick this commit"
+msgstr "Cherry-pick erre a commitra"
+
+#: gitk:2460
+msgid "Reset HEAD branch to here"
+msgstr "HEAD branch újraindítása ide"
+
+#: gitk:2461
+msgid "Mark this commit"
+msgstr "Ezen commit megjelölése"
+
+#: gitk:2462
+msgid "Return to mark"
+msgstr "Visszatérés a megjelöléshez"
+
+#: gitk:2463
+msgid "Find descendant of this and mark"
+msgstr "Találd meg ezen utódokat és jelöld meg"
+
+#: gitk:2464
+msgid "Compare with marked commit"
+msgstr "Összehasonlítás a megjelölt commit-tal"
+
+#: gitk:2478
+msgid "Check out this branch"
+msgstr "Check out ezt a branchot"
+
+#: gitk:2479
+msgid "Remove this branch"
+msgstr "Töröld ezt a branch-ot"
+
+#: gitk:2486
+msgid "Highlight this too"
+msgstr "Emeld ki ezt is"
+
+#: gitk:2487
+msgid "Highlight this only"
+msgstr "Csak ezt emeld ki"
+
+#: gitk:2488
+msgid "External diff"
+msgstr "Külső diff"
+
+#: gitk:2489
+msgid "Blame parent commit"
+msgstr "Blame szülő kommitra"
+
+#: gitk:2496
+msgid "Show origin of this line"
+msgstr "Mutasd meg ennek a sornak az eredetét"
+
+#: gitk:2497
+msgid "Run git gui blame on this line"
+msgstr "Futtasd a git gui blame-t ezen a soron"
+
+#: gitk:2779
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright ©9 2005-2009 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - commit nézegető a githez\n"
+"\n"
+"Szerzői jog ©9 2005-2009 Paul Mackerras\n"
+"\n"
+"Használd és terjeszd a GNU General Public License feltételei mellett"
+
+#: gitk:2787 gitk:2851 gitk:9215
+msgid "Close"
+msgstr "Bezárás"
+
+#: gitk:2808
+msgid "Gitk key bindings"
+msgstr "Gitk-billentyű hozzárendelés"
+
+#: gitk:2811
+msgid "Gitk key bindings:"
+msgstr "Gitk-billentyű hozzaárendelés:"
+
+#: gitk:2813
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tKilépés"
+
+#: gitk:2814
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Pos1>\t\tElső commithoz"
+
+#: gitk:2815
+msgid "<End>\t\tMove to last commit"
+msgstr "<Ende>\t\tUtolsó commithoz"
+
+#: gitk:2816
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Hoch>, p, i\tEgy committal feljebb"
+
+#: gitk:2817
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Runter>, n, k\tEgy committal lejjebb"
+
+#: gitk:2818
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Links>, z, j\tVissza a history listába"
+
+#: gitk:2819
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Rechts>, x, l\tElőre a history listába"
+
+#: gitk:2820
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<BildHoch>\tEgy lappal feljebb a commit listába"
+
+#: gitk:2821
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<BildRunter>\tEgy lappal lejjebb a commit listába"
+
+#: gitk:2822
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Pos1>\tGörgetés a commit lista tetejéhez"
+
+#: gitk:2823
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-Ende>\tGörgetés a commit lista aljához"
+
+#: gitk:2824
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Hoch>\tEgy sorral feljebb görgetés a commit listában"
+
+#: gitk:2825
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Runter>\tEgy sorral lejjebb görgetés a commit listában"
+
+#: gitk:2826
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-BildHoch>\tEgy lappal feljebb görgetés a commit listában"
+
+#: gitk:2827
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-BildRunter>\tEgy sorral lejjebb görgetés a commit listában"
+
+#: gitk:2828
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Umschalt-Hoch>\tKeresés visszafele (felfele, utolsó commitok)"
+
+#: gitk:2829
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Umschalt-Runter>\tKeresés előre (lefelé; korábbi commitok)"
+
+#: gitk:2830
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Entf>, b\t\tEgy lappal feljebb görgetés a diff nézetben"
+
+#: gitk:2831
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Löschtaste>\tEgy lappal feljebb görgetés a diff nézetben"
+
+#: gitk:2832
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Leertaste>\tEgy lappal lejjebb görgetés a diff nézetben"
+
+#: gitk:2833
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\t18 sorral felfelé görgetés diff nézetben"
+
+#: gitk:2834
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\t18 sorral lejjebb görgetés a diff nézetben"
+
+#: gitk:2835
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tKeresés"
+
+#: gitk:2836
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tKövetkező találathoz"
+
+#: gitk:2837
+msgid "<Return>\tMove to next find hit"
+msgstr "<Eingabetaste>\tKövetkező találathoz"
+
+#: gitk:2838
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tLépj a keresési mezőre"
+
+#: gitk:2839
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tElőző találathoz"
+
+#: gitk:2840
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tKövetkező fájlra görgetés diff nézetben"
+
+#: gitk:2841
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tKövetkező találatra keresés diff nézetben"
+
+#: gitk:2842
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tElőző találatra keresés diff nézetben"
+
+#: gitk:2843
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-Nummerblock-Plus>\tBetűméret növelése"
+
+#: gitk:2844
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-Plus>\tBetűméret növelése"
+
+#: gitk:2845
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-Nummernblock-Minus> Betűméret csökkentése"
+
+#: gitk:2846
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-Minus>\tBetűméret csökkentése"
+
+#: gitk:2847
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tFrissítés"
+
+#: gitk:3302 gitk:3311
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Hiba történt az ideiglenes könyvtár létrehozása közben %s:"
+
+#: gitk:3324
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Hiba történt \"%s\" letöltése közben %s-ről:"
+
+#: gitk:3387
+msgid "command failed:"
+msgstr "parancs hiba:"
+
+#: gitk:3536
+msgid "No such commit"
+msgstr "Nincs ilyen commit"
+
+#: gitk:3550
+msgid "git gui blame: command failed:"
+msgstr "git gui blame: parancs hiba:"
+
+#: gitk:3581
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Nem sikerült a Merge head olvasása: %s"
+
+#: gitk:3589
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Hiba történt az index olvasása közben: %s"
+
+#: gitk:3614
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Nem sikerült a git blame indítása: %s"
+
+#: gitk:3617 gitk:6406
+msgid "Searching"
+msgstr "Keresés"
+
+#: gitk:3649
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Hiba történt a git blame futtatása közben: %s"
+
+#: gitk:3677
+#, tcl-format
+msgid "That line comes from commit %s,  which is not in this view"
+msgstr ""
+"A %s commitból származik az a sor, amelyik nem található ebben a nézetben"
+
+#: gitk:3691
+msgid "External diff viewer failed:"
+msgstr "Külső diff nézegető hiba:"
+
+#: gitk:3809
+msgid "Gitk view definition"
+msgstr "Gitk nézet meghatározása"
+
+#: gitk:3813
+msgid "Remember this view"
+msgstr "Maradj ennél a nézetnél"
+
+#: gitk:3814
+msgid "References (space separated list):"
+msgstr "Referenciák (szóközzel tagolt lista"
+
+#: gitk:3815
+msgid "Branches & tags:"
+msgstr "Branch-ek & tagek:"
+
+#: gitk:3816
+msgid "All refs"
+msgstr "Minden ref"
+
+#: gitk:3817
+msgid "All (local) branches"
+msgstr "Minden (helyi) branch"
+
+#: gitk:3818
+msgid "All tags"
+msgstr "Minden tag"
+
+#: gitk:3819
+msgid "All remote-tracking branches"
+msgstr "Minden távoli követő branch"
+
+#: gitk:3820
+msgid "Commit Info (regular expressions):"
+msgstr "Commit Infó (reguláris kifejezés):"
+
+#: gitk:3821
+msgid "Author:"
+msgstr "Szerző:"
+
+#: gitk:3822
+msgid "Committer:"
+msgstr "Commitoló:"
+
+#: gitk:3823
+msgid "Commit Message:"
+msgstr "Commit üzenet:"
+
+#: gitk:3824
+msgid "Matches all Commit Info criteria"
+msgstr "Egyezik minen Commit Infó feltétellel"
+
+#: gitk:3825
+msgid "Changes to Files:"
+msgstr "Fájl változások:"
+
+#: gitk:3826
+msgid "Fixed String"
+msgstr "Fix String"
+
+#: gitk:3827
+msgid "Regular Expression"
+msgstr "Reguláris kifejezés"
+
+#: gitk:3828
+msgid "Search string:"
+msgstr "Keresés szöveg:"
+
+#: gitk:3829
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"Commit Dátumok (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+
+#: gitk:3830
+msgid "Since:"
+msgstr "Ettől:"
+
+#: gitk:3831
+msgid "Until:"
+msgstr "Eddig:"
+
+#: gitk:3832
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Limitálva és/vagy kihagyva egy adott számú revíziót (pozitív egész):"
+
+#: gitk:3833
+msgid "Number to show:"
+msgstr "Mutatandó szám:"
+
+#: gitk:3834
+msgid "Number to skip:"
+msgstr "Kihagyandó szám:"
+
+#: gitk:3835
+msgid "Miscellaneous options:"
+msgstr "Különféle opciók:"
+
+#: gitk:3836
+msgid "Strictly sort by date"
+msgstr "Szigorú rendezás dátum alapján"
+
+#: gitk:3837
+msgid "Mark branch sides"
+msgstr "Jelölje meg az ágakat"
+
+#: gitk:3838
+msgid "Limit to first parent"
+msgstr "Korlátozás az első szülőre"
+
+#: gitk:3839
+msgid "Simple history"
+msgstr "Egyszerű history"
+
+#: gitk:3840
+msgid "Additional arguments to git log:"
+msgstr "További argumentok a git log-hoz:"
+
+#: gitk:3841
+msgid "Enter files and directories to include, one per line:"
+msgstr "Fájlok és könyvtárak bejegyzése amiket tartalmaz, soronként:"
+
+#: gitk:3842
+msgid "Command to generate more commits to include:"
+msgstr "Parancs több tartalmazó commit generálására:"
+
+#: gitk:3964
+msgid "Gitk: edit view"
+msgstr "Gitk: szerkesztés nézet"
+
+#: gitk:3972
+msgid "-- criteria for selecting revisions"
+msgstr "-- kritériumok a revíziók kiválasztásához"
+
+#: gitk:3977
+msgid "View Name"
+msgstr "Nézet neve"
+
+#: gitk:4052
+msgid "Apply (F5)"
+msgstr "Alkalmaz (F5)"
+
+#: gitk:4090
+msgid "Error in commit selection arguments:"
+msgstr "Hiba történt a commit argumentumok kiválasztása közben:"
+
+#: gitk:4143 gitk:4195 gitk:4643 gitk:4657 gitk:5918 gitk:11519 gitk:11520
+msgid "None"
+msgstr "Keine"
+
+#: gitk:4591 gitk:6438 gitk:8282 gitk:8297
+msgid "Date"
+msgstr "Dátum"
+
+#: gitk:4591 gitk:6438
+msgid "CDate"
+msgstr "Dátum"
+
+#: gitk:4740 gitk:4745
+msgid "Descendant"
+msgstr "Leszármazott"
+
+#: gitk:4741
+msgid "Not descendant"
+msgstr "Nem leszármazott"
+
+#: gitk:4748 gitk:4753
+msgid "Ancestor"
+msgstr "Előd"
+
+#: gitk:4749
+msgid "Not ancestor"
+msgstr "Nem előd"
+
+#: gitk:5039
+msgid "Local changes checked in to index but not committed"
+msgstr ""
+"Lokális változtatások, melyek be vannak téve az indexbe, de még nincsenek "
+"commitolva"
+
+#: gitk:5075
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Lokális nem commitolt változások, nincsenek betéve az indexbe"
+
+#: gitk:6756
+msgid "many"
+msgstr "sok"
+
+#: gitk:6939
+msgid "Tags:"
+msgstr "Tagek:"
+
+#: gitk:6956 gitk:6962 gitk:8275
+msgid "Parent"
+msgstr "Eltern"
+
+#: gitk:6967
+msgid "Child"
+msgstr "Gyerek"
+
+#: gitk:6976
+msgid "Branch"
+msgstr "Ág"
+
+#: gitk:6979
+msgid "Follows"
+msgstr "Következők"
+
+#: gitk:6982
+msgid "Precedes"
+msgstr "Megelőzők"
+
+#: gitk:7519
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Hiba történt a diff-ek letöltése közben: %s"
+
+#: gitk:8103
+msgid "Goto:"
+msgstr "Menj:"
+
+#: gitk:8105
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
+
+#: gitk:8124
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "Rövid SHA1 id %s félreérthető"
+
+#: gitk:8131
+msgid "Revision %s is not known"
+msgstr "A(z) %s revízió nem ismert"
+
+#: gitk:8141
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "SHA1 id %s nem ismert"
+
+#: gitk:8143
+#, tcl-format
+msgid "Revision %s is not in the current view"
+msgstr "A(z) %s revízió nincs a jelenlegi nézetben"
+
+#: gitk:8285
+msgid "Children"
+msgstr "Gyerekek"
+
+#: gitk:8343
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Állítsd vissza a %s branch-ot ide"
+
+#: gitk:8345
+msgid "Detached head: can't reset"
+msgstr "Elkülönített head: nem lehet visszaállítani"
+
+#: gitk:8454 gitk:8460
+msgid "Skipping merge commit "
+msgstr "Merge commit kihagyása "
+
+#: gitk:8469 gitk:8474
+msgid "Error getting patch ID for "
+msgstr "Hiba történt a patch ID megszerzése közben a következőnél "
+
+#: gitk:8470 gitk:8475
+msgid " - stopping\n"
+msgstr " - abbahagyás\n"
+
+#: gitk:8480 gitk:8483 gitk:8491 gitk:8505 gitk:8514
+msgid "Commit "
+msgstr "Commit "
+
+#: gitk:8484
+msgid ""
+" is the same patch as\n"
+"       "
+msgstr ""
+" Ugyanaz a patch mint\n"
+"       "
+
+#: gitk:8492
+msgid ""
+" differs from\n"
+"       "
+msgstr ""
+" különbözik innentől\n"
+"       "
+
+#: gitk:8494
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr ""
+"A commitok diffje:\n"
+"\n"
+
+#: gitk:8506 gitk:8515
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr " %s gyereke van. abbahagyás\n"
+
+#: gitk:8534
+msgid "Error writing commit to file: %s"
+msgstr "Hiba történt a commit fájlba írása közben: %s"
+
+#: gitk:8540
+msgid "Error diffing commits: %s"
+msgstr "Hiba történt a commitok diffelése közben: %s"
+
+#: gitk:8570
+msgid "Top"
+msgstr "Teteje"
+
+#: gitk:8571
+msgid "From"
+msgstr "Innen"
+
+#: gitk:8576
+msgid "To"
+msgstr "Ide"
+
+#: gitk:8600
+msgid "Generate patch"
+msgstr "Patch generálása"
+
+#: gitk:8602
+msgid "From:"
+msgstr "Innen:"
+
+#: gitk:8611
+msgid "To:"
+msgstr "Ide:"
+
+#: gitk:8620
+msgid "Reverse"
+msgstr "Visszafele"
+
+#: gitk:8622 gitk:8807
+msgid "Output file:"
+msgstr "Kimeneti fájl:"
+
+#: gitk:8628
+msgid "Generate"
+msgstr "Generálás"
+
+#: gitk:8666
+msgid "Error creating patch:"
+msgstr "Hiba törtét a patch készítése közben:"
+
+#: gitk:8689 gitk:8795 gitk:8852
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:8698
+msgid "Tag name:"
+msgstr "Tag név:"
+
+#: gitk:8702 gitk:8861
+msgid "Create"
+msgstr "Létrehozás"
+
+#: gitk:8719
+msgid "No tag name specified"
+msgstr "A tag neve nincsen megadva"
+
+#: gitk:8723
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "%s Tag már létezik"
+
+#: gitk:8729
+msgid "Error creating tag:"
+msgstr "Hiba történt a tag létrehozása közben:"
+
+#: gitk:8804
+msgid "Command:"
+msgstr "Parancs:"
+
+#: gitk:8812
+msgid "Write"
+msgstr "Írás"
+
+#: gitk:8830
+msgid "Error writing commit:"
+msgstr "Hiba történt a commit írása közben:"
+
+#: gitk:8857
+msgid "Name:"
+msgstr "Név:"
+
+#: gitk:8880
+msgid "Please specify a name for the new branch"
+msgstr "Kérem adja meg a nevét az új branchhoz"
+
+#: gitk:8885
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "%s branch már létezik. Felülírja?"
+
+#: gitk:8951
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr ""
+"%s commit már benne van a %s branchban -- biztos hogy újra csinálja ?"
+"eintragen?"
+
+#: gitk:8956
+msgid "Cherry-picking"
+msgstr "Cherry-picking"
+
+#: gitk:8965
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"Cherry-pick hiba történt lokális váltotások miatt a '%s' fájlban.\n"
+"Kérem commitolja, indítsa újra vagy rejtse el a változtatásait és próbálja "
+"újra."
+
+#: gitk:8971
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"Cherry-pick hiba történt merge konfliktus miatt.\n"
+"Kívánja futtatni a git citool-t a probléma megoldásához?"
+
+#: gitk:8987
+msgid "No changes committed"
+msgstr "Nincsen változás commitolva"
+
+#: gitk:9013
+msgid "Confirm reset"
+msgstr "Újraindítás megerősítése"
+
+#: gitk:9015
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "Újraindítja a %s branchot %s-ig?"
+
+#: gitk:9017
+msgid "Reset type:"
+msgstr "Újraindítás típusa:"
+
+#: gitk:9020
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Soft: Hagyd a working tree-t és az indexet érintetlenül"
+
+#: gitk:9023
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr "Kevert: Hagyd a working tree-t érintetlenül, töröld az indexet"
+
+#: gitk:9026
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Hard: Indítsd újra a working tree-t és az indexet\n"
+"(MINDEN lokális változás eldobása)"
+
+#: gitk:9043
+msgid "Resetting"
+msgstr "Újraindítás"
+
+#: gitk:9103
+msgid "Checking out"
+msgstr "Kivesz"
+
+#: gitk:9156
+msgid "Cannot delete the currently checked-out branch"
+msgstr "Nem lehet a jelenleg kivett branch-ot törölni"
+
+#: gitk:9162
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"A %s branchon található commit nem található meg semelyik másik branchon.\n"
+"Tényleg törli a %s branchot?"
+
+#: gitk:9193
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Tagek és headek: %s"
+
+#: gitk:9208
+msgid "Filter"
+msgstr "Szűrő"
+
+#: gitk:9503
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Hiba történt a commit topológiai információ olvasása közben; branch ésa "
+"megelőző/következő információ nem lesz teljes."
+
+#: gitk:10489
+msgid "Tag"
+msgstr "Tag"
+
+#: gitk:10489
+msgid "Id"
+msgstr "Id"
+
+#: gitk:10539
+msgid "Gitk font chooser"
+msgstr "Gitk-betű kiválasztó"
+
+#: gitk:10556
+msgid "B"
+msgstr "F"
+
+#: gitk:10559
+msgid "I"
+msgstr "K"
+
+#: gitk:10677
+msgid "Gitk preferences"
+msgstr "Gitk beállítások"
+
+#: gitk:10679
+msgid "Commit list display options"
+msgstr "Commit lista kijelzési opciók"
+
+#: gitk:10682
+msgid "Maximum graph width (lines)"
+msgstr "Maximális grafikon szélesség (sorok)"
+
+#: gitk:10685
+#, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Maximális grafikon szélesség (táble %-je)"
+
+#: gitk:10688
+msgid "Show local changes"
+msgstr "Mutasd a lokális változtatásokat"
+
+#: gitk:10691
+msgid "Auto-select SHA1"
+msgstr "SHA1 Automatikus kiválasztása"
+
+#: gitk:10694
+msgid "Hide remote refs"
+msgstr "A távoli refek elrejtése"
+
+#: gitk:10698
+msgid "Diff display options"
+msgstr "Diff kijelző opciók"
+
+#: gitk:10700
+msgid "Tab spacing"
+msgstr "Tab sorköz"
+
+#: gitk:10703
+msgid "Display nearby tags"
+msgstr "Szomszédos tagek kijelzése"
+
+#: gitk:10706
+msgid "Limit diffs to listed paths"
+msgstr "Korlátozott diffek a kilistázott útvonalakhoz"
+
+#: gitk:10709
+msgid "Support per-file encodings"
+msgstr "Fájlonkénti kódolás támgatása"
+
+#: gitk:10715 gitk:10804
+msgid "External diff tool"
+msgstr "Külső diff alkalmazás"
+
+#: gitk:10716
+msgid "Choose..."
+msgstr "Válaszd ..."
+
+#: gitk:10721
+msgid "General options"
+msgstr "Általános opciók"
+
+#: gitk:10724
+msgid "Use themed widgets"
+msgstr "Témázott vezérlők használata"
+
+#: gitk:10726
+msgid "(change requires restart)"
+msgstr "(a változás újraindítást igényel)"
+
+#: gitk:10728
+msgid "(currently unavailable)"
+msgstr "(jelenleg nem elérhető)"
+
+#: gitk:10732
+msgid "Colors: press to choose"
+msgstr "Színek: nyomja meg a kiválasztáshoz"
+
+#: gitk:10735
+msgid "Interface"
+msgstr "Interfész"
+
+#: gitk:10736
+msgid "interface"
+msgstr "interfész"
+
+#: gitk:10739
+msgid "Background"
+msgstr "Háttér"
+
+#: gitk:10740 gitk:10770
+msgid "background"
+msgstr "háttér"
+
+#: gitk:10743
+msgid "Foreground"
+msgstr "Előtér"
+
+#: gitk:10744
+msgid "foreground"
+msgstr "előtér"
+
+#: gitk:10747
+msgid "Diff: old lines"
+msgstr "Diff: régi sorok"
+
+#: gitk:10748
+msgid "diff old lines"
+msgstr "diff régi sorok"
+
+#: gitk:10752
+msgid "Diff: new lines"
+msgstr "Diff: új sorok"
+
+#: gitk:10753
+msgid "diff new lines"
+msgstr "diff - új sorok"
+
+#: gitk:10757
+msgid "Diff: hunk header"
+msgstr "Diff: nagy headerök"
+
+#: gitk:10759
+msgid "diff hunk header"
+msgstr "diff - nagy headerök"
+
+#: gitk:10763
+msgid "Marked line bg"
+msgstr "Megjelölt sor háttér"
+
+#: gitk:10765
+msgid "marked line background"
+msgstr "megjelölt sor háttér"
+
+#: gitk:10769
+msgid "Select bg"
+msgstr "Válasszon hátteret"
+
+#: gitk:10773
+msgid "Fonts: press to choose"
+msgstr "Betű: nyomja meg a kiválasztáshoz"
+
+#: gitk:10775
+msgid "Main font"
+msgstr "Fő betű"
+
+#: gitk:10776
+msgid "Diff display font"
+msgstr "Diff kijelző betű"
+
+#: gitk:10777
+msgid "User interface font"
+msgstr "Felhasználói interfész betű"
+
+#: gitk:10814
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: válasszon színt a %s-ra"
+
+#: gitk:11418
+msgid "Cannot find a git repository here."
+msgstr "Nem találhatü git repository itt."
+
+#: gitk:11422
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "Nem található a \"%s\" könyvtár."
+
+#: gitk:11469
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "Félreérthető argumentum '%s': revízió és fájlnév is"
+
+#: gitk:11481
+msgid "Bad arguments to gitk:"
+msgstr "Rossz gitk argumentumok:"
+
+#: gitk:11572
+msgid "Command line"
+msgstr "Parancs sor"
+
+#~ msgid "Use all refs"
+#~ msgstr "Használd az összes referenciát"
+
+#~ msgid "Max count:"
+#~ msgstr "Max. szám:"
+
+#~ msgid "Skip:"
+#~ msgstr "Kihagy:"
+
+#~ msgid "Name"
+#~ msgstr "Név"
+
+#~ msgid "Tag/Head %s is not known"
+#~ msgstr "Tag/Head %s nem ismert"
+
+#~ msgid "- stopping\n"
+#~ msgstr "- abbahagyás.\n"
+
+#~ msgid ""
+#~ "Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+#~ " Gitk requires at least Tcl/Tk 8.4."
+#~ msgstr ""
+#~ "Sajnáljuk, de a gitk nem futtatható ezzel a Tcl/Tk verzióval.\n"
+#~ "Gitk futtatásához legalább Tcl/Tk 8.4 szükséges."
index e89c95702cf71d259543f6bb6713e69f624b9b53..4818652309d91e90fb4595b500cb687bcdedcb94 100644 (file)
@@ -8,25 +8,33 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gitk\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-10-18 22:03+1100\n"
-"PO-Revision-Date: 2008-03-13 17:34+0100\n"
+"POT-Creation-Date: 2010-01-28 18:40+0100\n"
+"PO-Revision-Date: 2010-01-28 18:41+0100\n"
 "Last-Translator: Michele Ballabio <barra_cuda@katamail.com>\n"
 "Language-Team: Italian\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: gitk:113
+#: gitk:115
 msgid "Couldn't get list of unmerged files:"
 msgstr "Impossibile ottenere l'elenco dei file in attesa di fusione:"
 
-#: gitk:340
+#: gitk:274
+msgid "Error parsing revisions:"
+msgstr "Errore nella lettura delle revisioni:"
+
+#: gitk:329
+msgid "Error executing --argscmd command:"
+msgstr "Errore nell'esecuzione del comando specificato con --argscmd:"
+
+#: gitk:342
 msgid "No files selected: --merge specified but no files are unmerged."
 msgstr ""
 "Nessun file selezionato: è stata specificata l'opzione --merge ma non ci "
 "sono file in attesa di fusione."
 
-#: gitk:343
+#: gitk:345
 msgid ""
 "No files selected: --merge specified but no unmerged files are within file "
 "limit."
@@ -34,688 +42,994 @@ msgstr ""
 "Nessun file selezionato: è stata specificata l'opzione --merge ma i file "
 "specificati non sono in attesa di fusione."
 
-#: gitk:365 gitk:503
+#: gitk:367 gitk:514
 msgid "Error executing git log:"
 msgstr "Errore nell'esecuzione di git log:"
 
-#: gitk:378
+#: gitk:385 gitk:530
 msgid "Reading"
 msgstr "Lettura in corso"
 
-#: gitk:438 gitk:3462
+#: gitk:445 gitk:4261
 msgid "Reading commits..."
 msgstr "Lettura delle revisioni in corso..."
 
-#: gitk:441 gitk:1528 gitk:3465
+#: gitk:448 gitk:1578 gitk:4264
 msgid "No commits selected"
 msgstr "Nessuna revisione selezionata"
 
-#: gitk:1399
+#: gitk:1454
 msgid "Can't parse git log output:"
 msgstr "Impossibile elaborare i dati di git log:"
 
-#: gitk:1605
+#: gitk:1674
 msgid "No commit information available"
 msgstr "Nessuna informazione disponibile sulle revisioni"
 
-#: gitk:1709 gitk:1731 gitk:3259 gitk:7764 gitk:9293 gitk:9466
+#: gitk:1816
+msgid "mc"
+msgstr ""
+
+#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804
 msgid "OK"
 msgstr "OK"
 
-#: gitk:1733 gitk:3260 gitk:7439 gitk:7510 gitk:7613 gitk:7660 gitk:7766
-#: gitk:9294 gitk:9467
+#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046
+#: gitk:10586 gitk:10805
 msgid "Cancel"
 msgstr "Annulla"
 
-#: gitk:1811
+#: gitk:1975
 msgid "Update"
 msgstr "Aggiorna"
 
-#: gitk:1813
+#: gitk:1976
+msgid "Reload"
+msgstr "Ricarica"
+
+#: gitk:1977
 msgid "Reread references"
 msgstr "Rileggi riferimenti"
 
-#: gitk:1814
+#: gitk:1978
 msgid "List references"
 msgstr "Elenca riferimenti"
 
-#: gitk:1815
+#: gitk:1980
+msgid "Start git gui"
+msgstr "Avvia git gui"
+
+#: gitk:1982
 msgid "Quit"
 msgstr "Esci"
 
-#: gitk:1810
+#: gitk:1974
 msgid "File"
 msgstr "File"
 
-#: gitk:1818
+#: gitk:1986
 msgid "Preferences"
 msgstr "Preferenze"
 
-#: gitk:1817
+#: gitk:1985
 msgid "Edit"
 msgstr "Modifica"
 
-#: gitk:1821
+#: gitk:1990
 msgid "New view..."
 msgstr "Nuova vista..."
 
-#: gitk:1822
+#: gitk:1991
 msgid "Edit view..."
 msgstr "Modifica vista..."
 
-#: gitk:1823
+#: gitk:1992
 msgid "Delete view"
 msgstr "Elimina vista"
 
-#: gitk:1825
+#: gitk:1994
 msgid "All files"
 msgstr "Tutti i file"
 
-#: gitk:1820 gitk:3196
+#: gitk:1989 gitk:3808
 msgid "View"
 msgstr "Vista"
 
-#: gitk:1828 gitk:2487
+#: gitk:1999 gitk:2009 gitk:2780
 msgid "About gitk"
 msgstr "Informazioni su gitk"
 
-#: gitk:1829
+#: gitk:2000 gitk:2014
 msgid "Key bindings"
 msgstr "Scorciatoie da tastiera"
 
-#: gitk:1827
+#: gitk:1998 gitk:2013
 msgid "Help"
 msgstr "Aiuto"
 
-#: gitk:1887
-msgid "SHA1 ID: "
-msgstr "SHA1 ID: "
+#: gitk:2091 gitk:8110
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
 
-#: gitk:1918
+#: gitk:2122
 msgid "Row"
-msgstr ""
+msgstr "Riga"
 
-#: gitk:1949
+#: gitk:2160
 msgid "Find"
 msgstr "Trova"
 
-#: gitk:1950
+#: gitk:2161
 msgid "next"
 msgstr "succ"
 
-#: gitk:1951
+#: gitk:2162
 msgid "prev"
 msgstr "prec"
 
-#: gitk:1952
+#: gitk:2163
 msgid "commit"
 msgstr "revisione"
 
-#: gitk:1955 gitk:1957 gitk:3617 gitk:3640 gitk:3664 gitk:5550 gitk:5621
+#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482
+#: gitk:6566
 msgid "containing:"
 msgstr "contenente:"
 
-#: gitk:1958 gitk:2954 gitk:2959 gitk:3692
+#: gitk:2169 gitk:3290 gitk:3295 gitk:4497
 msgid "touching paths:"
 msgstr "che riguarda i percorsi:"
 
-#: gitk:1959 gitk:3697
+#: gitk:2170 gitk:4502
 msgid "adding/removing string:"
 msgstr "che aggiunge/rimuove la stringa:"
 
-#: gitk:1968 gitk:1970
+#: gitk:2179 gitk:2181
 msgid "Exact"
 msgstr "Esatto"
 
-#: gitk:1970 gitk:3773 gitk:5518
+#: gitk:2181 gitk:4577 gitk:6378
 msgid "IgnCase"
 msgstr ""
 
-#: gitk:1970 gitk:3666 gitk:3771 gitk:5514
+#: gitk:2181 gitk:4471 gitk:4575 gitk:6374
 msgid "Regexp"
 msgstr ""
 
-#: gitk:1972 gitk:1973 gitk:3792 gitk:3822 gitk:3829 gitk:5641 gitk:5708
+#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570
 msgid "All fields"
 msgstr "Tutti i campi"
 
-#: gitk:1973 gitk:3790 gitk:3822 gitk:5580
+#: gitk:2184 gitk:4594 gitk:4626 gitk:6441
 msgid "Headline"
 msgstr "Titolo"
 
-#: gitk:1974 gitk:3790 gitk:5580 gitk:5708 gitk:6109
+#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003
 msgid "Comments"
 msgstr "Commenti"
 
-#: gitk:1974 gitk:3790 gitk:3794 gitk:3829 gitk:5580 gitk:6045 gitk:7285
-#: gitk:7300
+#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285
+#: gitk:8300
 msgid "Author"
 msgstr "Autore"
 
-#: gitk:1974 gitk:3790 gitk:5580 gitk:6047
+#: gitk:2185 gitk:4594 gitk:6441 gitk:6940
 msgid "Committer"
 msgstr "Revisione creata da"
 
-#: gitk:2003
+#: gitk:2216
 msgid "Search"
 msgstr "Cerca"
 
-#: gitk:2010
+#: gitk:2224
 msgid "Diff"
 msgstr ""
 
-#: gitk:2012
+#: gitk:2226
 msgid "Old version"
 msgstr "Vecchia versione"
 
-#: gitk:2014
+#: gitk:2228
 msgid "New version"
 msgstr "Nuova versione"
 
-#: gitk:2016
+#: gitk:2230
 msgid "Lines of context"
 msgstr "Linee di contesto"
 
-#: gitk:2026
+#: gitk:2240
 msgid "Ignore space change"
 msgstr "Ignora modifiche agli spazi"
 
-#: gitk:2084
+#: gitk:2299
 msgid "Patch"
 msgstr "Modifiche"
 
-#: gitk:2086
+#: gitk:2301
 msgid "Tree"
 msgstr "Directory"
 
-#: gitk:2213 gitk:2226
+#: gitk:2456 gitk:2473
 msgid "Diff this -> selected"
 msgstr "Diff questo -> selezionato"
 
-#: gitk:2214 gitk:2227
+#: gitk:2457 gitk:2474
 msgid "Diff selected -> this"
 msgstr "Diff selezionato -> questo"
 
-#: gitk:2215 gitk:2228
+#: gitk:2458 gitk:2475
 msgid "Make patch"
 msgstr "Crea patch"
 
-#: gitk:2216 gitk:7494
+#: gitk:2459 gitk:8692
 msgid "Create tag"
 msgstr "Crea etichetta"
 
-#: gitk:2217 gitk:7593
+#: gitk:2460 gitk:8808
 msgid "Write commit to file"
 msgstr "Scrivi revisione in un file"
 
-#: gitk:2218 gitk:7647
+#: gitk:2461 gitk:8865
 msgid "Create new branch"
 msgstr "Crea un nuovo ramo"
 
-#: gitk:2219
+#: gitk:2462
 msgid "Cherry-pick this commit"
 msgstr "Porta questa revisione in cima al ramo attuale"
 
-#: gitk:2220
+#: gitk:2463
 msgid "Reset HEAD branch to here"
 msgstr "Aggiorna il ramo HEAD a questa revisione"
 
-#: gitk:2234
+#: gitk:2464
+msgid "Mark this commit"
+msgstr "Segna questa revisione"
+
+#: gitk:2465
+msgid "Return to mark"
+msgstr "Torna alla revisione segnata"
+
+#: gitk:2466
+msgid "Find descendant of this and mark"
+msgstr "Trova il discendente di questa revisione e di quella segnata"
+
+#: gitk:2467
+msgid "Compare with marked commit"
+msgstr "Confronta con la revisione segnata"
+
+#: gitk:2481
 msgid "Check out this branch"
 msgstr "Attiva questo ramo"
 
-#: gitk:2235
+#: gitk:2482
 msgid "Remove this branch"
 msgstr "Elimina questo ramo"
 
-#: gitk:2242
+#: gitk:2489
 msgid "Highlight this too"
 msgstr "Evidenzia anche questo"
 
-#: gitk:2243
+#: gitk:2490
 msgid "Highlight this only"
 msgstr "Evidenzia solo questo"
 
-#: gitk:2245
+#: gitk:2491
+msgid "External diff"
+msgstr "Visualizza differenze in un altro programma"
+
+#: gitk:2492
 msgid "Blame parent commit"
-msgstr ""
+msgstr "Annota la revisione precedente"
+
+#: gitk:2499
+msgid "Show origin of this line"
+msgstr "Mostra la provenienza di questa riga"
 
-#: gitk:2488
+#: gitk:2500
+msgid "Run git gui blame on this line"
+msgstr "Esegui git gui blame su questa riga"
+
+#: gitk:2782
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright © 2005-2009 Paul Mackerras\n"
 "\n"
 "Use and redistribute under the terms of the GNU General Public License"
 msgstr ""
 "\n"
 "Gitk - un visualizzatore di revisioni per git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright © 2005-2009 Paul Mackerras\n"
 "\n"
 "Utilizzo e redistribuzione permessi sotto i termini della GNU General Public "
 "License"
 
-#: gitk:2496 gitk:2557 gitk:7943
+#: gitk:2790 gitk:2854 gitk:9230
 msgid "Close"
 msgstr "Chiudi"
 
-#: gitk:2515
+#: gitk:2811
 msgid "Gitk key bindings"
 msgstr "Scorciatoie da tastiera di Gitk"
 
-#: gitk:2517
+#: gitk:2814
 msgid "Gitk key bindings:"
 msgstr "Scorciatoie da tastiera di Gitk:"
 
-#: gitk:2519
+#: gitk:2816
 #, tcl-format
 msgid "<%s-Q>\t\tQuit"
 msgstr "<%s-Q>\t\tEsci"
 
-#: gitk:2520
+#: gitk:2817
 msgid "<Home>\t\tMove to first commit"
 msgstr "<Home>\t\tVai alla prima revisione"
 
-#: gitk:2521
+#: gitk:2818
 msgid "<End>\t\tMove to last commit"
 msgstr "<End>\t\tVai all'ultima revisione"
 
-#: gitk:2522
+#: gitk:2819
 msgid "<Up>, p, i\tMove up one commit"
-msgstr "<Up>, p, i\tVai più in alto di una revisione"
+msgstr "<Su>, p, i\tVai più in alto di una revisione"
 
-#: gitk:2523
+#: gitk:2820
 msgid "<Down>, n, k\tMove down one commit"
-msgstr "<Down>, n, k\tVai più in basso di una revisione"
+msgstr "<Giù>, n, k\tVai più in basso di una revisione"
 
-#: gitk:2524
+#: gitk:2821
 msgid "<Left>, z, j\tGo back in history list"
-msgstr "<Left>, z, j\tTorna indietro nella cronologia"
+msgstr "<Sinistra>, z, j\tTorna indietro nella cronologia"
 
-#: gitk:2525
+#: gitk:2822
 msgid "<Right>, x, l\tGo forward in history list"
-msgstr "<Right>, x, l\tVai avanti nella cronologia"
+msgstr "<Destra>, x, l\tVai avanti nella cronologia"
 
-#: gitk:2526
+#: gitk:2823
 msgid "<PageUp>\tMove up one page in commit list"
-msgstr "<PageUp>\tVai più in alto di una pagina nella lista delle revisioni"
+msgstr "<PaginaSu>\tVai più in alto di una pagina nella lista delle revisioni"
 
-#: gitk:2527
+#: gitk:2824
 msgid "<PageDown>\tMove down one page in commit list"
-msgstr "<PageDown>\tVai più in basso di una pagina nella lista delle revisioni"
+msgstr ""
+"<PaginaGiù>\tVai più in basso di una pagina nella lista delle revisioni"
 
-#: gitk:2528
+#: gitk:2825
 #, tcl-format
 msgid "<%s-Home>\tScroll to top of commit list"
 msgstr "<%s-Home>\tScorri alla cima della lista delle revisioni"
 
-#: gitk:2529
+#: gitk:2826
 #, tcl-format
 msgid "<%s-End>\tScroll to bottom of commit list"
 msgstr "<%s-End>\tScorri alla fine della lista delle revisioni"
 
-#: gitk:2530
+#: gitk:2827
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
-msgstr "<%s-Up>\tScorri la lista delle revisioni in alto di una riga"
+msgstr "<%s-Su>\tScorri la lista delle revisioni in alto di una riga"
 
-#: gitk:2531
+#: gitk:2828
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
-msgstr "<%s-Down>\tScorri la lista delle revisioni in basso di una riga"
+msgstr "<%s-Giù>\tScorri la lista delle revisioni in basso di una riga"
 
-#: gitk:2532
+#: gitk:2829
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
-msgstr "<%s-PageUp>\tScorri la lista delle revisioni in alto di una pagina"
+msgstr "<%s-PaginaSu>\tScorri la lista delle revisioni in alto di una pagina"
 
-#: gitk:2533
+#: gitk:2830
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
-msgstr "<%s-PageDown>\tScorri la lista delle revisioni in basso di una pagina"
+msgstr "<%s-PaginaGiù>\tScorri la lista delle revisioni in basso di una pagina"
 
-#: gitk:2534
+#: gitk:2831
 msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
-msgstr "<Shift-Up>\tTrova all'indietro (verso l'alto, revisioni successive)"
+msgstr "<Shift-Su>\tTrova all'indietro (verso l'alto, revisioni successive)"
 
-#: gitk:2535
+#: gitk:2832
 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
-msgstr "<Shift-Down>\tTrova in avanti (verso il basso, revisioni precedenti)"
+msgstr "<Shift-Giù>\tTrova in avanti (verso il basso, revisioni precedenti)"
 
-#: gitk:2536
+#: gitk:2833
 msgid "<Delete>, b\tScroll diff view up one page"
 msgstr "<Delete>, b\tScorri la vista delle differenze in alto di una pagina"
 
-#: gitk:2537
+#: gitk:2834
 msgid "<Backspace>\tScroll diff view up one page"
 msgstr "<Backspace>\tScorri la vista delle differenze in alto di una pagina"
 
-#: gitk:2538
+#: gitk:2835
 msgid "<Space>\t\tScroll diff view down one page"
-msgstr "<Space>\t\tScorri la vista delle differenze in basso di una pagina"
+msgstr "<Spazio>\t\tScorri la vista delle differenze in basso di una pagina"
 
-#: gitk:2539
+#: gitk:2836
 msgid "u\t\tScroll diff view up 18 lines"
 msgstr "u\t\tScorri la vista delle differenze in alto di 18 linee"
 
-#: gitk:2540
+#: gitk:2837
 msgid "d\t\tScroll diff view down 18 lines"
 msgstr "d\t\tScorri la vista delle differenze in basso di 18 linee"
 
-#: gitk:2541
+#: gitk:2838
 #, tcl-format
 msgid "<%s-F>\t\tFind"
 msgstr "<%s-F>\t\tTrova"
 
-#: gitk:2542
+#: gitk:2839
 #, tcl-format
 msgid "<%s-G>\t\tMove to next find hit"
 msgstr "<%s-G>\t\tTrova in avanti"
 
-#: gitk:2543
+#: gitk:2840
 msgid "<Return>\tMove to next find hit"
-msgstr "<Return>\tTrova in avanti"
+msgstr "<Invio>\tTrova in avanti"
 
-#: gitk:2544
-msgid "/\t\tMove to next find hit, or redo find"
-msgstr "/\t\tTrova in avanti, o cerca di nuovo"
+#: gitk:2841
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tCursore nel box di ricerca"
 
-#: gitk:2545
+#: gitk:2842
 msgid "?\t\tMove to previous find hit"
 msgstr "?\t\tTrova all'indietro"
 
-#: gitk:2546
+#: gitk:2843
 msgid "f\t\tScroll diff view to next file"
 msgstr "f\t\tScorri la vista delle differenze al file successivo"
 
-#: gitk:2547
+#: gitk:2844
 #, tcl-format
 msgid "<%s-S>\t\tSearch for next hit in diff view"
 msgstr "<%s-S>\t\tCerca in avanti nella vista delle differenze"
 
-#: gitk:2548
+#: gitk:2845
 #, tcl-format
 msgid "<%s-R>\t\tSearch for previous hit in diff view"
 msgstr "<%s-R>\t\tCerca all'indietro nella vista delle differenze"
 
-#: gitk:2549
+#: gitk:2846
 #, tcl-format
 msgid "<%s-KP+>\tIncrease font size"
-msgstr "<%s-KP+>\tAumenta grandezza carattere"
+msgstr "<%s-KP+>\tAumenta dimensione carattere"
 
-#: gitk:2550
+#: gitk:2847
 #, tcl-format
 msgid "<%s-plus>\tIncrease font size"
-msgstr "<%s-plus>\tAumenta grandezza carattere"
+msgstr "<%s-più>\tAumenta dimensione carattere"
 
-#: gitk:2551
+#: gitk:2848
 #, tcl-format
 msgid "<%s-KP->\tDecrease font size"
-msgstr "<%s-KP->\tDiminuisci grandezza carattere"
+msgstr "<%s-KP->\tDiminuisci dimensione carattere"
 
-#: gitk:2552
+#: gitk:2849
 #, tcl-format
 msgid "<%s-minus>\tDecrease font size"
-msgstr "<%s-minus>\tDiminuisci grandezza carattere"
+msgstr "<%s-meno>\tDiminuisci dimensione carattere"
 
-#: gitk:2553
+#: gitk:2850
 msgid "<F5>\t\tUpdate"
 msgstr "<F5>\t\tAggiorna"
 
-#: gitk:3200
+#: gitk:3305 gitk:3314
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Errore durante la creazione della directory temporanea %s:"
+
+#: gitk:3327
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Errore nella lettura di \"%s\" da %s:"
+
+#: gitk:3390
+msgid "command failed:"
+msgstr "impossibile eseguire il comando:"
+
+#: gitk:3539
+msgid "No such commit"
+msgstr "Revisione inesistente"
+
+#: gitk:3553
+msgid "git gui blame: command failed:"
+msgstr "git gui blame: impossibile eseguire il comando:"
+
+#: gitk:3584
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Impossibile leggere merge head: %s"
+
+#: gitk:3592
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Errore nella lettura dell'indice: %s"
+
+#: gitk:3617
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Impossibile eseguire git blame: %s"
+
+#: gitk:3620 gitk:6409
+msgid "Searching"
+msgstr "Ricerca in corso"
+
+#: gitk:3652
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Errore nell'esecuzione di git blame: %s"
+
+#: gitk:3680
+#, tcl-format
+msgid "That line comes from commit %s,  which is not in this view"
+msgstr "Quella riga proviene dalla revisione %s, non presente in questa vista"
+
+#: gitk:3694
+msgid "External diff viewer failed:"
+msgstr "Impossibile eseguire il visualizzatore di differenze:"
+
+#: gitk:3812
 msgid "Gitk view definition"
 msgstr "Scelta vista Gitk"
 
-#: gitk:3225
-msgid "Name"
-msgstr "Nome"
-
-#: gitk:3228
+#: gitk:3816
 msgid "Remember this view"
 msgstr "Ricorda questa vista"
 
-#: gitk:3232
-msgid "Commits to include (arguments to git log):"
-msgstr "Revisioni da includere (argomenti di git log):"
+#: gitk:3817
+msgid "References (space separated list):"
+msgstr "Riferimenti (lista di elementi separati da spazi)"
 
-#: gitk:3239
-msgid "Command to generate more commits to include:"
-msgstr "Comando che genera altre revisioni da visualizzare:"
+#: gitk:3818
+msgid "Branches & tags:"
+msgstr "Rami ed etichette"
+
+#: gitk:3819
+msgid "All refs"
+msgstr "Tutti i riferimenti"
+
+#: gitk:3820
+msgid "All (local) branches"
+msgstr "Tutti i rami (locali)"
+
+#: gitk:3821
+msgid "All tags"
+msgstr "Tutte le etichette"
+
+#: gitk:3822
+msgid "All remote-tracking branches"
+msgstr "Tutti i rami remoti"
+
+#: gitk:3823
+msgid "Commit Info (regular expressions):"
+msgstr "Informazioni sulla revisione (espressioni regolari):"
+
+#: gitk:3824
+msgid "Author:"
+msgstr "Autore:"
+
+#: gitk:3825
+msgid "Committer:"
+msgstr "Revisione creata da:"
+
+#: gitk:3826
+msgid "Commit Message:"
+msgstr "Messaggio di revisione:"
+
+#: gitk:3827
+msgid "Matches all Commit Info criteria"
+msgstr "Risponde a tutti i criteri di ricerca sulle revisioni"
+
+#: gitk:3828
+msgid "Changes to Files:"
+msgstr "Modifiche ai file:"
+
+#: gitk:3829
+msgid "Fixed String"
+msgstr "Stringa fissa"
+
+#: gitk:3830
+msgid "Regular Expression"
+msgstr "Espressione regolare"
+
+#: gitk:3831
+msgid "Search string:"
+msgstr "Cerca stringa:"
+
+#: gitk:3832
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"Date di revisione (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, "
+"2009 15:27:38\"):"
+
+#: gitk:3833
+msgid "Since:"
+msgstr "Da:"
+
+#: gitk:3834
+msgid "Until:"
+msgstr "A:"
+
+#: gitk:3835
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "Limita e/o salta N revisioni (intero positivo):"
+
+#: gitk:3836
+msgid "Number to show:"
+msgstr "Numero di revisioni da mostrare:"
+
+#: gitk:3837
+msgid "Number to skip:"
+msgstr "Numero di revisioni da saltare:"
+
+#: gitk:3838
+msgid "Miscellaneous options:"
+msgstr "Altre opzioni:"
+
+#: gitk:3839
+msgid "Strictly sort by date"
+msgstr "Ordina solo per data"
+
+#: gitk:3840
+msgid "Mark branch sides"
+msgstr "Segna i lati del ramo"
+
+#: gitk:3841
+msgid "Limit to first parent"
+msgstr "Limita al primo genitore"
 
-#: gitk:3246
+#: gitk:3842
+msgid "Simple history"
+msgstr "Cronologia semplificata"
+
+#: gitk:3843
+msgid "Additional arguments to git log:"
+msgstr "Ulteriori argomenti da passare a git log:"
+
+#: gitk:3844
 msgid "Enter files and directories to include, one per line:"
 msgstr "Inserire file e directory da includere, uno per riga:"
 
-#: gitk:3293
+#: gitk:3845
+msgid "Command to generate more commits to include:"
+msgstr "Comando che genera altre revisioni da visualizzare:"
+
+#: gitk:3967
+msgid "Gitk: edit view"
+msgstr "Gitk: modifica vista"
+
+#: gitk:3975
+msgid "-- criteria for selecting revisions"
+msgstr "-- criteri per la scelta delle revisioni"
+
+#: gitk:3980
+msgid "View Name"
+msgstr "Nome vista"
+
+#: gitk:4055
+msgid "Apply (F5)"
+msgstr "Applica (F5)"
+
+#: gitk:4093
 msgid "Error in commit selection arguments:"
 msgstr "Errore negli argomenti di selezione delle revisioni:"
 
-#: gitk:3347 gitk:3399 gitk:3842 gitk:3856 gitk:5060 gitk:10141 gitk:10142
+#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535
 msgid "None"
 msgstr "Nessuno"
 
-#: gitk:3790 gitk:5580 gitk:7287 gitk:7302
+#: gitk:4594 gitk:6441 gitk:8287 gitk:8302
 msgid "Date"
 msgstr "Data"
 
-#: gitk:3790 gitk:5580
+#: gitk:4594 gitk:6441
 msgid "CDate"
 msgstr ""
 
-#: gitk:3939 gitk:3944
+#: gitk:4743 gitk:4748
 msgid "Descendant"
 msgstr "Discendente"
 
-#: gitk:3940
+#: gitk:4744
 msgid "Not descendant"
 msgstr "Non discendente"
 
-#: gitk:3947 gitk:3952
+#: gitk:4751 gitk:4756
 msgid "Ancestor"
 msgstr "Ascendente"
 
-#: gitk:3948
+#: gitk:4752
 msgid "Not ancestor"
 msgstr "Non ascendente"
 
-#: gitk:4187
+#: gitk:5042
 msgid "Local changes checked in to index but not committed"
 msgstr "Modifiche locali presenti nell'indice ma non nell'archivio"
 
-#: gitk:4220
+#: gitk:5078
 msgid "Local uncommitted changes, not checked in to index"
 msgstr "Modifiche locali non presenti né nell'archivio né nell'indice"
 
-#: gitk:5549
-msgid "Searching"
-msgstr "Ricerca in corso"
+#: gitk:6759
+msgid "many"
+msgstr "molti"
 
-#: gitk:6049
+#: gitk:6942
 msgid "Tags:"
 msgstr "Etichette:"
 
-#: gitk:6066 gitk:6072 gitk:7280
+#: gitk:6959 gitk:6965 gitk:8280
 msgid "Parent"
 msgstr "Genitore"
 
-#: gitk:6077
+#: gitk:6970
 msgid "Child"
 msgstr "Figlio"
 
-#: gitk:6086
+#: gitk:6979
 msgid "Branch"
 msgstr "Ramo"
 
-#: gitk:6089
+#: gitk:6982
 msgid "Follows"
 msgstr "Segue"
 
-#: gitk:6092
+#: gitk:6985
 msgid "Precedes"
 msgstr "Precede"
 
-#: gitk:6378
-msgid "Error getting merge diffs:"
-msgstr "Errore nella lettura delle differenze di fusione:"
+#: gitk:7522
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Errore nella lettura delle differenze:"
 
-#: gitk:7113
+#: gitk:8108
 msgid "Goto:"
 msgstr "Vai a:"
 
-#: gitk:7115
-msgid "SHA1 ID:"
-msgstr "SHA1 ID:"
-
-#: gitk:7134
+#: gitk:8129
 #, tcl-format
 msgid "Short SHA1 id %s is ambiguous"
 msgstr "La SHA1 id abbreviata %s è ambigua"
 
-#: gitk:7146
+#: gitk:8136
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "La revisione %s è sconosciuta"
+
+#: gitk:8146
 #, tcl-format
 msgid "SHA1 id %s is not known"
 msgstr "La SHA1 id %s è sconosciuta"
 
-#: gitk:7148
+#: gitk:8148
 #, tcl-format
-msgid "Tag/Head %s is not known"
-msgstr "L'etichetta/ramo %s è sconosciuto"
+msgid "Revision %s is not in the current view"
+msgstr "La revisione %s non è presente nella vista attuale"
 
-#: gitk:7290
+#: gitk:8290
 msgid "Children"
 msgstr "Figli"
 
-#: gitk:7347
+#: gitk:8348
 #, tcl-format
 msgid "Reset %s branch to here"
 msgstr "Aggiorna il ramo %s a questa revisione"
 
-#: gitk:7349
+#: gitk:8350
 msgid "Detached head: can't reset"
+msgstr "Nessun ramo attivo: reset impossibile"
+
+#: gitk:8459 gitk:8465
+msgid "Skipping merge commit "
+msgstr "Salto la revisione di fusione "
+
+#: gitk:8474 gitk:8479
+msgid "Error getting patch ID for "
+msgstr "Errore nella identificazione della patch per "
+
+#: gitk:8475 gitk:8480
+msgid " - stopping\n"
+msgstr " - fine\n"
+
+#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519
+msgid "Commit "
+msgstr "La revisione "
+
+#: gitk:8489
+msgid ""
+" is the same patch as\n"
+"       "
 msgstr ""
+" ha le stesse differenze di\n"
+"       "
+
+#: gitk:8497
+msgid ""
+" differs from\n"
+"       "
+msgstr ""
+" è diversa da\n"
+"       "
+
+#: gitk:8499
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr ""
+"Differenze tra le revisioni:\n"
+"\n"
+
+#: gitk:8511 gitk:8520
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr " ha %s figli - fine\n"
+
+#: gitk:8539
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "Errore nella scrittura della revisione nel file: %s"
 
-#: gitk:7381
+#: gitk:8545
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "Errore nelle differenze tra le revisioni: %s"
+
+#: gitk:8575
 msgid "Top"
 msgstr "Inizio"
 
-#: gitk:7382
+#: gitk:8576
 msgid "From"
 msgstr "Da"
 
-#: gitk:7387
+#: gitk:8581
 msgid "To"
 msgstr "A"
 
-#: gitk:7410
+#: gitk:8605
 msgid "Generate patch"
 msgstr "Genera patch"
 
-#: gitk:7412
+#: gitk:8607
 msgid "From:"
 msgstr "Da:"
 
-#: gitk:7421
+#: gitk:8616
 msgid "To:"
 msgstr "A:"
 
-#: gitk:7430
+#: gitk:8625
 msgid "Reverse"
 msgstr "Inverti"
 
-#: gitk:7432 gitk:7607
+#: gitk:8627 gitk:8822
 msgid "Output file:"
 msgstr "Scrivi sul file:"
 
-#: gitk:7438
+#: gitk:8633
 msgid "Generate"
 msgstr "Genera"
 
-#: gitk:7474
+#: gitk:8671
 msgid "Error creating patch:"
 msgstr "Errore nella creazione della patch:"
 
-#: gitk:7496 gitk:7595 gitk:7649
+#: gitk:8694 gitk:8810 gitk:8867
 msgid "ID:"
 msgstr "ID:"
 
-#: gitk:7505
+#: gitk:8703
 msgid "Tag name:"
 msgstr "Nome etichetta:"
 
-#: gitk:7509 gitk:7659
+#: gitk:8706
+msgid "Tag message is optional"
+msgstr "Il messaggio dell'etichetta è opzionale"
+
+#: gitk:8708
+msgid "Tag message:"
+msgstr "Messaggio dell'etichetta:"
+
+#: gitk:8712 gitk:8876
 msgid "Create"
 msgstr "Crea"
 
-#: gitk:7524
+#: gitk:8730
 msgid "No tag name specified"
 msgstr "Nessuna etichetta specificata"
 
-#: gitk:7528
+#: gitk:8734
 #, tcl-format
 msgid "Tag \"%s\" already exists"
 msgstr "L'etichetta \"%s\" esiste già"
 
-#: gitk:7534
+#: gitk:8744
 msgid "Error creating tag:"
 msgstr "Errore nella creazione dell'etichetta:"
 
-#: gitk:7604
+#: gitk:8819
 msgid "Command:"
 msgstr "Comando:"
 
-#: gitk:7612
+#: gitk:8827
 msgid "Write"
 msgstr "Scrivi"
 
-#: gitk:7628
+#: gitk:8845
 msgid "Error writing commit:"
 msgstr "Errore nella scrittura della revisione:"
 
-#: gitk:7654
+#: gitk:8872
 msgid "Name:"
 msgstr "Nome:"
 
-#: gitk:7674
+#: gitk:8895
 msgid "Please specify a name for the new branch"
 msgstr "Specificare un nome per il nuovo ramo"
 
-#: gitk:7703
+#: gitk:8900
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "Il ramo '%s' esiste già. Sovrascrivere?"
+
+#: gitk:8966
 #, tcl-format
 msgid "Commit %s is already included in branch %s -- really re-apply it?"
 msgstr "La revisione %s è già inclusa nel ramo %s -- applicarla di nuovo?"
 
-#: gitk:7708
+#: gitk:8971
 msgid "Cherry-picking"
 msgstr ""
 
-#: gitk:7720
+#: gitk:8980
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"Impossibile eseguire cherry-pick perché il file '%s' è stato modificato "
+"nella directory di lavoro.\n"
+"Prima di riprovare, bisogna creare una nuova revisione, annullare le "
+"modifiche o usare 'git stash'."
+
+#: gitk:8986
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"Impossibile eseguire cherry-pick a causa di un conflitto nella fusione.\n"
+"Vuoi avviare git citool per risolverlo?"
+
+#: gitk:9002
 msgid "No changes committed"
 msgstr "Nessuna modifica archiviata"
 
-#: gitk:7745
+#: gitk:9028
 msgid "Confirm reset"
 msgstr "Conferma git reset"
 
-#: gitk:7747
+#: gitk:9030
 #, tcl-format
 msgid "Reset branch %s to %s?"
 msgstr "Aggiornare il ramo %s a %s?"
 
-#: gitk:7751
+#: gitk:9032
 msgid "Reset type:"
 msgstr "Tipo di aggiornamento:"
 
-#: gitk:7755
+#: gitk:9035
 msgid "Soft: Leave working tree and index untouched"
 msgstr "Soft: Lascia la direcory di lavoro e l'indice come sono"
 
-#: gitk:7758
+#: gitk:9038
 msgid "Mixed: Leave working tree untouched, reset index"
 msgstr "Mixed: Lascia la directory di lavoro come è, aggiorna l'indice"
 
-#: gitk:7761
+#: gitk:9041
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
@@ -723,19 +1037,19 @@ msgstr ""
 "Hard: Aggiorna la directory di lavoro e l'indice\n"
 "(abbandona TUTTE le modifiche locali)"
 
-#: gitk:7777
+#: gitk:9058
 msgid "Resetting"
 msgstr "git reset in corso"
 
-#: gitk:7834
+#: gitk:9118
 msgid "Checking out"
 msgstr "Attivazione in corso"
 
-#: gitk:7885
+#: gitk:9171
 msgid "Cannot delete the currently checked-out branch"
 msgstr "Impossibile cancellare il ramo attualmente attivo"
 
-#: gitk:7891
+#: gitk:9177
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
@@ -744,16 +1058,16 @@ msgstr ""
 "Le revisioni nel ramo %s non sono presenti su altri rami.\n"
 "Cancellare il ramo %s?"
 
-#: gitk:7922
+#: gitk:9208
 #, tcl-format
 msgid "Tags and heads: %s"
 msgstr "Etichette e rami: %s"
 
-#: gitk:7936
+#: gitk:9223
 msgid "Filter"
 msgstr "Filtro"
 
-#: gitk:8230
+#: gitk:9518
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
@@ -761,154 +1075,202 @@ msgstr ""
 "Errore nella lettura della topologia delle revisioni: le informazioni sul "
 "ramo e le etichette precedenti e seguenti saranno incomplete."
 
-#: gitk:9216
+#: gitk:10504
 msgid "Tag"
 msgstr "Etichetta"
 
-#: gitk:9216
+#: gitk:10504
 msgid "Id"
 msgstr "Id"
 
-#: gitk:9262
+#: gitk:10554
 msgid "Gitk font chooser"
 msgstr "Scelta caratteri gitk"
 
-#: gitk:9279
+#: gitk:10571
 msgid "B"
 msgstr "B"
 
-#: gitk:9282
+#: gitk:10574
 msgid "I"
 msgstr "I"
 
-#: gitk:9375
+#: gitk:10692
 msgid "Gitk preferences"
 msgstr "Preferenze gitk"
 
-#: gitk:9376
+#: gitk:10694
 msgid "Commit list display options"
 msgstr "Opzioni visualizzazione dell'elenco revisioni"
 
-#: gitk:9379
+#: gitk:10697
 msgid "Maximum graph width (lines)"
 msgstr "Larghezza massima del grafico (in linee)"
 
-#: gitk:9383
+#: gitk:10700
 #, tcl-format
 msgid "Maximum graph width (% of pane)"
 msgstr "Larghezza massima del grafico (% del pannello)"
 
-#: gitk:9388
+#: gitk:10703
 msgid "Show local changes"
 msgstr "Mostra modifiche locali"
 
-#: gitk:9393
+#: gitk:10706
 msgid "Auto-select SHA1"
 msgstr "Seleziona automaticamente SHA1 hash"
 
-#: gitk:9398
+#: gitk:10709
+msgid "Hide remote refs"
+msgstr "Nascondi i riferimenti remoti"
+
+#: gitk:10713
 msgid "Diff display options"
 msgstr "Opzioni di visualizzazione delle differenze"
 
-#: gitk:9400
+#: gitk:10715
 msgid "Tab spacing"
 msgstr "Spaziatura tabulazioni"
 
-#: gitk:9404
+#: gitk:10718
 msgid "Display nearby tags"
 msgstr "Mostra etichette vicine"
 
-#: gitk:9409
+#: gitk:10721
 msgid "Limit diffs to listed paths"
 msgstr "Limita le differenze ai percorsi elencati"
 
-#: gitk:9414
+#: gitk:10724
 msgid "Support per-file encodings"
-msgstr ""
+msgstr "Attiva codifica file per file"
 
-#: gitk:9421
+#: gitk:10730 gitk:10819
 msgid "External diff tool"
-msgstr ""
+msgstr "Visualizzatore di differenze"
 
-#: gitk:9423
+#: gitk:10731
 msgid "Choose..."
-msgstr ""
+msgstr "Scegli..."
 
-#: gitk:9428
+#: gitk:10736
+msgid "General options"
+msgstr "Opzioni generali"
+
+#: gitk:10739
+msgid "Use themed widgets"
+msgstr "Utilizza interfaccia a tema"
+
+#: gitk:10741
+msgid "(change requires restart)"
+msgstr "(una modifica richiede il riavvio)"
+
+#: gitk:10743
+msgid "(currently unavailable)"
+msgstr "(momentaneamente non disponibile)"
+
+#: gitk:10747
 msgid "Colors: press to choose"
 msgstr "Colori: premere per scegliere"
 
-#: gitk:9431
+#: gitk:10750
+msgid "Interface"
+msgstr "Interfaccia"
+
+#: gitk:10751
+msgid "interface"
+msgstr "interfaccia"
+
+#: gitk:10754
 msgid "Background"
 msgstr "Sfondo"
 
-#: gitk:9435
+#: gitk:10755 gitk:10785
+msgid "background"
+msgstr "sfondo"
+
+#: gitk:10758
 msgid "Foreground"
 msgstr "Primo piano"
 
-#: gitk:9439
+#: gitk:10759
+msgid "foreground"
+msgstr "primo piano"
+
+#: gitk:10762
 msgid "Diff: old lines"
 msgstr "Diff: vecchie linee"
 
-#: gitk:9444
+#: gitk:10763
+msgid "diff old lines"
+msgstr "vecchie linee"
+
+#: gitk:10767
 msgid "Diff: new lines"
 msgstr "Diff: nuove linee"
 
-#: gitk:9449
+#: gitk:10768
+msgid "diff new lines"
+msgstr "nuove linee"
+
+#: gitk:10772
 msgid "Diff: hunk header"
 msgstr "Diff: intestazione della sezione"
 
-#: gitk:9455
+#: gitk:10774
+msgid "diff hunk header"
+msgstr "intestazione della sezione"
+
+#: gitk:10778
+msgid "Marked line bg"
+msgstr "Sfondo riga selezionata"
+
+#: gitk:10780
+msgid "marked line background"
+msgstr "sfondo riga selezionata"
+
+#: gitk:10784
 msgid "Select bg"
-msgstr "Sfondo selezione"
+msgstr "Sfondo"
 
-#: gitk:9459
+#: gitk:10788
 msgid "Fonts: press to choose"
 msgstr "Carattere: premere per scegliere"
 
-#: gitk:9461
+#: gitk:10790
 msgid "Main font"
 msgstr "Carattere principale"
 
-#: gitk:9462
+#: gitk:10791
 msgid "Diff display font"
 msgstr "Carattere per differenze"
 
-#: gitk:9463
+#: gitk:10792
 msgid "User interface font"
 msgstr "Carattere per interfaccia utente"
 
-#: gitk:9488
+#: gitk:10829
 #, tcl-format
 msgid "Gitk: choose color for %s"
 msgstr "Gitk: scegliere un colore per %s"
 
-#: gitk:9934
-msgid ""
-"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
-" Gitk requires at least Tcl/Tk 8.4."
-msgstr ""
-"Questa versione di Tcl/Tk non può avviare gitk.\n"
-" Gitk richiede Tcl/Tk versione 8.4 o superiore."
-
-#: gitk:10047
+#: gitk:11433
 msgid "Cannot find a git repository here."
 msgstr "Archivio git non trovato."
 
-#: gitk:10051
+#: gitk:11437
 #, tcl-format
 msgid "Cannot find the git directory \"%s\"."
 msgstr "Directory git \"%s\" non trovata."
 
-#: gitk:10098
+#: gitk:11484
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
 msgstr "Argomento ambiguo: '%s' è sia revisione che nome di file"
 
-#: gitk:10110
+#: gitk:11496
 msgid "Bad arguments to gitk:"
 msgstr "Gitk: argomenti errati:"
 
-#: gitk:10170
+#: gitk:11587
 msgid "Command line"
 msgstr "Linea di comando"
index 624eb2281e0bbd7c6dc55e0345bfd9acf6e46eb4..0f5e2fd8d79b585e1487b7ba9e2d1a36a49c7c7d 100644 (file)
@@ -2,39 +2,39 @@
 # Copyright (C) 2005-2009 Paul Mackerras
 # This file is distributed under the same license as the gitk package.
 #
-# Peter Krefting <peter@softwolves.pp.se>, 2008-2009.
+# Peter Krefting <peter@softwolves.pp.se>, 2008-2010.
 # Mikael Magnusson <mikachu@gmail.com>, 2008.
 msgid ""
 msgstr ""
 "Project-Id-Version: sv\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-08-13 13:38+0100\n"
-"PO-Revision-Date: 2009-08-13 13:40+0100\n"
+"POT-Creation-Date: 2010-01-28 13:16+0100\n"
+"PO-Revision-Date: 2010-01-28 13:48+0100\n"
 "Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
 "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit"
 
-#: gitk:113
+#: gitk:115
 msgid "Couldn't get list of unmerged files:"
 msgstr "Kunde inte hämta lista över ej sammanslagna filer:"
 
-#: gitk:269
+#: gitk:274
 msgid "Error parsing revisions:"
 msgstr "Fel vid tolkning av revisioner:"
 
-#: gitk:324
+#: gitk:329
 msgid "Error executing --argscmd command:"
 msgstr "Fel vid körning av --argscmd-kommando:"
 
-#: gitk:337
+#: gitk:342
 msgid "No files selected: --merge specified but no files are unmerged."
 msgstr ""
 "Inga filer valdes: --merge angavs men det finns inga filer som inte har "
 "slagits samman."
 
-#: gitk:340
+#: gitk:345
 msgid ""
 "No files selected: --merge specified but no unmerged files are within file "
 "limit."
@@ -42,596 +42,600 @@ msgstr ""
 "Inga filer valdes: --merge angavs men det finns inga filer inom "
 "filbegränsningen."
 
-#: gitk:362 gitk:509
+#: gitk:367 gitk:514
 msgid "Error executing git log:"
 msgstr "Fel vid körning av git log:"
 
-#: gitk:380 gitk:525
+#: gitk:385 gitk:530
 msgid "Reading"
 msgstr "Läser"
 
-#: gitk:440 gitk:4123
+#: gitk:445 gitk:4261
 msgid "Reading commits..."
 msgstr "Läser incheckningar..."
 
-#: gitk:443 gitk:1561 gitk:4126
+#: gitk:448 gitk:1578 gitk:4264
 msgid "No commits selected"
 msgstr "Inga incheckningar markerade"
 
-#: gitk:1437
+#: gitk:1454
 msgid "Can't parse git log output:"
 msgstr "Kan inte tolka utdata från git log:"
 
-#: gitk:1657
+#: gitk:1674
 msgid "No commit information available"
 msgstr "Ingen incheckningsinformation är tillgänglig"
 
-#: gitk:1793 gitk:1817 gitk:3916 gitk:8786 gitk:10322 gitk:10498
+#: gitk:1816
+msgid "mc"
+msgstr "mc"
+
+#: gitk:1851 gitk:4054 gitk:9044 gitk:10585 gitk:10804
 msgid "OK"
 msgstr "OK"
 
-#: gitk:1819 gitk:3918 gitk:8383 gitk:8457 gitk:8567 gitk:8616 gitk:8788
-#: gitk:10323 gitk:10499
+#: gitk:1853 gitk:4056 gitk:8634 gitk:8713 gitk:8828 gitk:8877 gitk:9046
+#: gitk:10586 gitk:10805
 msgid "Cancel"
 msgstr "Avbryt"
 
-#: gitk:1919
+#: gitk:1975
 msgid "Update"
 msgstr "Uppdatera"
 
-#: gitk:1920
+#: gitk:1976
 msgid "Reload"
 msgstr "Ladda om"
 
-#: gitk:1921
+#: gitk:1977
 msgid "Reread references"
 msgstr "Läs om referenser"
 
-#: gitk:1922
+#: gitk:1978
 msgid "List references"
 msgstr "Visa referenser"
 
-#: gitk:1924
+#: gitk:1980
 msgid "Start git gui"
 msgstr "Starta git gui"
 
-#: gitk:1926
+#: gitk:1982
 msgid "Quit"
 msgstr "Avsluta"
 
-#: gitk:1918
+#: gitk:1974
 msgid "File"
 msgstr "Arkiv"
 
-#: gitk:1930
+#: gitk:1986
 msgid "Preferences"
 msgstr "Inställningar"
 
-#: gitk:1929
+#: gitk:1985
 msgid "Edit"
 msgstr "Redigera"
 
-#: gitk:1934
+#: gitk:1990
 msgid "New view..."
 msgstr "Ny vy..."
 
-#: gitk:1935
+#: gitk:1991
 msgid "Edit view..."
 msgstr "Ändra vy..."
 
-#: gitk:1936
+#: gitk:1992
 msgid "Delete view"
 msgstr "Ta bort vy"
 
-#: gitk:1938
+#: gitk:1994
 msgid "All files"
 msgstr "Alla filer"
 
-#: gitk:1933 gitk:3670
+#: gitk:1989 gitk:3808
 msgid "View"
 msgstr "Visa"
 
-#: gitk:1943 gitk:1953 gitk:2654
+#: gitk:1999 gitk:2009 gitk:2780
 msgid "About gitk"
 msgstr "Om gitk"
 
-#: gitk:1944 gitk:1958
+#: gitk:2000 gitk:2014
 msgid "Key bindings"
 msgstr "Tangentbordsbindningar"
 
-#: gitk:1942 gitk:1957
+#: gitk:1998 gitk:2013
 msgid "Help"
 msgstr "Hjälp"
 
-#: gitk:2018
-msgid "SHA1 ID: "
-msgstr "SHA1-id: "
+#: gitk:2091 gitk:8110
+msgid "SHA1 ID:"
+msgstr "SHA1-id:"
 
-#: gitk:2049
+#: gitk:2122
 msgid "Row"
 msgstr "Rad"
 
-#: gitk:2080
+#: gitk:2160
 msgid "Find"
 msgstr "Sök"
 
-#: gitk:2081
+#: gitk:2161
 msgid "next"
 msgstr "nästa"
 
-#: gitk:2082
+#: gitk:2162
 msgid "prev"
 msgstr "föreg"
 
-#: gitk:2083
+#: gitk:2163
 msgid "commit"
 msgstr "incheckning"
 
-#: gitk:2086 gitk:2088 gitk:4284 gitk:4307 gitk:4331 gitk:6272 gitk:6344
-#: gitk:6428
+#: gitk:2166 gitk:2168 gitk:4422 gitk:4445 gitk:4469 gitk:6410 gitk:6482
+#: gitk:6566
 msgid "containing:"
 msgstr "som innehåller:"
 
-#: gitk:2089 gitk:3162 gitk:3167 gitk:4359
+#: gitk:2169 gitk:3290 gitk:3295 gitk:4497
 msgid "touching paths:"
 msgstr "som rör sökväg:"
 
-#: gitk:2090 gitk:4364
+#: gitk:2170 gitk:4502
 msgid "adding/removing string:"
 msgstr "som lägger/till tar bort sträng:"
 
-#: gitk:2099 gitk:2101
+#: gitk:2179 gitk:2181
 msgid "Exact"
 msgstr "Exakt"
 
-#: gitk:2101 gitk:4439 gitk:6240
+#: gitk:2181 gitk:4577 gitk:6378
 msgid "IgnCase"
 msgstr "IgnVersaler"
 
-#: gitk:2101 gitk:4333 gitk:4437 gitk:6236
+#: gitk:2181 gitk:4471 gitk:4575 gitk:6374
 msgid "Regexp"
 msgstr "Reg.uttr."
 
-#: gitk:2103 gitk:2104 gitk:4458 gitk:4488 gitk:4495 gitk:6364 gitk:6432
+#: gitk:2183 gitk:2184 gitk:4596 gitk:4626 gitk:4633 gitk:6502 gitk:6570
 msgid "All fields"
 msgstr "Alla fält"
 
-#: gitk:2104 gitk:4456 gitk:4488 gitk:6303
+#: gitk:2184 gitk:4594 gitk:4626 gitk:6441
 msgid "Headline"
 msgstr "Rubrik"
 
-#: gitk:2105 gitk:4456 gitk:6303 gitk:6432 gitk:6866
+#: gitk:2185 gitk:4594 gitk:6441 gitk:6570 gitk:7003
 msgid "Comments"
 msgstr "Kommentarer"
 
-#: gitk:2105 gitk:4456 gitk:4460 gitk:4495 gitk:6303 gitk:6801 gitk:8063
-#: gitk:8078
+#: gitk:2185 gitk:4594 gitk:4598 gitk:4633 gitk:6441 gitk:6938 gitk:8285
+#: gitk:8300
 msgid "Author"
 msgstr "Författare"
 
-#: gitk:2105 gitk:4456 gitk:6303 gitk:6803
+#: gitk:2185 gitk:4594 gitk:6441 gitk:6940
 msgid "Committer"
 msgstr "Incheckare"
 
-#: gitk:2134
+#: gitk:2216
 msgid "Search"
 msgstr "Sök"
 
-#: gitk:2141
+#: gitk:2224
 msgid "Diff"
 msgstr "Diff"
 
-#: gitk:2143
+#: gitk:2226
 msgid "Old version"
 msgstr "Gammal version"
 
-#: gitk:2145
+#: gitk:2228
 msgid "New version"
 msgstr "Ny version"
 
-#: gitk:2147
+#: gitk:2230
 msgid "Lines of context"
 msgstr "Rader sammanhang"
 
-#: gitk:2157
+#: gitk:2240
 msgid "Ignore space change"
 msgstr "Ignorera ändringar i blanksteg"
 
-#: gitk:2215
+#: gitk:2299
 msgid "Patch"
 msgstr "Patch"
 
-#: gitk:2217
+#: gitk:2301
 msgid "Tree"
 msgstr "Träd"
 
-#: gitk:2361 gitk:2378
+#: gitk:2456 gitk:2473
 msgid "Diff this -> selected"
 msgstr "Diff denna -> markerad"
 
-#: gitk:2362 gitk:2379
+#: gitk:2457 gitk:2474
 msgid "Diff selected -> this"
 msgstr "Diff markerad -> denna"
 
-#: gitk:2363 gitk:2380
+#: gitk:2458 gitk:2475
 msgid "Make patch"
 msgstr "Skapa patch"
 
-#: gitk:2364 gitk:8441
+#: gitk:2459 gitk:8692
 msgid "Create tag"
 msgstr "Skapa tagg"
 
-#: gitk:2365 gitk:8547
+#: gitk:2460 gitk:8808
 msgid "Write commit to file"
 msgstr "Skriv incheckning till fil"
 
-#: gitk:2366 gitk:8604
+#: gitk:2461 gitk:8865
 msgid "Create new branch"
 msgstr "Skapa ny gren"
 
-#: gitk:2367
+#: gitk:2462
 msgid "Cherry-pick this commit"
 msgstr "Plocka denna incheckning"
 
-#: gitk:2368
+#: gitk:2463
 msgid "Reset HEAD branch to here"
 msgstr "Återställ HEAD-grenen hit"
 
-#: gitk:2369
+#: gitk:2464
 msgid "Mark this commit"
 msgstr "Markera denna incheckning"
 
-#: gitk:2370
+#: gitk:2465
 msgid "Return to mark"
 msgstr "Återgå till markering"
 
-#: gitk:2371
+#: gitk:2466
 msgid "Find descendant of this and mark"
 msgstr "Hitta efterföljare till denna och markera"
 
-#: gitk:2372
+#: gitk:2467
 msgid "Compare with marked commit"
 msgstr "Jämför med markerad incheckning"
 
-#: gitk:2386
+#: gitk:2481
 msgid "Check out this branch"
 msgstr "Checka ut denna gren"
 
-#: gitk:2387
+#: gitk:2482
 msgid "Remove this branch"
 msgstr "Ta bort denna gren"
 
-#: gitk:2394
+#: gitk:2489
 msgid "Highlight this too"
 msgstr "Markera även detta"
 
-#: gitk:2395
+#: gitk:2490
 msgid "Highlight this only"
 msgstr "Markera bara detta"
 
-#: gitk:2396
+#: gitk:2491
 msgid "External diff"
 msgstr "Extern diff"
 
-#: gitk:2397
+#: gitk:2492
 msgid "Blame parent commit"
 msgstr "Klandra föräldraincheckning"
 
-#: gitk:2404
+#: gitk:2499
 msgid "Show origin of this line"
 msgstr "Visa ursprunget för den här raden"
 
-#: gitk:2405
+#: gitk:2500
 msgid "Run git gui blame on this line"
 msgstr "Kör git gui blame på den här raden"
 
-#: gitk:2656
+#: gitk:2782
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright ©9 2005-2009 Paul Mackerras\n"
 "\n"
 "Use and redistribute under the terms of the GNU General Public License"
 msgstr ""
 "\n"
 "Gitk - en incheckningsvisare för git\n"
 "\n"
-"Copyright © 2005-2008 Paul Mackerras\n"
+"Copyright © 2005-2009 Paul Mackerras\n"
 "\n"
 "Använd och vidareförmedla enligt villkoren i GNU General Public License"
 
-#: gitk:2664 gitk:2726 gitk:8969
+#: gitk:2790 gitk:2854 gitk:9230
 msgid "Close"
 msgstr "Stäng"
 
-#: gitk:2683
+#: gitk:2811
 msgid "Gitk key bindings"
 msgstr "Tangentbordsbindningar för Gitk"
 
-#: gitk:2686
+#: gitk:2814
 msgid "Gitk key bindings:"
 msgstr "Tangentbordsbindningar för Gitk:"
 
-#: gitk:2688
+#: gitk:2816
 #, tcl-format
 msgid "<%s-Q>\t\tQuit"
 msgstr "<%s-Q>\t\tAvsluta"
 
-#: gitk:2689
+#: gitk:2817
 msgid "<Home>\t\tMove to first commit"
 msgstr "<Home>\t\tGå till första incheckning"
 
-#: gitk:2690
+#: gitk:2818
 msgid "<End>\t\tMove to last commit"
 msgstr "<End>\t\tGå till sista incheckning"
 
-#: gitk:2691
+#: gitk:2819
 msgid "<Up>, p, i\tMove up one commit"
 msgstr "<Upp>, p, i\tGå en incheckning upp"
 
-#: gitk:2692
+#: gitk:2820
 msgid "<Down>, n, k\tMove down one commit"
 msgstr "<Ned>, n, k\tGå en incheckning ned"
 
-#: gitk:2693
+#: gitk:2821
 msgid "<Left>, z, j\tGo back in history list"
 msgstr "<Vänster>, z, j\tGå bakåt i historiken"
 
-#: gitk:2694
+#: gitk:2822
 msgid "<Right>, x, l\tGo forward in history list"
 msgstr "<Höger>, x, l\tGå framåt i historiken"
 
-#: gitk:2695
+#: gitk:2823
 msgid "<PageUp>\tMove up one page in commit list"
 msgstr "<PageUp>\tGå upp en sida i incheckningslistan"
 
-#: gitk:2696
+#: gitk:2824
 msgid "<PageDown>\tMove down one page in commit list"
 msgstr "<PageDown>\tGå ned en sida i incheckningslistan"
 
-#: gitk:2697
+#: gitk:2825
 #, tcl-format
 msgid "<%s-Home>\tScroll to top of commit list"
 msgstr "<%s-Home>\tRulla till början av incheckningslistan"
 
-#: gitk:2698
+#: gitk:2826
 #, tcl-format
 msgid "<%s-End>\tScroll to bottom of commit list"
 msgstr "<%s-End>\tRulla till slutet av incheckningslistan"
 
-#: gitk:2699
+#: gitk:2827
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
 msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg"
 
-#: gitk:2700
+#: gitk:2828
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
 msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg"
 
-#: gitk:2701
+#: gitk:2829
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
 msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida"
 
-#: gitk:2702
+#: gitk:2830
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
 msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida"
 
-#: gitk:2703
+#: gitk:2831
 msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
 msgstr "<Skift-Upp>\tSök bakåt (uppåt, senare incheckningar)"
 
-#: gitk:2704
+#: gitk:2832
 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
 msgstr "<Skift-Ned>\tSök framåt (nedåt, tidigare incheckningar)"
 
-#: gitk:2705
+#: gitk:2833
 msgid "<Delete>, b\tScroll diff view up one page"
 msgstr "<Delete>, b\tRulla diffvisningen upp en sida"
 
-#: gitk:2706
+#: gitk:2834
 msgid "<Backspace>\tScroll diff view up one page"
 msgstr "<Baksteg>\tRulla diffvisningen upp en sida"
 
-#: gitk:2707
+#: gitk:2835
 msgid "<Space>\t\tScroll diff view down one page"
 msgstr "<Blanksteg>\tRulla diffvisningen ned en sida"
 
-#: gitk:2708
+#: gitk:2836
 msgid "u\t\tScroll diff view up 18 lines"
 msgstr "u\t\tRulla diffvisningen upp 18 rader"
 
-#: gitk:2709
+#: gitk:2837
 msgid "d\t\tScroll diff view down 18 lines"
 msgstr "d\t\tRulla diffvisningen ned 18 rader"
 
-#: gitk:2710
+#: gitk:2838
 #, tcl-format
 msgid "<%s-F>\t\tFind"
 msgstr "<%s-F>\t\tSök"
 
-#: gitk:2711
+#: gitk:2839
 #, tcl-format
 msgid "<%s-G>\t\tMove to next find hit"
 msgstr "<%s-G>\t\tGå till nästa sökträff"
 
-#: gitk:2712
+#: gitk:2840
 msgid "<Return>\tMove to next find hit"
 msgstr "<Return>\t\tGå till nästa sökträff"
 
-#: gitk:2713
+#: gitk:2841
 msgid "/\t\tFocus the search box"
 msgstr "/\t\tFokusera sökrutan"
 
-#: gitk:2714
+#: gitk:2842
 msgid "?\t\tMove to previous find hit"
 msgstr "?\t\tGå till föregående sökträff"
 
-#: gitk:2715
+#: gitk:2843
 msgid "f\t\tScroll diff view to next file"
 msgstr "f\t\tRulla diffvisningen till nästa fil"
 
-#: gitk:2716
+#: gitk:2844
 #, tcl-format
 msgid "<%s-S>\t\tSearch for next hit in diff view"
 msgstr "<%s-S>\t\tGå till nästa sökträff i diffvisningen"
 
-#: gitk:2717
+#: gitk:2845
 #, tcl-format
 msgid "<%s-R>\t\tSearch for previous hit in diff view"
 msgstr "<%s-R>\t\tGå till föregående sökträff i diffvisningen"
 
-#: gitk:2718
+#: gitk:2846
 #, tcl-format
 msgid "<%s-KP+>\tIncrease font size"
 msgstr "<%s-Num+>\tÖka teckenstorlek"
 
-#: gitk:2719
+#: gitk:2847
 #, tcl-format
 msgid "<%s-plus>\tIncrease font size"
 msgstr "<%s-plus>\tÖka teckenstorlek"
 
-#: gitk:2720
+#: gitk:2848
 #, tcl-format
 msgid "<%s-KP->\tDecrease font size"
 msgstr "<%s-Num->\tMinska teckenstorlek"
 
-#: gitk:2721
+#: gitk:2849
 #, tcl-format
 msgid "<%s-minus>\tDecrease font size"
 msgstr "<%s-minus>\tMinska teckenstorlek"
 
-#: gitk:2722
+#: gitk:2850
 msgid "<F5>\t\tUpdate"
 msgstr "<F5>\t\tUppdatera"
 
-#: gitk:3177
-#, tcl-format
-msgid "Error getting \"%s\" from %s:"
-msgstr "Fel vid hämtning av  \"%s\" från %s:"
-
-#: gitk:3234 gitk:3243
+#: gitk:3305 gitk:3314
 #, tcl-format
 msgid "Error creating temporary directory %s:"
 msgstr "Fel vid skapande av temporär katalog %s:"
 
-#: gitk:3255
+#: gitk:3327
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Fel vid hämtning av  \"%s\" från %s:"
+
+#: gitk:3390
 msgid "command failed:"
 msgstr "kommando misslyckades:"
 
-#: gitk:3401
+#: gitk:3539
 msgid "No such commit"
 msgstr "Incheckning saknas"
 
-#: gitk:3415
+#: gitk:3553
 msgid "git gui blame: command failed:"
 msgstr "git gui blame: kommando misslyckades:"
 
-#: gitk:3446
+#: gitk:3584
 #, tcl-format
 msgid "Couldn't read merge head: %s"
 msgstr "Kunde inte läsa sammanslagningshuvud: %s"
 
-#: gitk:3454
+#: gitk:3592
 #, tcl-format
 msgid "Error reading index: %s"
 msgstr "Fel vid läsning av index: %s"
 
-#: gitk:3479
+#: gitk:3617
 #, tcl-format
 msgid "Couldn't start git blame: %s"
 msgstr "Kunde inte starta git blame: %s"
 
-#: gitk:3482 gitk:6271
+#: gitk:3620 gitk:6409
 msgid "Searching"
 msgstr "Söker"
 
-#: gitk:3514
+#: gitk:3652
 #, tcl-format
 msgid "Error running git blame: %s"
 msgstr "Fel vid körning av git blame: %s"
 
-#: gitk:3542
+#: gitk:3680
 #, tcl-format
 msgid "That line comes from commit %s,  which is not in this view"
 msgstr "Raden kommer från incheckningen %s, som inte finns i denna vy"
 
-#: gitk:3556
+#: gitk:3694
 msgid "External diff viewer failed:"
 msgstr "Externt diff-verktyg misslyckades:"
 
-#: gitk:3674
+#: gitk:3812
 msgid "Gitk view definition"
 msgstr "Definition av Gitk-vy"
 
-#: gitk:3678
+#: gitk:3816
 msgid "Remember this view"
 msgstr "Spara denna vy"
 
-#: gitk:3679
+#: gitk:3817
 msgid "References (space separated list):"
 msgstr "Referenser (blankstegsavdelad lista):"
 
-#: gitk:3680
+#: gitk:3818
 msgid "Branches & tags:"
 msgstr "Grenar & taggar:"
 
-#: gitk:3681
+#: gitk:3819
 msgid "All refs"
 msgstr "Alla referenser"
 
-#: gitk:3682
+#: gitk:3820
 msgid "All (local) branches"
 msgstr "Alla (lokala) grenar"
 
-#: gitk:3683
+#: gitk:3821
 msgid "All tags"
 msgstr "Alla taggar"
 
-#: gitk:3684
+#: gitk:3822
 msgid "All remote-tracking branches"
 msgstr "Alla fjärrspårande grenar"
 
-#: gitk:3685
+#: gitk:3823
 msgid "Commit Info (regular expressions):"
 msgstr "Incheckningsinfo (reguljära uttryck):"
 
-#: gitk:3686
+#: gitk:3824
 msgid "Author:"
 msgstr "Författare:"
 
-#: gitk:3687
+#: gitk:3825
 msgid "Committer:"
 msgstr "Incheckare:"
 
-#: gitk:3688
+#: gitk:3826
 msgid "Commit Message:"
 msgstr "Incheckningsmeddelande:"
 
-#: gitk:3689
+#: gitk:3827
 msgid "Matches all Commit Info criteria"
 msgstr "Motsvarar alla kriterier för incheckningsinfo"
 
-#: gitk:3690
+#: gitk:3828
 msgid "Changes to Files:"
 msgstr "Ändringar av filer:"
 
-#: gitk:3691
+#: gitk:3829
 msgid "Fixed String"
 msgstr "Fast sträng"
 
-#: gitk:3692
+#: gitk:3830
 msgid "Regular Expression"
 msgstr "Reguljärt uttryck"
 
-#: gitk:3693
+#: gitk:3831
 msgid "Search string:"
 msgstr "Söksträng:"
 
-#: gitk:3694
+#: gitk:3832
 msgid ""
 "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
 "15:27:38\"):"
@@ -639,205 +643,201 @@ msgstr ""
 "Incheckingsdatum (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
 "15:27:38\"):"
 
-#: gitk:3695
+#: gitk:3833
 msgid "Since:"
 msgstr "Från:"
 
-#: gitk:3696
+#: gitk:3834
 msgid "Until:"
 msgstr "Till:"
 
-#: gitk:3697
+#: gitk:3835
 msgid "Limit and/or skip a number of revisions (positive integer):"
 msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):"
 
-#: gitk:3698
+#: gitk:3836
 msgid "Number to show:"
 msgstr "Antal att visa:"
 
-#: gitk:3699
+#: gitk:3837
 msgid "Number to skip:"
 msgstr "Antal att hoppa över:"
 
-#: gitk:3700
+#: gitk:3838
 msgid "Miscellaneous options:"
 msgstr "Diverse alternativ:"
 
-#: gitk:3701
+#: gitk:3839
 msgid "Strictly sort by date"
 msgstr "Strikt datumsortering"
 
-#: gitk:3702
+#: gitk:3840
 msgid "Mark branch sides"
 msgstr "Markera sidogrenar"
 
-#: gitk:3703
+#: gitk:3841
 msgid "Limit to first parent"
 msgstr "Begränsa till första förälder"
 
-#: gitk:3704
+#: gitk:3842
 msgid "Simple history"
 msgstr "Enkel historik"
 
-#: gitk:3705
+#: gitk:3843
 msgid "Additional arguments to git log:"
 msgstr "Ytterligare argument till git log:"
 
-#: gitk:3706
+#: gitk:3844
 msgid "Enter files and directories to include, one per line:"
 msgstr "Ange filer och kataloger att ta med, en per rad:"
 
-#: gitk:3707
+#: gitk:3845
 msgid "Command to generate more commits to include:"
 msgstr "Kommando för att generera fler incheckningar att ta med:"
 
-#: gitk:3829
+#: gitk:3967
 msgid "Gitk: edit view"
 msgstr "Gitk: redigera vy"
 
-#: gitk:3837
+#: gitk:3975
 msgid "-- criteria for selecting revisions"
 msgstr " - kriterier för val av revisioner"
 
-#: gitk:3842
-msgid "View Name:"
-msgstr "Namn på vy:"
+#: gitk:3980
+msgid "View Name"
+msgstr "Namn på vy"
 
-#: gitk:3917
+#: gitk:4055
 msgid "Apply (F5)"
 msgstr "Använd (F5)"
 
-#: gitk:3955
+#: gitk:4093
 msgid "Error in commit selection arguments:"
 msgstr "Fel i argument för val av incheckningar:"
 
-#: gitk:4008 gitk:4060 gitk:4508 gitk:4522 gitk:5783 gitk:11196 gitk:11197
+#: gitk:4146 gitk:4198 gitk:4646 gitk:4660 gitk:5921 gitk:11534 gitk:11535
 msgid "None"
 msgstr "Inget"
 
-#: gitk:4456 gitk:6303 gitk:8065 gitk:8080
+#: gitk:4594 gitk:6441 gitk:8287 gitk:8302
 msgid "Date"
 msgstr "Datum"
 
-#: gitk:4456 gitk:6303
+#: gitk:4594 gitk:6441
 msgid "CDate"
 msgstr "Skapat datum"
 
-#: gitk:4605 gitk:4610
+#: gitk:4743 gitk:4748
 msgid "Descendant"
 msgstr "Avkomling"
 
-#: gitk:4606
+#: gitk:4744
 msgid "Not descendant"
 msgstr "Inte avkomling"
 
-#: gitk:4613 gitk:4618
+#: gitk:4751 gitk:4756
 msgid "Ancestor"
 msgstr "Förfader"
 
-#: gitk:4614
+#: gitk:4752
 msgid "Not ancestor"
 msgstr "Inte förfader"
 
-#: gitk:4904
+#: gitk:5042
 msgid "Local changes checked in to index but not committed"
 msgstr "Lokala ändringar sparade i indexet men inte incheckade"
 
-#: gitk:4940
+#: gitk:5078
 msgid "Local uncommitted changes, not checked in to index"
 msgstr "Lokala ändringar, ej sparade i indexet"
 
-#: gitk:6621
+#: gitk:6759
 msgid "many"
 msgstr "många"
 
-#: gitk:6805
+#: gitk:6942
 msgid "Tags:"
 msgstr "Taggar:"
 
-#: gitk:6822 gitk:6828 gitk:8058
+#: gitk:6959 gitk:6965 gitk:8280
 msgid "Parent"
 msgstr "Förälder"
 
-#: gitk:6833
+#: gitk:6970
 msgid "Child"
 msgstr "Barn"
 
-#: gitk:6842
+#: gitk:6979
 msgid "Branch"
 msgstr "Gren"
 
-#: gitk:6845
+#: gitk:6982
 msgid "Follows"
 msgstr "Följer"
 
-#: gitk:6848
+#: gitk:6985
 msgid "Precedes"
 msgstr "Föregår"
 
-#: gitk:7346
+#: gitk:7522
 #, tcl-format
 msgid "Error getting diffs: %s"
 msgstr "Fel vid hämtning av diff: %s"
 
-#: gitk:7886
+#: gitk:8108
 msgid "Goto:"
 msgstr "Gå till:"
 
-#: gitk:7888
-msgid "SHA1 ID:"
-msgstr "SHA1-id:"
-
-#: gitk:7907
+#: gitk:8129
 #, tcl-format
 msgid "Short SHA1 id %s is ambiguous"
 msgstr "Förkortat SHA1-id %s är tvetydigt"
 
-#: gitk:7914
+#: gitk:8136
 #, tcl-format
 msgid "Revision %s is not known"
 msgstr "Revisionen %s är inte känd"
 
-#: gitk:7924
+#: gitk:8146
 #, tcl-format
 msgid "SHA1 id %s is not known"
 msgstr "SHA-id:t %s är inte känt"
 
-#: gitk:7926
+#: gitk:8148
 #, tcl-format
 msgid "Revision %s is not in the current view"
 msgstr "Revisionen %s finns inte i den nuvarande vyn"
 
-#: gitk:8068
+#: gitk:8290
 msgid "Children"
 msgstr "Barn"
 
-#: gitk:8125
+#: gitk:8348
 #, tcl-format
 msgid "Reset %s branch to here"
 msgstr "Återställ grenen %s hit"
 
-#: gitk:8127
+#: gitk:8350
 msgid "Detached head: can't reset"
 msgstr "Frånkopplad head: kan inte återställa"
 
-#: gitk:8236 gitk:8242
+#: gitk:8459 gitk:8465
 msgid "Skipping merge commit "
 msgstr "Hoppar över sammanslagningsincheckning "
 
-#: gitk:8251 gitk:8256
+#: gitk:8474 gitk:8479
 msgid "Error getting patch ID for "
 msgstr "Fel vid hämtning av patch-id för "
 
-#: gitk:8252 gitk:8257
+#: gitk:8475 gitk:8480
 msgid " - stopping\n"
 msgstr " - stannar\n"
 
-#: gitk:8262 gitk:8265 gitk:8273 gitk:8283 gitk:8292
+#: gitk:8485 gitk:8488 gitk:8496 gitk:8510 gitk:8519
 msgid "Commit "
 msgstr "Incheckning "
 
-#: gitk:8266
+#: gitk:8489
 msgid ""
 " is the same patch as\n"
 "       "
@@ -845,7 +845,7 @@ msgstr ""
 " är samma patch som\n"
 "       "
 
-#: gitk:8274
+#: gitk:8497
 msgid ""
 " differs from\n"
 "       "
@@ -853,117 +853,139 @@ msgstr ""
 " skiljer sig från\n"
 "       "
 
-#: gitk:8276
-msgid "- stopping\n"
-msgstr "- stannar\n"
+#: gitk:8499
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr "Skillnad mellan incheckningar:\n"
+"\n"
+""
 
-#: gitk:8284 gitk:8293
+#: gitk:8511 gitk:8520
 #, tcl-format
 msgid " has %s children - stopping\n"
 msgstr " har %s barn - stannar\n"
 
-#: gitk:8324
+#: gitk:8539
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "Fel vid skrivning av incheckning till fil: %s"
+
+#: gitk:8545
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "Fel vid jämförelse av incheckningar: %s"
+
+#: gitk:8575
 msgid "Top"
 msgstr "Topp"
 
-#: gitk:8325
+#: gitk:8576
 msgid "From"
 msgstr "Från"
 
-#: gitk:8330
+#: gitk:8581
 msgid "To"
 msgstr "Till"
 
-#: gitk:8354
+#: gitk:8605
 msgid "Generate patch"
 msgstr "Generera patch"
 
-#: gitk:8356
+#: gitk:8607
 msgid "From:"
 msgstr "Från:"
 
-#: gitk:8365
+#: gitk:8616
 msgid "To:"
 msgstr "Till:"
 
-#: gitk:8374
+#: gitk:8625
 msgid "Reverse"
 msgstr "Vänd"
 
-#: gitk:8376 gitk:8561
+#: gitk:8627 gitk:8822
 msgid "Output file:"
 msgstr "Utdatafil:"
 
-#: gitk:8382
+#: gitk:8633
 msgid "Generate"
 msgstr "Generera"
 
-#: gitk:8420
+#: gitk:8671
 msgid "Error creating patch:"
 msgstr "Fel vid generering av patch:"
 
-#: gitk:8443 gitk:8549 gitk:8606
+#: gitk:8694 gitk:8810 gitk:8867
 msgid "ID:"
 msgstr "Id:"
 
-#: gitk:8452
+#: gitk:8703
 msgid "Tag name:"
 msgstr "Taggnamn:"
 
-#: gitk:8456 gitk:8615
+#: gitk:8706
+msgid "Tag message is optional"
+msgstr "Taggmeddelandet är valfritt"
+
+#: gitk:8708
+msgid "Tag message:"
+msgstr "Taggmeddelande:"
+
+#: gitk:8712 gitk:8876
 msgid "Create"
 msgstr "Skapa"
 
-#: gitk:8473
+#: gitk:8730
 msgid "No tag name specified"
 msgstr "Inget taggnamn angavs"
 
-#: gitk:8477
+#: gitk:8734
 #, tcl-format
 msgid "Tag \"%s\" already exists"
 msgstr "Taggen \"%s\" finns redan"
 
-#: gitk:8483
+#: gitk:8744
 msgid "Error creating tag:"
 msgstr "Fel vid skapande av tagg:"
 
-#: gitk:8558
+#: gitk:8819
 msgid "Command:"
 msgstr "Kommando:"
 
-#: gitk:8566
+#: gitk:8827
 msgid "Write"
 msgstr "Skriv"
 
-#: gitk:8584
+#: gitk:8845
 msgid "Error writing commit:"
 msgstr "Fel vid skrivning av incheckning:"
 
-#: gitk:8611
+#: gitk:8872
 msgid "Name:"
 msgstr "Namn:"
 
-#: gitk:8634
+#: gitk:8895
 msgid "Please specify a name for the new branch"
 msgstr "Ange ett namn för den nya grenen"
 
-#: gitk:8639
+#: gitk:8900
 #, tcl-format
 msgid "Branch '%s' already exists. Overwrite?"
 msgstr "Grenen \"%s\" finns redan. Skriva över?"
 
-#: gitk:8705
+#: gitk:8966
 #, tcl-format
 msgid "Commit %s is already included in branch %s -- really re-apply it?"
 msgstr ""
 "Incheckningen %s finns redan på grenen %s -- skall den verkligen appliceras "
 "på nytt?"
 
-#: gitk:8710
+#: gitk:8971
 msgid "Cherry-picking"
 msgstr "Plockar"
 
-#: gitk:8719
+#: gitk:8980
 #, tcl-format
 msgid ""
 "Cherry-pick failed because of local changes to file '%s'.\n"
@@ -973,7 +995,7 @@ msgstr ""
 "Checka in, återställ eller spara undan (stash) dina ändringar och försök "
 "igen."
 
-#: gitk:8725
+#: gitk:8986
 msgid ""
 "Cherry-pick failed because of merge conflict.\n"
 "Do you wish to run git citool to resolve it?"
@@ -981,32 +1003,32 @@ msgstr ""
 "Cherry-pick misslyckades på grund av en sammanslagningskonflikt.\n"
 "Vill du köra git citool för att lösa den?"
 
-#: gitk:8741
+#: gitk:9002
 msgid "No changes committed"
 msgstr "Inga ändringar incheckade"
 
-#: gitk:8767
+#: gitk:9028
 msgid "Confirm reset"
 msgstr "Bekräfta återställning"
 
-#: gitk:8769
+#: gitk:9030
 #, tcl-format
 msgid "Reset branch %s to %s?"
 msgstr "Återställa grenen %s till %s?"
 
-#: gitk:8773
+#: gitk:9032
 msgid "Reset type:"
 msgstr "Typ av återställning:"
 
-#: gitk:8777
+#: gitk:9035
 msgid "Soft: Leave working tree and index untouched"
 msgstr "Mjuk: Rör inte utcheckning och index"
 
-#: gitk:8780
+#: gitk:9038
 msgid "Mixed: Leave working tree untouched, reset index"
 msgstr "Blandad: Rör inte utcheckning, återställ index"
 
-#: gitk:8783
+#: gitk:9041
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
@@ -1014,19 +1036,19 @@ msgstr ""
 "Hård: Återställ utcheckning och index\n"
 "(förkastar ALLA lokala ändringar)"
 
-#: gitk:8800
+#: gitk:9058
 msgid "Resetting"
 msgstr "Återställer"
 
-#: gitk:8857
+#: gitk:9118
 msgid "Checking out"
 msgstr "Checkar ut"
 
-#: gitk:8910
+#: gitk:9171
 msgid "Cannot delete the currently checked-out branch"
 msgstr "Kan inte ta bort den just nu utcheckade grenen"
 
-#: gitk:8916
+#: gitk:9177
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
@@ -1035,16 +1057,16 @@ msgstr ""
 "Incheckningarna på grenen %s existerar inte på någon annan gren.\n"
 "Vill du verkligen ta bort grenen %s?"
 
-#: gitk:8947
+#: gitk:9208
 #, tcl-format
 msgid "Tags and heads: %s"
 msgstr "Taggar och huvuden: %s"
 
-#: gitk:8962
+#: gitk:9223
 msgid "Filter"
 msgstr "Filter"
 
-#: gitk:9257
+#: gitk:9518
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
@@ -1052,190 +1074,219 @@ msgstr ""
 "Fel vid läsning av information om incheckningstopologi; information om "
 "grenar och föregående/senare taggar kommer inte vara komplett."
 
-#: gitk:10243
+#: gitk:10504
 msgid "Tag"
 msgstr "Tagg"
 
-#: gitk:10243
+#: gitk:10504
 msgid "Id"
 msgstr "Id"
 
-#: gitk:10291
+#: gitk:10554
 msgid "Gitk font chooser"
 msgstr "Teckensnittsväljare för Gitk"
 
-#: gitk:10308
+#: gitk:10571
 msgid "B"
 msgstr "F"
 
-#: gitk:10311
+#: gitk:10574
 msgid "I"
 msgstr "K"
 
-#: gitk:10407
+#: gitk:10692
 msgid "Gitk preferences"
 msgstr "Inställningar för Gitk"
 
-#: gitk:10409
+#: gitk:10694
 msgid "Commit list display options"
 msgstr "Alternativ för incheckningslistvy"
 
-#: gitk:10412
+#: gitk:10697
 msgid "Maximum graph width (lines)"
 msgstr "Maximal grafbredd (rader)"
 
-#: gitk:10416
+#: gitk:10700
 #, tcl-format
 msgid "Maximum graph width (% of pane)"
 msgstr "Maximal grafbredd (% av ruta)"
 
-#: gitk:10420
+#: gitk:10703
 msgid "Show local changes"
 msgstr "Visa lokala ändringar"
 
-#: gitk:10423
+#: gitk:10706
 msgid "Auto-select SHA1"
 msgstr "Välj SHA1 automatiskt"
 
-#: gitk:10427
+#: gitk:10709
+msgid "Hide remote refs"
+msgstr "Dölj fjärr-referenser"
+
+#: gitk:10713
 msgid "Diff display options"
 msgstr "Alternativ för diffvy"
 
-#: gitk:10429
+#: gitk:10715
 msgid "Tab spacing"
 msgstr "Blanksteg för tabulatortecken"
 
-#: gitk:10432
+#: gitk:10718
 msgid "Display nearby tags"
 msgstr "Visa närliggande taggar"
 
-#: gitk:10435
-msgid "Hide remote refs"
-msgstr "Dölj fjärr-referenser"
-
-#: gitk:10438
+#: gitk:10721
 msgid "Limit diffs to listed paths"
 msgstr "Begränsa diff till listade sökvägar"
 
-#: gitk:10441
+#: gitk:10724
 msgid "Support per-file encodings"
 msgstr "Stöd för filspecifika teckenkodningar"
 
-#: gitk:10447 gitk:10512
+#: gitk:10730 gitk:10819
 msgid "External diff tool"
 msgstr "Externt diff-verktyg"
 
-#: gitk:10449
+#: gitk:10731
 msgid "Choose..."
 msgstr "Välj..."
 
-#: gitk:10454
+#: gitk:10736
+msgid "General options"
+msgstr "Allmänna inställningar"
+
+#: gitk:10739
+msgid "Use themed widgets"
+msgstr "Använd tema på fönsterelement"
+
+#: gitk:10741
+msgid "(change requires restart)"
+msgstr "(ändringen kräver omstart)"
+
+#: gitk:10743
+msgid "(currently unavailable)"
+msgstr "(för närvarande inte tillgängligt)"
+
+#: gitk:10747
 msgid "Colors: press to choose"
 msgstr "Färger: tryck för att välja"
 
-#: gitk:10457
+#: gitk:10750
+msgid "Interface"
+msgstr "Gränssnitt"
+
+#: gitk:10751
+msgid "interface"
+msgstr "gränssnitt"
+
+#: gitk:10754
 msgid "Background"
 msgstr "Bakgrund"
 
-#: gitk:10458 gitk:10488
+#: gitk:10755 gitk:10785
 msgid "background"
 msgstr "bakgrund"
 
-#: gitk:10461
+#: gitk:10758
 msgid "Foreground"
 msgstr "Förgrund"
 
-#: gitk:10462
+#: gitk:10759
 msgid "foreground"
 msgstr "förgrund"
 
-#: gitk:10465
+#: gitk:10762
 msgid "Diff: old lines"
 msgstr "Diff: gamla rader"
 
-#: gitk:10466
+#: gitk:10763
 msgid "diff old lines"
 msgstr "diff gamla rader"
 
-#: gitk:10470
+#: gitk:10767
 msgid "Diff: new lines"
 msgstr "Diff: nya rader"
 
-#: gitk:10471
+#: gitk:10768
 msgid "diff new lines"
 msgstr "diff nya rader"
 
-#: gitk:10475
+#: gitk:10772
 msgid "Diff: hunk header"
 msgstr "Diff: delhuvud"
 
-#: gitk:10477
+#: gitk:10774
 msgid "diff hunk header"
 msgstr "diff delhuvud"
 
-#: gitk:10481
+#: gitk:10778
 msgid "Marked line bg"
 msgstr "Markerad rad bakgrund"
 
-#: gitk:10483
+#: gitk:10780
 msgid "marked line background"
 msgstr "markerad rad bakgrund"
 
-#: gitk:10487
+#: gitk:10784
 msgid "Select bg"
 msgstr "Markerad bakgrund"
 
-#: gitk:10491
+#: gitk:10788
 msgid "Fonts: press to choose"
 msgstr "Teckensnitt: tryck för att välja"
 
-#: gitk:10493
+#: gitk:10790
 msgid "Main font"
 msgstr "Huvudteckensnitt"
 
-#: gitk:10494
+#: gitk:10791
 msgid "Diff display font"
 msgstr "Teckensnitt för diffvisning"
 
-#: gitk:10495
+#: gitk:10792
 msgid "User interface font"
 msgstr "Teckensnitt för användargränssnitt"
 
-#: gitk:10522
+#: gitk:10829
 #, tcl-format
 msgid "Gitk: choose color for %s"
 msgstr "Gitk: välj färg för %s"
 
-#: gitk:10973
-msgid ""
-"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
-" Gitk requires at least Tcl/Tk 8.4."
-msgstr ""
-"Gitk kan tyvärr inte köra med denna version av Tcl/Tk.\n"
-" Gitk kräver åtminstone Tcl/Tk 8.4."
-
-#: gitk:11101
+#: gitk:11433
 msgid "Cannot find a git repository here."
 msgstr "Hittar inget gitk-arkiv här."
 
-#: gitk:11105
+#: gitk:11437
 #, tcl-format
 msgid "Cannot find the git directory \"%s\"."
 msgstr "Hittar inte git-katalogen \"%s\"."
 
-#: gitk:11152
+#: gitk:11484
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
 msgstr "Tvetydigt argument \"%s\": både revision och filnamn"
 
-#: gitk:11164
+#: gitk:11496
 msgid "Bad arguments to gitk:"
 msgstr "Felaktiga argument till gitk:"
 
-#: gitk:11249
+#: gitk:11587
 msgid "Command line"
 msgstr "Kommandorad"
 
+#~ msgid "SHA1 ID: "
+#~ msgstr "SHA1-id: "
+
+#~ msgid "- stopping\n"
+#~ msgstr "- stannar\n"
+
+#~ msgid ""
+#~ "Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+#~ " Gitk requires at least Tcl/Tk 8.4."
+#~ msgstr ""
+#~ "Gitk kan tyvärr inte köra med denna version av Tcl/Tk.\n"
+#~ " Gitk kräver åtminstone Tcl/Tk 8.4."
+
 #~ msgid "Tag/Head %s is not known"
 #~ msgstr "Tagg/huvud %s är okänt"
 
diff --git a/gitweb/Makefile b/gitweb/Makefile
new file mode 100644 (file)
index 0000000..c9eb1ee
--- /dev/null
@@ -0,0 +1,129 @@
+# The default target of this Makefile is...
+all::
+
+# Define V=1 to have a more verbose compile.
+#
+# Define JSMIN to point to JavaScript minifier that functions as
+# a filter to have gitweb.js minified.
+#
+
+prefix ?= $(HOME)
+bindir ?= $(prefix)/bin
+RM ?= rm -f
+
+# JavaScript minifier invocation that can function as filter
+JSMIN ?=
+
+# default configuration for gitweb
+GITWEB_CONFIG = gitweb_config.perl
+GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf
+GITWEB_HOME_LINK_STR = projects
+GITWEB_SITENAME =
+GITWEB_PROJECTROOT = /pub/git
+GITWEB_PROJECT_MAXDEPTH = 2007
+GITWEB_EXPORT_OK =
+GITWEB_STRICT_EXPORT =
+GITWEB_BASE_URL =
+GITWEB_LIST =
+GITWEB_HOMETEXT = indextext.html
+GITWEB_CSS = gitweb.css
+GITWEB_LOGO = git-logo.png
+GITWEB_FAVICON = git-favicon.png
+ifdef JSMIN
+GITWEB_JS = gitweb.min.js
+else
+GITWEB_JS = gitweb.js
+endif
+GITWEB_SITE_HEADER =
+GITWEB_SITE_FOOTER =
+
+# include user config
+-include ../config.mak.autogen
+-include ../config.mak
+
+# determine version
+../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+       $(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE
+
+-include ../GIT-VERSION-FILE
+
+### Build rules
+
+SHELL_PATH ?= $(SHELL)
+PERL_PATH  ?= /usr/bin/perl
+
+# Shell quote;
+bindir_SQ = $(subst ','\'',$(bindir))         #'
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) #'
+PERL_PATH_SQ  = $(subst ','\'',$(PERL_PATH))  #'
+
+# Quiet generation (unless V=1)
+QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
+QUIET_SUBDIR1  =
+
+ifneq ($(findstring $(MAKEFLAGS),w),w)
+PRINT_DIR = --no-print-directory
+else # "make -w"
+NO_SUBDIR = :
+endif
+
+ifneq ($(findstring $(MAKEFLAGS),s),s)
+ifndef V
+       QUIET          = @
+       QUIET_GEN      = $(QUIET)echo '   ' GEN $@;
+       QUIET_SUBDIR0  = +@subdir=
+       QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
+                        $(MAKE) $(PRINT_DIR) -C $$subdir
+       export V
+       export QUIET
+       export QUIET_GEN
+       export QUIET_SUBDIR0
+       export QUIET_SUBDIR1
+endif
+endif
+
+all:: gitweb.cgi
+
+ifdef JSMIN
+FILES=gitweb.cgi gitweb.min.js
+gitweb.cgi: gitweb.perl gitweb.min.js
+else # !JSMIN
+FILES=gitweb.cgi
+gitweb.cgi: gitweb.perl
+endif # JSMIN
+
+gitweb.cgi:
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+           -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
+           -e 's|++GIT_BINDIR++|$(bindir)|g' \
+           -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \
+           -e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \
+           -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \
+           -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \
+           -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \
+           -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \
+           -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \
+           -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \
+           -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \
+           -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \
+           -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \
+           -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \
+           -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
+           -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
+           -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \
+           -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
+           -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
+           $< >$@+ && \
+       chmod +x $@+ && \
+       mv $@+ $@
+
+ifdef JSMIN
+gitweb.min.js: gitweb.js
+       $(QUIET_GEN)$(JSMIN) <$< >$@
+endif # JSMIN
+
+clean:
+       $(RM) $(FILES)
+
+.PHONY: all clean .FORCE-GIT-VERSION-FILE
index e34ee793ef51584344f8c808454b9ead0c3348c6..6c2c8e12598ca9ba17e7cc3d74812a8b7883d59a 100644 (file)
@@ -174,7 +174,7 @@ not include variables usually directly set during build):
    Base URL for relative URLs in pages generated by gitweb,
    (e.g. $logo, $favicon, @stylesheets if they are relative URLs),
    needed and used only for URLs with nonempty PATH_INFO via
-   <base href="$base_url>.  Usually gitweb sets its value correctly,
+   <base href="$base_url">.  Usually gitweb sets its value correctly,
    and there is no need to set this variable, e.g. to $my_uri or "/".
  * $home_link
    Target of the home link on top of all pages (the first part of view
@@ -228,6 +228,11 @@ not include variables usually directly set during build):
    repositories from launching cross-site scripting (XSS) attacks.  Set this
    to true if you don't trust the content of your repositories. The default
    is false.
+ * $maxload
+   Used to set the maximum load that we will still respond to gitweb queries.
+   If server load exceed this value then return "503 Service Unavaliable" error.
+   Server load is taken to be 0 if gitweb cannot determine its value.  Set it to
+   undefined value to turn it off.  The default is 300.
 
 
 Projects list file format
index 2a25b7cc470a25572e8741803b4e7124933feff5..9c66928c4af8cf37b9c13a6a40936523e16fcc6f 100644 (file)
@@ -779,7 +779,12 @@ function handleResponse() {
        }
 
        // the server returned error
-       if (xhr.readyState === 3 && xhr.status !== 200) {
+       // try ... catch block is to work around bug in IE8
+       try {
+               if (xhr.readyState === 3 && xhr.status !== 200) {
+                       return;
+               }
+       } catch (e) {
                return;
        }
        if (xhr.readyState === 4 && xhr.status !== 200) {
index 7e477af9567cff322d18fc9a1fbfe507732418dc..1f6978ac1f3ca2f915c5b87d8b196ee1e0e52aca 100755 (executable)
@@ -221,6 +221,12 @@ our %avatar_size = (
        'double'  => 32
 );
 
+# Used to set the maximum load that we will still respond to gitweb queries.
+# If server load exceed this value then return "503 server busy" error.
+# If gitweb cannot determined server load, it is taken to be 0.
+# Leave it undefined (or set to 'undef') to turn off load checking.
+our $maxload = 300;
+
 # You define site-wide feature defaults here; override them with
 # $GITWEB_CONFIG as necessary.
 our %feature = (
@@ -551,12 +557,38 @@ if (-e $GITWEB_CONFIG) {
        do $GITWEB_CONFIG_SYSTEM if -e $GITWEB_CONFIG_SYSTEM;
 }
 
+# Get loadavg of system, to compare against $maxload.
+# Currently it requires '/proc/loadavg' present to get loadavg;
+# if it is not present it returns 0, which means no load checking.
+sub get_loadavg {
+       if( -e '/proc/loadavg' ){
+               open my $fd, '<', '/proc/loadavg'
+                       or return 0;
+               my @load = split(/\s+/, scalar <$fd>);
+               close $fd;
+
+               # The first three columns measure CPU and IO utilization of the last one,
+               # five, and 10 minute periods.  The fourth column shows the number of
+               # currently running processes and the total number of processes in the m/n
+               # format.  The last column displays the last process ID used.
+               return $load[0] || 0;
+       }
+       # additional checks for load average should go here for things that don't export
+       # /proc/loadavg
+
+       return 0;
+}
+
 # version of the core git binary
 our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
 $number_of_git_cmds++;
 
 $projects_list ||= $projectroot;
 
+if (defined $maxload && get_loadavg() > $maxload) {
+       die_error(503, "The load average on the server is too high");
+}
+
 # ======================================================================
 # input validation and dispatch
 
@@ -1298,7 +1330,6 @@ sub chop_str {
                $str =~ m/^(.*?)($begre)$/;
                my ($lead, $body) = ($1, $2);
                if (length($lead) > 4) {
-                       $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/);
                        $lead = " ...";
                }
                return "$lead$body";
@@ -1309,8 +1340,6 @@ sub chop_str {
                $str =~ m/^(.*?)($begre)$/;
                my ($mid, $right) = ($1, $2);
                if (length($mid) > 5) {
-                       $left  =~ s/&[^;]*$//;
-                       $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/);
                        $mid = " ... ";
                }
                return "$left$mid$right";
@@ -1320,7 +1349,6 @@ sub chop_str {
                my $body = $1;
                my $tail = $2;
                if (length($tail) > 4) {
-                       $body =~ s/&[^;]*$//;
                        $tail = "... ";
                }
                return "$body$tail";
@@ -3222,7 +3250,7 @@ EOF
        print "</head>\n" .
              "<body>\n";
 
-       if (-f $site_header) {
+       if (defined $site_header && -f $site_header) {
                insert_file($site_header);
        }
 
@@ -3323,12 +3351,13 @@ sub git_footer_html {
                print "</div>\n"; # class="page_footer"
        }
 
-       if (-f $site_footer) {
+       if (defined $site_footer && -f $site_footer) {
                insert_file($site_footer);
        }
 
        print qq!<script type="text/javascript" src="$javascript"></script>\n!;
-       if ($action eq 'blame_incremental') {
+       if (defined $action &&
+           $action eq 'blame_incremental') {
                print qq!<script type="text/javascript">\n!.
                      qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
                      qq!           "!. href() .qq!");\n!.
@@ -3354,22 +3383,33 @@ sub git_footer_html {
 # 500: The server isn't configured properly, or
 #      an internal error occurred (e.g. failed assertions caused by bugs), or
 #      an unknown error occurred (e.g. the git binary died unexpectedly).
+# 503: The server is currently unavailable (because it is overloaded,
+#      or down for maintenance).  Generally, this is a temporary state.
 sub die_error {
        my $status = shift || 500;
        my $error = shift || "Internal server error";
-
-       my %http_responses = (400 => '400 Bad Request',
-                             403 => '403 Forbidden',
-                             404 => '404 Not Found',
-                             500 => '500 Internal Server Error');
+       my $extra = shift;
+
+       my %http_responses = (
+               400 => '400 Bad Request',
+               403 => '403 Forbidden',
+               404 => '404 Not Found',
+               500 => '500 Internal Server Error',
+               503 => '503 Service Unavailable',
+       );
        git_header_html($http_responses{$status});
        print <<EOF;
 <div class="page_body">
 <br /><br />
 $status - $error
 <br />
-</div>
 EOF
+       if (defined $extra) {
+               print "<hr />\n" .
+                     "$extra\n";
+       }
+       print "</div>\n";
+
        git_footer_html();
        exit;
 }
@@ -3471,14 +3511,21 @@ sub git_print_header_div {
 }
 
 sub print_local_time {
+       print format_local_time(@_);
+}
+
+sub format_local_time {
+       my $localtime = '';
        my %date = @_;
        if ($date{'hour_local'} < 6) {
-               printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
+               $localtime .= sprintf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
                        $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
        } else {
-               printf(" (%02d:%02d %s)",
+               $localtime .= sprintf(" (%02d:%02d %s)",
                        $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
        }
+
+       return $localtime;
 }
 
 # Outputs the author name and date in long form
@@ -4302,17 +4349,24 @@ sub fill_project_list_info {
 # print 'sort by' <th> element, generating 'sort by $name' replay link
 # if that order is not selected
 sub print_sort_th {
+       print format_sort_th(@_);
+}
+
+sub format_sort_th {
        my ($name, $order, $header) = @_;
+       my $sort_th = "";
        $header ||= ucfirst($name);
 
        if ($order eq $name) {
-               print "<th>$header</th>\n";
+               $sort_th .= "<th>$header</th>\n";
        } else {
-               print "<th>" .
-                     $cgi->a({-href => href(-replay=>1, order=>$name),
-                              -class => "header"}, $header) .
-                     "</th>\n";
+               $sort_th .= "<th>" .
+                           $cgi->a({-href => href(-replay=>1, order=>$name),
+                                    -class => "header"}, $header) .
+                           "</th>\n";
        }
+
+       return $sort_th;
 }
 
 sub git_project_list_body {
@@ -4743,7 +4797,7 @@ sub git_project_list {
        }
 
        git_header_html();
-       if (-f $home_text) {
+       if (defined $home_text && -f $home_text) {
                print "<div class=\"index_include\">\n";
                insert_file($home_text);
                print "</div>\n";
diff --git a/grep.c b/grep.c
index bdadf2c0ccfafc18011beee3bc05754860392523..a0864f1cbbe5fcc6f28eb57a08ebfd9a76c87da6 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -29,11 +29,26 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat,
        p->next = NULL;
 }
 
-static int is_fixed(const char *s)
+struct grep_opt *grep_opt_dup(const struct grep_opt *opt)
 {
-       while (*s && !is_regex_special(*s))
-               s++;
-       return !*s;
+       struct grep_pat *pat;
+       struct grep_opt *ret = xmalloc(sizeof(struct grep_opt));
+       *ret = *opt;
+
+       ret->pattern_list = NULL;
+       ret->pattern_tail = &ret->pattern_list;
+
+       for(pat = opt->pattern_list; pat != NULL; pat = pat->next)
+       {
+               if(pat->token == GREP_PATTERN_HEAD)
+                       append_header_grep_pattern(ret, pat->field,
+                                                  pat->pattern);
+               else
+                       append_grep_pattern(ret, pat->pattern, pat->origin,
+                                           pat->no, pat->token);
+       }
+
+       return ret;
 }
 
 static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
@@ -42,11 +57,8 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
 
        p->word_regexp = opt->word_regexp;
        p->ignore_case = opt->ignore_case;
+       p->fixed = opt->fixed;
 
-       if (opt->fixed || is_fixed(p->pattern))
-               p->fixed = 1;
-       if (opt->regflags & REG_ICASE)
-               p->fixed = 0;
        if (p->fixed)
                return;
 
@@ -260,7 +272,8 @@ static int word_char(char ch)
 
 static void show_name(struct grep_opt *opt, const char *name)
 {
-       printf("%s%c", name, opt->null_following_name ? '\0' : '\n');
+       opt->output(opt, name, strlen(name));
+       opt->output(opt, opt->null_following_name ? "\0" : "\n", 1);
 }
 
 
@@ -497,24 +510,32 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
                      const char *name, unsigned lno, char sign)
 {
        int rest = eol - bol;
+       char sign_str[1];
 
+       sign_str[0] = sign;
        if (opt->pre_context || opt->post_context) {
                if (opt->last_shown == 0) {
                        if (opt->show_hunk_mark)
-                               fputs("--\n", stdout);
+                               opt->output(opt, "--\n", 3);
                        else
                                opt->show_hunk_mark = 1;
                } else if (lno > opt->last_shown + 1)
-                       fputs("--\n", stdout);
+                       opt->output(opt, "--\n", 3);
        }
        opt->last_shown = lno;
 
        if (opt->null_following_name)
-               sign = '\0';
-       if (opt->pathname)
-               printf("%s%c", name, sign);
-       if (opt->linenum)
-               printf("%d%c", lno, sign);
+               sign_str[0] = '\0';
+       if (opt->pathname) {
+               opt->output(opt, name, strlen(name));
+               opt->output(opt, sign_str, 1);
+       }
+       if (opt->linenum) {
+               char buf[32];
+               snprintf(buf, sizeof(buf), "%d", lno);
+               opt->output(opt, buf, strlen(buf));
+               opt->output(opt, sign_str, 1);
+       }
        if (opt->color) {
                regmatch_t match;
                enum grep_context ctx = GREP_CONTEXT_BODY;
@@ -525,18 +546,22 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
                while (next_match(opt, bol, eol, ctx, &match, eflags)) {
                        if (match.rm_so == match.rm_eo)
                                break;
-                       printf("%.*s%s%.*s%s",
-                              (int)match.rm_so, bol,
-                              opt->color_match,
-                              (int)(match.rm_eo - match.rm_so), bol + match.rm_so,
-                              GIT_COLOR_RESET);
+
+                       opt->output(opt, bol, match.rm_so);
+                       opt->output(opt, opt->color_match,
+                                   strlen(opt->color_match));
+                       opt->output(opt, bol + match.rm_so,
+                                   (int)(match.rm_eo - match.rm_so));
+                       opt->output(opt, GIT_COLOR_RESET,
+                                   strlen(GIT_COLOR_RESET));
                        bol += match.rm_eo;
                        rest -= match.rm_eo;
                        eflags = REG_NOTBOL;
                }
                *eol = ch;
        }
-       printf("%.*s\n", rest, bol);
+       opt->output(opt, bol, rest);
+       opt->output(opt, "\n", 1);
 }
 
 static int match_funcname(struct grep_opt *opt, char *bol, char *eol)
@@ -615,6 +640,98 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
        }
 }
 
+static int should_lookahead(struct grep_opt *opt)
+{
+       struct grep_pat *p;
+
+       if (opt->extended)
+               return 0; /* punt for too complex stuff */
+       if (opt->invert)
+               return 0;
+       for (p = opt->pattern_list; p; p = p->next) {
+               if (p->token != GREP_PATTERN)
+                       return 0; /* punt for "header only" and stuff */
+       }
+       return 1;
+}
+
+static int look_ahead(struct grep_opt *opt,
+                     unsigned long *left_p,
+                     unsigned *lno_p,
+                     char **bol_p)
+{
+       unsigned lno = *lno_p;
+       char *bol = *bol_p;
+       struct grep_pat *p;
+       char *sp, *last_bol;
+       regoff_t earliest = -1;
+
+       for (p = opt->pattern_list; p; p = p->next) {
+               int hit;
+               regmatch_t m;
+
+               if (p->fixed)
+                       hit = !fixmatch(p->pattern, bol, p->ignore_case, &m);
+               else {
+#ifdef REG_STARTEND
+                       m.rm_so = 0;
+                       m.rm_eo = *left_p;
+                       hit = !regexec(&p->regexp, bol, 1, &m, REG_STARTEND);
+#else
+                       hit = !regexec(&p->regexp, bol, 1, &m, 0);
+#endif
+               }
+               if (!hit || m.rm_so < 0 || m.rm_eo < 0)
+                       continue;
+               if (earliest < 0 || m.rm_so < earliest)
+                       earliest = m.rm_so;
+       }
+
+       if (earliest < 0) {
+               *bol_p = bol + *left_p;
+               *left_p = 0;
+               return 1;
+       }
+       for (sp = bol + earliest; bol < sp && sp[-1] != '\n'; sp--)
+               ; /* find the beginning of the line */
+       last_bol = sp;
+
+       for (sp = bol; sp < last_bol; sp++) {
+               if (*sp == '\n')
+                       lno++;
+       }
+       *left_p -= last_bol - bol;
+       *bol_p = last_bol;
+       *lno_p = lno;
+       return 0;
+}
+
+int grep_threads_ok(const struct grep_opt *opt)
+{
+       /* If this condition is true, then we may use the attribute
+        * machinery in grep_buffer_1. The attribute code is not
+        * thread safe, so we disable the use of threads.
+        */
+       if (opt->funcname && !opt->unmatch_name_only && !opt->status_only &&
+           !opt->name_only)
+               return 0;
+
+       /* If we are showing hunk marks, we should not do it for the
+        * first match. The synchronization problem we get for this
+        * constraint is not yet solved, so we disable threading in
+        * this case.
+        */
+       if (opt->pre_context || opt->post_context)
+               return 0;
+
+       return 1;
+}
+
+static void std_output(struct grep_opt *opt, const void *buf, size_t size)
+{
+       fwrite(buf, size, 1, stdout);
+}
+
 static int grep_buffer_1(struct grep_opt *opt, const char *name,
                         char *buf, unsigned long size, int collect_hits)
 {
@@ -624,11 +741,15 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
        unsigned last_hit = 0;
        int binary_match_only = 0;
        unsigned count = 0;
+       int try_lookahead = 0;
        enum grep_context ctx = GREP_CONTEXT_HEAD;
        xdemitconf_t xecfg;
 
        opt->last_shown = 0;
 
+       if (!opt->output)
+               opt->output = std_output;
+
        if (buffer_is_binary(buf, size)) {
                switch (opt->binary) {
                case GREP_BINARY_DEFAULT:
@@ -652,11 +773,26 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                        opt->priv = &xecfg;
                }
        }
+       try_lookahead = should_lookahead(opt);
 
        while (left) {
                char *eol, ch;
                int hit;
 
+               /*
+                * look_ahead() skips quicly to the line that possibly
+                * has the next hit; don't call it if we need to do
+                * something more than just skipping the current line
+                * in response to an unmatch for the current line.  E.g.
+                * inside a post-context window, we will show the current
+                * line as a context around the previous hit when it
+                * doesn't hit.
+                */
+               if (try_lookahead
+                   && !(last_hit
+                        && lno <= last_hit + opt->post_context)
+                   && look_ahead(opt, &left, &lno, &bol))
+                       break;
                eol = end_of_line(bol, &left);
                ch = *eol;
                *eol = 0;
@@ -686,7 +822,9 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
                        if (opt->status_only)
                                return 1;
                        if (binary_match_only) {
-                               printf("Binary file %s matches\n", name);
+                               opt->output(opt, "Binary file ", 12);
+                               opt->output(opt, name, strlen(name));
+                               opt->output(opt, " matches\n", 9);
                                return 1;
                        }
                        if (opt->name_only) {
@@ -742,9 +880,13 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
         * which feels mostly useless but sometimes useful.  Maybe
         * make it another option?  For now suppress them.
         */
-       if (opt->count && count)
-               printf("%s%c%u\n", name,
-                      opt->null_following_name ? '\0' : ':', count);
+       if (opt->count && count) {
+               char buf[32];
+               opt->output(opt, name, strlen(name));
+               snprintf(buf, sizeof(buf), "%c%u\n",
+                        opt->null_following_name ? '\0' : ':', count);
+               opt->output(opt, buf, strlen(buf));
+       }
        return !!last_hit;
 }
 
diff --git a/grep.h b/grep.h
index 75370f60d7091becd3eaddf3b7069c26fca8c125..970308799664fe6a3871c0b8364c13e43cf96e1f 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -85,13 +85,15 @@ struct grep_opt {
        int max_depth;
        int funcname;
        char color_match[COLOR_MAXLEN];
-       const char *color_external;
        int regflags;
        unsigned pre_context;
        unsigned post_context;
        unsigned last_shown;
        int show_hunk_mark;
        void *priv;
+
+       void (*output)(struct grep_opt *opt, const void *data, size_t size);
+       void *output_priv;
 };
 
 extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);
@@ -100,4 +102,7 @@ extern void compile_grep_patterns(struct grep_opt *opt);
 extern void free_grep_patterns(struct grep_opt *opt);
 extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
 
+extern struct grep_opt *grep_opt_dup(const struct grep_opt *opt);
+extern int grep_threads_ok(const struct grep_opt *opt);
+
 #endif
diff --git a/hash-object.c b/hash-object.c
deleted file mode 100644 (file)
index 9455dd0..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- * Copyright (C) Junio C Hamano, 2005
- */
-#include "cache.h"
-#include "blob.h"
-#include "quote.h"
-#include "parse-options.h"
-#include "exec_cmd.h"
-
-static void hash_fd(int fd, const char *type, int write_object, const char *path)
-{
-       struct stat st;
-       unsigned char sha1[20];
-       if (fstat(fd, &st) < 0 ||
-           index_fd(sha1, fd, &st, write_object, type_from_string(type), path))
-               die(write_object
-                   ? "Unable to add %s to database"
-                   : "Unable to hash %s", path);
-       printf("%s\n", sha1_to_hex(sha1));
-       maybe_flush_or_die(stdout, "hash to stdout");
-}
-
-static void hash_object(const char *path, const char *type, int write_object,
-                       const char *vpath)
-{
-       int fd;
-       fd = open(path, O_RDONLY);
-       if (fd < 0)
-               die_errno("Cannot open '%s'", path);
-       hash_fd(fd, type, write_object, vpath);
-}
-
-static void hash_stdin_paths(const char *type, int write_objects)
-{
-       struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
-
-       while (strbuf_getline(&buf, stdin, '\n') != EOF) {
-               if (buf.buf[0] == '"') {
-                       strbuf_reset(&nbuf);
-                       if (unquote_c_style(&nbuf, buf.buf, NULL))
-                               die("line is badly quoted");
-                       strbuf_swap(&buf, &nbuf);
-               }
-               hash_object(buf.buf, type, write_objects, buf.buf);
-       }
-       strbuf_release(&buf);
-       strbuf_release(&nbuf);
-}
-
-static const char * const hash_object_usage[] = {
-       "git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...",
-       "git hash-object  --stdin-paths < <list-of-paths>",
-       NULL
-};
-
-static const char *type;
-static int write_object;
-static int hashstdin;
-static int stdin_paths;
-static int no_filters;
-static const char *vpath;
-
-static const struct option hash_object_options[] = {
-       OPT_STRING('t', NULL, &type, "type", "object type"),
-       OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"),
-       OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"),
-       OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"),
-       OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"),
-       OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"),
-       OPT_END()
-};
-
-int main(int argc, const char **argv)
-{
-       int i;
-       const char *prefix = NULL;
-       int prefix_length = -1;
-       const char *errstr = NULL;
-
-       type = blob_type;
-
-       git_extract_argv0_path(argv[0]);
-
-       argc = parse_options(argc, argv, NULL, hash_object_options,
-                            hash_object_usage, 0);
-
-       if (write_object) {
-               prefix = setup_git_directory();
-               prefix_length = prefix ? strlen(prefix) : 0;
-               if (vpath && prefix)
-                       vpath = prefix_filename(prefix, prefix_length, vpath);
-       }
-
-       git_config(git_default_config, NULL);
-
-       if (stdin_paths) {
-               if (hashstdin)
-                       errstr = "Can't use --stdin-paths with --stdin";
-               else if (argc)
-                       errstr = "Can't specify files with --stdin-paths";
-               else if (vpath)
-                       errstr = "Can't use --stdin-paths with --path";
-               else if (no_filters)
-                       errstr = "Can't use --stdin-paths with --no-filters";
-       }
-       else {
-               if (hashstdin > 1)
-                       errstr = "Multiple --stdin arguments are not supported";
-               if (vpath && no_filters)
-                       errstr = "Can't use --path with --no-filters";
-       }
-
-       if (errstr) {
-               error("%s", errstr);
-               usage_with_options(hash_object_usage, hash_object_options);
-       }
-
-       if (hashstdin)
-               hash_fd(0, type, write_object, vpath);
-
-       for (i = 0 ; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (0 <= prefix_length)
-                       arg = prefix_filename(prefix, prefix_length, arg);
-               hash_object(arg, type, write_object,
-                           no_filters ? NULL : vpath ? vpath : arg);
-       }
-
-       if (stdin_paths)
-               hash_stdin_paths(type, write_object);
-
-       return 0;
-}
diff --git a/hex.c b/hex.c
new file mode 100644 (file)
index 0000000..bb402fb
--- /dev/null
+++ b/hex.c
@@ -0,0 +1,67 @@
+#include "cache.h"
+
+const signed char hexval_table[256] = {
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 00-07 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 08-0f */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 10-17 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 18-1f */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 20-27 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 28-2f */
+         0,  1,  2,  3,  4,  5,  6,  7,                /* 30-37 */
+         8,  9, -1, -1, -1, -1, -1, -1,                /* 38-3f */
+        -1, 10, 11, 12, 13, 14, 15, -1,                /* 40-47 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 48-4f */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 50-57 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 58-5f */
+        -1, 10, 11, 12, 13, 14, 15, -1,                /* 60-67 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 68-67 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 70-77 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 78-7f */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 80-87 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 88-8f */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 90-97 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* 98-9f */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* a0-a7 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* a8-af */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* b0-b7 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* b8-bf */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* c0-c7 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* c8-cf */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* d0-d7 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* d8-df */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* e0-e7 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* e8-ef */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* f0-f7 */
+        -1, -1, -1, -1, -1, -1, -1, -1,                /* f8-ff */
+};
+
+int get_sha1_hex(const char *hex, unsigned char *sha1)
+{
+       int i;
+       for (i = 0; i < 20; i++) {
+               unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
+               if (val & ~0xff)
+                       return -1;
+               *sha1++ = val;
+               hex += 2;
+       }
+       return 0;
+}
+
+char *sha1_to_hex(const unsigned char *sha1)
+{
+       static int bufno;
+       static char hexbuffer[4][50];
+       static const char hex[] = "0123456789abcdef";
+       char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
+       int i;
+
+       for (i = 0; i < 20; i++) {
+               unsigned int val = *sha1++;
+               *buf++ = hex[val >> 4];
+               *buf++ = hex[val & 0xf];
+       }
+       *buf = '\0';
+
+       return buffer;
+}
index f729488fc5f787c0f4997aacdd1bb14732d717f7..345c12b79064f23e0ae0a15781731b9a42272d83 100644 (file)
@@ -648,6 +648,9 @@ int main(int argc, char **argv)
        setup_path();
        if (!enter_repo(dir, 0))
                not_found("Not a git repository: '%s'", dir);
+       if (!getenv("GIT_HTTP_EXPORT_ALL") &&
+           access("git-daemon-export-ok", F_OK) )
+               not_found("Repository not exported: '%s'", dir);
 
        git_config(http_config, NULL);
        cmd->imp(cmd_arg);
diff --git a/http.c b/http.c
index ed6414a2aaa4e0f6cf7672a089f49060aad62bfb..deab59551dad9a0d2c2e86d75071fa561e4cbf1a 100644 (file)
--- a/http.c
+++ b/http.c
@@ -7,6 +7,12 @@ int active_requests;
 int http_is_verbose;
 size_t http_post_buffer = 16 * LARGE_PACKET_MAX;
 
+#if LIBCURL_VERSION_NUM >= 0x070a06
+#define LIBCURL_CAN_HANDLE_AUTH_ANY
+#endif
+
+static int min_curl_sessions = 1;
+static int curl_session_count;
 #ifdef USE_CURL_MULTI
 static int max_requests = -1;
 static CURLM *curlm;
@@ -152,6 +158,14 @@ static int http_options(const char *var, const char *value, void *cb)
                        ssl_cert_password_required = 1;
                return 0;
        }
+       if (!strcmp("http.minsessions", var)) {
+               min_curl_sessions = git_config_int(var, value);
+#ifndef USE_CURL_MULTI
+               if (min_curl_sessions > 1)
+                       min_curl_sessions = 1;
+#endif
+               return 0;
+       }
 #ifdef USE_CURL_MULTI
        if (!strcmp("http.maxrequests", var)) {
                max_requests = git_config_int(var, value);
@@ -230,6 +244,9 @@ static CURL *get_curl_handle(void)
 #if LIBCURL_VERSION_NUM >= 0x070907
        curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
 #endif
+#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
+       curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
+#endif
 
        init_curl_http_auth(result);
 
@@ -372,6 +389,7 @@ void http_init(struct remote *remote)
        if (curl_ssl_verify == -1)
                curl_ssl_verify = 1;
 
+       curl_session_count = 0;
 #ifdef USE_CURL_MULTI
        if (max_requests < 1)
                max_requests = DEFAULT_MAX_REQUESTS;
@@ -480,6 +498,7 @@ struct active_request_slot *get_active_slot(void)
 #else
                slot->curl = curl_easy_duphandle(curl_default);
 #endif
+               curl_session_count++;
        }
 
        active_requests++;
@@ -558,9 +577,11 @@ void fill_active_slots(void)
        }
 
        while (slot != NULL) {
-               if (!slot->in_use && slot->curl != NULL) {
+               if (!slot->in_use && slot->curl != NULL
+                       && curl_session_count > min_curl_sessions) {
                        curl_easy_cleanup(slot->curl);
                        slot->curl = NULL;
+                       curl_session_count--;
                }
                slot = slot->next;
        }
@@ -630,15 +651,16 @@ static void closedown_active_slot(struct active_request_slot *slot)
        slot->in_use = 0;
 }
 
-void release_active_slot(struct active_request_slot *slot)
+static void release_active_slot(struct active_request_slot *slot)
 {
        closedown_active_slot(slot);
-       if (slot->curl) {
+       if (slot->curl && curl_session_count > min_curl_sessions) {
 #ifdef USE_CURL_MULTI
                curl_multi_remove_handle(curlm, slot->curl);
 #endif
                curl_easy_cleanup(slot->curl);
                slot->curl = NULL;
+               curl_session_count--;
        }
 #ifdef USE_CURL_MULTI
        fill_active_slots();
@@ -812,7 +834,13 @@ int http_get_strbuf(const char *url, struct strbuf *result, int options)
        return http_request(url, result, HTTP_REQUEST_STRBUF, options);
 }
 
-int http_get_file(const char *url, const char *filename, int options)
+/*
+ * Downloads an url and stores the result in the given file.
+ *
+ * If a previous interrupted download is detected (i.e. a previous temporary
+ * file is still around) the download is resumed.
+ */
+static int http_get_file(const char *url, const char *filename, int options)
 {
        int ret;
        struct strbuf tmpfile = STRBUF_INIT;
@@ -1244,7 +1272,7 @@ int finish_http_object_request(struct http_object_request *freq)
        process_http_object_request(freq);
 
        if (freq->http_code == 416) {
-               fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
+               warning("requested range invalid; we may already have all the data.");
        } else if (freq->curl_result != CURLE_OK) {
                if (stat(freq->tmpfile, &st) == 0)
                        if (st.st_size == 0)
diff --git a/http.h b/http.h
index f828e1d8067aeff3036c14a1c43e333902e46bd6..5c9441c10ce708be426afe7424d63dcbb68a49e2 100644 (file)
--- a/http.h
+++ b/http.h
@@ -81,7 +81,6 @@ extern int start_active_slot(struct active_request_slot *slot);
 extern void run_active_slot(struct active_request_slot *slot);
 extern void finish_active_slot(struct active_request_slot *slot);
 extern void finish_all_active_slots(void);
-extern void release_active_slot(struct active_request_slot *slot);
 
 #ifdef USE_CURL_MULTI
 extern void fill_active_slots(void);
@@ -135,14 +134,6 @@ extern char *get_remote_object_url(const char *url, const char *hex,
  */
 int http_get_strbuf(const char *url, struct strbuf *result, int options);
 
-/*
- * Downloads an url and stores the result in the given file.
- *
- * If a previous interrupted download is detected (i.e. a previous temporary
- * file is still around) the download is resumed.
- */
-int http_get_file(const char *url, const char *filename, int options);
-
 /*
  * Prints an error message using error() containing url and curl_errorstr,
  * and returns ret.
diff --git a/ident.c b/ident.c
index 26409b2a1b191765706265c2aa6d2ae163ba5bab..9e2438826dfce158e04549933d5c588dd6abcf5c 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -85,10 +85,11 @@ static void setup_ident(void)
        if (!git_default_email[0]) {
                const char *email = getenv("EMAIL");
 
-               if (email && email[0])
+               if (email && email[0]) {
                        strlcpy(git_default_email, email,
                                sizeof(git_default_email));
-               else {
+                       user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
+               } else {
                        if (!pw)
                                pw = getpwuid(getuid());
                        if (!pw)
@@ -168,8 +169,6 @@ static int copy(char *buf, size_t size, int offset, const char *src)
        return offset;
 }
 
-static const char au_env[] = "GIT_AUTHOR_NAME";
-static const char co_env[] = "GIT_COMMITTER_NAME";
 static const char *env_hint =
 "\n"
 "*** Please tell me who you are.\n"
@@ -204,7 +203,7 @@ const char *fmt_ident(const char *name, const char *email,
 
                if ((warn_on_no_name || error_on_no_name) &&
                    name == git_default_name && env_hint) {
-                       fprintf(stderr, env_hint, au_env, co_env);
+                       fputs(env_hint, stderr);
                        env_hint = NULL; /* warn only once */
                }
                if (error_on_no_name)
@@ -251,11 +250,21 @@ const char *git_author_info(int flag)
 
 const char *git_committer_info(int flag)
 {
-       if (getenv("GIT_COMMITTER_NAME") &&
-           getenv("GIT_COMMITTER_EMAIL"))
-               user_ident_explicitly_given = 1;
+       if (getenv("GIT_COMMITTER_NAME"))
+               user_ident_explicitly_given |= IDENT_NAME_GIVEN;
+       if (getenv("GIT_COMMITTER_EMAIL"))
+               user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
        return fmt_ident(getenv("GIT_COMMITTER_NAME"),
                         getenv("GIT_COMMITTER_EMAIL"),
                         getenv("GIT_COMMITTER_DATE"),
                         flag);
 }
+
+int user_ident_sufficiently_given(void)
+{
+#ifndef WINDOWS
+       return (user_ident_explicitly_given & IDENT_MAIL_GIVEN);
+#else
+       return (user_ident_explicitly_given == IDENT_ALL_GIVEN);
+#endif
+}
index de8114bac010ef095cf9de17671566e248366dcb..51f371ba9f08637657ec9198cf3f16d0c0407232 100644 (file)
@@ -965,17 +965,13 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
        /* open connection to IMAP server */
 
        if (srvc->tunnel) {
-               const char *argv[4];
+               const char *argv[] = { srvc->tunnel, NULL };
                struct child_process tunnel = {0};
 
                imap_info("Starting tunnel '%s'... ", srvc->tunnel);
 
-               argv[0] = "sh";
-               argv[1] = "-c";
-               argv[2] = srvc->tunnel;
-               argv[3] = NULL;
-
                tunnel.argv = argv;
+               tunnel.use_shell = 1;
                tunnel.in = -1;
                tunnel.out = -1;
                if (start_command(&tunnel))
diff --git a/index-pack.c b/index-pack.c
deleted file mode 100644 (file)
index 190f372..0000000
+++ /dev/null
@@ -1,1047 +0,0 @@
-#include "cache.h"
-#include "delta.h"
-#include "pack.h"
-#include "csum-file.h"
-#include "blob.h"
-#include "commit.h"
-#include "tag.h"
-#include "tree.h"
-#include "progress.h"
-#include "fsck.h"
-#include "exec_cmd.h"
-
-static const char index_pack_usage[] =
-"git index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
-
-struct object_entry
-{
-       struct pack_idx_entry idx;
-       unsigned long size;
-       unsigned int hdr_size;
-       enum object_type type;
-       enum object_type real_type;
-};
-
-union delta_base {
-       unsigned char sha1[20];
-       off_t offset;
-};
-
-struct base_data {
-       struct base_data *base;
-       struct base_data *child;
-       struct object_entry *obj;
-       void *data;
-       unsigned long size;
-};
-
-/*
- * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
- * to memcmp() only the first 20 bytes.
- */
-#define UNION_BASE_SZ  20
-
-#define FLAG_LINK (1u<<20)
-#define FLAG_CHECKED (1u<<21)
-
-struct delta_entry
-{
-       union delta_base base;
-       int obj_no;
-};
-
-static struct object_entry *objects;
-static struct delta_entry *deltas;
-static struct base_data *base_cache;
-static size_t base_cache_used;
-static int nr_objects;
-static int nr_deltas;
-static int nr_resolved_deltas;
-
-static int from_stdin;
-static int strict;
-static int verbose;
-
-static struct progress *progress;
-
-/* We always read in 4kB chunks. */
-static unsigned char input_buffer[4096];
-static unsigned int input_offset, input_len;
-static off_t consumed_bytes;
-static git_SHA_CTX input_ctx;
-static uint32_t input_crc32;
-static int input_fd, output_fd, pack_fd;
-
-static int mark_link(struct object *obj, int type, void *data)
-{
-       if (!obj)
-               return -1;
-
-       if (type != OBJ_ANY && obj->type != type)
-               die("object type mismatch at %s", sha1_to_hex(obj->sha1));
-
-       obj->flags |= FLAG_LINK;
-       return 0;
-}
-
-/* The content of each linked object must have been checked
-   or it must be already present in the object database */
-static void check_object(struct object *obj)
-{
-       if (!obj)
-               return;
-
-       if (!(obj->flags & FLAG_LINK))
-               return;
-
-       if (!(obj->flags & FLAG_CHECKED)) {
-               unsigned long size;
-               int type = sha1_object_info(obj->sha1, &size);
-               if (type != obj->type || type <= 0)
-                       die("object of unexpected type");
-               obj->flags |= FLAG_CHECKED;
-               return;
-       }
-}
-
-static void check_objects(void)
-{
-       unsigned i, max;
-
-       max = get_max_object_index();
-       for (i = 0; i < max; i++)
-               check_object(get_indexed_object(i));
-}
-
-
-/* Discard current buffer used content. */
-static void flush(void)
-{
-       if (input_offset) {
-               if (output_fd >= 0)
-                       write_or_die(output_fd, input_buffer, input_offset);
-               git_SHA1_Update(&input_ctx, input_buffer, input_offset);
-               memmove(input_buffer, input_buffer + input_offset, input_len);
-               input_offset = 0;
-       }
-}
-
-/*
- * Make sure at least "min" bytes are available in the buffer, and
- * return the pointer to the buffer.
- */
-static void *fill(int min)
-{
-       if (min <= input_len)
-               return input_buffer + input_offset;
-       if (min > sizeof(input_buffer))
-               die("cannot fill %d bytes", min);
-       flush();
-       do {
-               ssize_t ret = xread(input_fd, input_buffer + input_len,
-                               sizeof(input_buffer) - input_len);
-               if (ret <= 0) {
-                       if (!ret)
-                               die("early EOF");
-                       die_errno("read error on input");
-               }
-               input_len += ret;
-               if (from_stdin)
-                       display_throughput(progress, consumed_bytes + input_len);
-       } while (input_len < min);
-       return input_buffer;
-}
-
-static void use(int bytes)
-{
-       if (bytes > input_len)
-               die("used more bytes than were available");
-       input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes);
-       input_len -= bytes;
-       input_offset += bytes;
-
-       /* make sure off_t is sufficiently large not to wrap */
-       if (consumed_bytes > consumed_bytes + bytes)
-               die("pack too large for current definition of off_t");
-       consumed_bytes += bytes;
-}
-
-static char *open_pack_file(char *pack_name)
-{
-       if (from_stdin) {
-               input_fd = 0;
-               if (!pack_name) {
-                       static char tmpfile[PATH_MAX];
-                       output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
-                                               "pack/tmp_pack_XXXXXX");
-                       pack_name = xstrdup(tmpfile);
-               } else
-                       output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
-               if (output_fd < 0)
-                       die_errno("unable to create '%s'", pack_name);
-               pack_fd = output_fd;
-       } else {
-               input_fd = open(pack_name, O_RDONLY);
-               if (input_fd < 0)
-                       die_errno("cannot open packfile '%s'", pack_name);
-               output_fd = -1;
-               pack_fd = input_fd;
-       }
-       git_SHA1_Init(&input_ctx);
-       return pack_name;
-}
-
-static void parse_pack_header(void)
-{
-       struct pack_header *hdr = fill(sizeof(struct pack_header));
-
-       /* Header consistency check */
-       if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
-               die("pack signature mismatch");
-       if (!pack_version_ok(hdr->hdr_version))
-               die("pack version %"PRIu32" unsupported",
-                       ntohl(hdr->hdr_version));
-
-       nr_objects = ntohl(hdr->hdr_entries);
-       use(sizeof(struct pack_header));
-}
-
-static NORETURN void bad_object(unsigned long offset, const char *format,
-                      ...) __attribute__((format (printf, 2, 3)));
-
-static void bad_object(unsigned long offset, const char *format, ...)
-{
-       va_list params;
-       char buf[1024];
-
-       va_start(params, format);
-       vsnprintf(buf, sizeof(buf), format, params);
-       va_end(params);
-       die("pack has bad object at offset %lu: %s", offset, buf);
-}
-
-static void free_base_data(struct base_data *c)
-{
-       if (c->data) {
-               free(c->data);
-               c->data = NULL;
-               base_cache_used -= c->size;
-       }
-}
-
-static void prune_base_data(struct base_data *retain)
-{
-       struct base_data *b;
-       for (b = base_cache;
-            base_cache_used > delta_base_cache_limit && b;
-            b = b->child) {
-               if (b->data && b != retain)
-                       free_base_data(b);
-       }
-}
-
-static void link_base_data(struct base_data *base, struct base_data *c)
-{
-       if (base)
-               base->child = c;
-       else
-               base_cache = c;
-
-       c->base = base;
-       c->child = NULL;
-       if (c->data)
-               base_cache_used += c->size;
-       prune_base_data(c);
-}
-
-static void unlink_base_data(struct base_data *c)
-{
-       struct base_data *base = c->base;
-       if (base)
-               base->child = NULL;
-       else
-               base_cache = NULL;
-       free_base_data(c);
-}
-
-static void *unpack_entry_data(unsigned long offset, unsigned long size)
-{
-       z_stream stream;
-       void *buf = xmalloc(size);
-
-       memset(&stream, 0, sizeof(stream));
-       stream.next_out = buf;
-       stream.avail_out = size;
-       stream.next_in = fill(1);
-       stream.avail_in = input_len;
-       git_inflate_init(&stream);
-
-       for (;;) {
-               int ret = git_inflate(&stream, 0);
-               use(input_len - stream.avail_in);
-               if (stream.total_out == size && ret == Z_STREAM_END)
-                       break;
-               if (ret != Z_OK)
-                       bad_object(offset, "inflate returned %d", ret);
-               stream.next_in = fill(1);
-               stream.avail_in = input_len;
-       }
-       git_inflate_end(&stream);
-       return buf;
-}
-
-static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
-{
-       unsigned char *p;
-       unsigned long size, c;
-       off_t base_offset;
-       unsigned shift;
-       void *data;
-
-       obj->idx.offset = consumed_bytes;
-       input_crc32 = crc32(0, Z_NULL, 0);
-
-       p = fill(1);
-       c = *p;
-       use(1);
-       obj->type = (c >> 4) & 7;
-       size = (c & 15);
-       shift = 4;
-       while (c & 0x80) {
-               p = fill(1);
-               c = *p;
-               use(1);
-               size += (c & 0x7f) << shift;
-               shift += 7;
-       }
-       obj->size = size;
-
-       switch (obj->type) {
-       case OBJ_REF_DELTA:
-               hashcpy(delta_base->sha1, fill(20));
-               use(20);
-               break;
-       case OBJ_OFS_DELTA:
-               memset(delta_base, 0, sizeof(*delta_base));
-               p = fill(1);
-               c = *p;
-               use(1);
-               base_offset = c & 127;
-               while (c & 128) {
-                       base_offset += 1;
-                       if (!base_offset || MSB(base_offset, 7))
-                               bad_object(obj->idx.offset, "offset value overflow for delta base object");
-                       p = fill(1);
-                       c = *p;
-                       use(1);
-                       base_offset = (base_offset << 7) + (c & 127);
-               }
-               delta_base->offset = obj->idx.offset - base_offset;
-               if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset)
-                       bad_object(obj->idx.offset, "delta base offset is out of bound");
-               break;
-       case OBJ_COMMIT:
-       case OBJ_TREE:
-       case OBJ_BLOB:
-       case OBJ_TAG:
-               break;
-       default:
-               bad_object(obj->idx.offset, "unknown object type %d", obj->type);
-       }
-       obj->hdr_size = consumed_bytes - obj->idx.offset;
-
-       data = unpack_entry_data(obj->idx.offset, obj->size);
-       obj->idx.crc32 = input_crc32;
-       return data;
-}
-
-static void *get_data_from_pack(struct object_entry *obj)
-{
-       off_t from = obj[0].idx.offset + obj[0].hdr_size;
-       unsigned long len = obj[1].idx.offset - from;
-       unsigned long rdy = 0;
-       unsigned char *src, *data;
-       z_stream stream;
-       int st;
-
-       src = xmalloc(len);
-       data = src;
-       do {
-               ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy);
-               if (n < 0)
-                       die_errno("cannot pread pack file");
-               if (!n)
-                       die("premature end of pack file, %lu bytes missing",
-                           len - rdy);
-               rdy += n;
-       } while (rdy < len);
-       data = xmalloc(obj->size);
-       memset(&stream, 0, sizeof(stream));
-       stream.next_out = data;
-       stream.avail_out = obj->size;
-       stream.next_in = src;
-       stream.avail_in = len;
-       git_inflate_init(&stream);
-       while ((st = git_inflate(&stream, Z_FINISH)) == Z_OK);
-       git_inflate_end(&stream);
-       if (st != Z_STREAM_END || stream.total_out != obj->size)
-               die("serious inflate inconsistency");
-       free(src);
-       return data;
-}
-
-static int find_delta(const union delta_base *base)
-{
-       int first = 0, last = nr_deltas;
-
-        while (first < last) {
-                int next = (first + last) / 2;
-                struct delta_entry *delta = &deltas[next];
-                int cmp;
-
-                cmp = memcmp(base, &delta->base, UNION_BASE_SZ);
-                if (!cmp)
-                        return next;
-                if (cmp < 0) {
-                        last = next;
-                        continue;
-                }
-                first = next+1;
-        }
-        return -first-1;
-}
-
-static void find_delta_children(const union delta_base *base,
-                               int *first_index, int *last_index)
-{
-       int first = find_delta(base);
-       int last = first;
-       int end = nr_deltas - 1;
-
-       if (first < 0) {
-               *first_index = 0;
-               *last_index = -1;
-               return;
-       }
-       while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
-               --first;
-       while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
-               ++last;
-       *first_index = first;
-       *last_index = last;
-}
-
-static void sha1_object(const void *data, unsigned long size,
-                       enum object_type type, unsigned char *sha1)
-{
-       hash_sha1_file(data, size, typename(type), sha1);
-       if (has_sha1_file(sha1)) {
-               void *has_data;
-               enum object_type has_type;
-               unsigned long has_size;
-               has_data = read_sha1_file(sha1, &has_type, &has_size);
-               if (!has_data)
-                       die("cannot read existing object %s", sha1_to_hex(sha1));
-               if (size != has_size || type != has_type ||
-                   memcmp(data, has_data, size) != 0)
-                       die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
-               free(has_data);
-       }
-       if (strict) {
-               if (type == OBJ_BLOB) {
-                       struct blob *blob = lookup_blob(sha1);
-                       if (blob)
-                               blob->object.flags |= FLAG_CHECKED;
-                       else
-                               die("invalid blob object %s", sha1_to_hex(sha1));
-               } else {
-                       struct object *obj;
-                       int eaten;
-                       void *buf = (void *) data;
-
-                       /*
-                        * we do not need to free the memory here, as the
-                        * buf is deleted by the caller.
-                        */
-                       obj = parse_object_buffer(sha1, type, size, buf, &eaten);
-                       if (!obj)
-                               die("invalid %s", typename(type));
-                       if (fsck_object(obj, 1, fsck_error_function))
-                               die("Error in object");
-                       if (fsck_walk(obj, mark_link, NULL))
-                               die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1));
-
-                       if (obj->type == OBJ_TREE) {
-                               struct tree *item = (struct tree *) obj;
-                               item->buffer = NULL;
-                       }
-                       if (obj->type == OBJ_COMMIT) {
-                               struct commit *commit = (struct commit *) obj;
-                               commit->buffer = NULL;
-                       }
-                       obj->flags |= FLAG_CHECKED;
-               }
-       }
-}
-
-static void *get_base_data(struct base_data *c)
-{
-       if (!c->data) {
-               struct object_entry *obj = c->obj;
-
-               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
-                       void *base = get_base_data(c->base);
-                       void *raw = get_data_from_pack(obj);
-                       c->data = patch_delta(
-                               base, c->base->size,
-                               raw, obj->size,
-                               &c->size);
-                       free(raw);
-                       if (!c->data)
-                               bad_object(obj->idx.offset, "failed to apply delta");
-               } else {
-                       c->data = get_data_from_pack(obj);
-                       c->size = obj->size;
-               }
-
-               base_cache_used += c->size;
-               prune_base_data(c);
-       }
-       return c->data;
-}
-
-static void resolve_delta(struct object_entry *delta_obj,
-                         struct base_data *base, struct base_data *result)
-{
-       void *base_data, *delta_data;
-
-       delta_obj->real_type = base->obj->real_type;
-       delta_data = get_data_from_pack(delta_obj);
-       base_data = get_base_data(base);
-       result->obj = delta_obj;
-       result->data = patch_delta(base_data, base->size,
-                                  delta_data, delta_obj->size, &result->size);
-       free(delta_data);
-       if (!result->data)
-               bad_object(delta_obj->idx.offset, "failed to apply delta");
-       sha1_object(result->data, result->size, delta_obj->real_type,
-                   delta_obj->idx.sha1);
-       nr_resolved_deltas++;
-}
-
-static void find_unresolved_deltas(struct base_data *base,
-                                  struct base_data *prev_base)
-{
-       int i, ref_first, ref_last, ofs_first, ofs_last;
-
-       /*
-        * This is a recursive function. Those brackets should help reducing
-        * stack usage by limiting the scope of the delta_base union.
-        */
-       {
-               union delta_base base_spec;
-
-               hashcpy(base_spec.sha1, base->obj->idx.sha1);
-               find_delta_children(&base_spec, &ref_first, &ref_last);
-
-               memset(&base_spec, 0, sizeof(base_spec));
-               base_spec.offset = base->obj->idx.offset;
-               find_delta_children(&base_spec, &ofs_first, &ofs_last);
-       }
-
-       if (ref_last == -1 && ofs_last == -1) {
-               free(base->data);
-               return;
-       }
-
-       link_base_data(prev_base, base);
-
-       for (i = ref_first; i <= ref_last; i++) {
-               struct object_entry *child = objects + deltas[i].obj_no;
-               if (child->real_type == OBJ_REF_DELTA) {
-                       struct base_data result;
-                       resolve_delta(child, base, &result);
-                       if (i == ref_last && ofs_last == -1)
-                               free_base_data(base);
-                       find_unresolved_deltas(&result, base);
-               }
-       }
-
-       for (i = ofs_first; i <= ofs_last; i++) {
-               struct object_entry *child = objects + deltas[i].obj_no;
-               if (child->real_type == OBJ_OFS_DELTA) {
-                       struct base_data result;
-                       resolve_delta(child, base, &result);
-                       if (i == ofs_last)
-                               free_base_data(base);
-                       find_unresolved_deltas(&result, base);
-               }
-       }
-
-       unlink_base_data(base);
-}
-
-static int compare_delta_entry(const void *a, const void *b)
-{
-       const struct delta_entry *delta_a = a;
-       const struct delta_entry *delta_b = b;
-       return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ);
-}
-
-/* Parse all objects and return the pack content SHA1 hash */
-static void parse_pack_objects(unsigned char *sha1)
-{
-       int i;
-       struct delta_entry *delta = deltas;
-       struct stat st;
-
-       /*
-        * First pass:
-        * - find locations of all objects;
-        * - calculate SHA1 of all non-delta objects;
-        * - remember base (SHA1 or offset) for all deltas.
-        */
-       if (verbose)
-               progress = start_progress(
-                               from_stdin ? "Receiving objects" : "Indexing objects",
-                               nr_objects);
-       for (i = 0; i < nr_objects; i++) {
-               struct object_entry *obj = &objects[i];
-               void *data = unpack_raw_entry(obj, &delta->base);
-               obj->real_type = obj->type;
-               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
-                       nr_deltas++;
-                       delta->obj_no = i;
-                       delta++;
-               } else
-                       sha1_object(data, obj->size, obj->type, obj->idx.sha1);
-               free(data);
-               display_progress(progress, i+1);
-       }
-       objects[i].idx.offset = consumed_bytes;
-       stop_progress(&progress);
-
-       /* Check pack integrity */
-       flush();
-       git_SHA1_Final(sha1, &input_ctx);
-       if (hashcmp(fill(20), sha1))
-               die("pack is corrupted (SHA1 mismatch)");
-       use(20);
-
-       /* If input_fd is a file, we should have reached its end now. */
-       if (fstat(input_fd, &st))
-               die_errno("cannot fstat packfile");
-       if (S_ISREG(st.st_mode) &&
-                       lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
-               die("pack has junk at the end");
-
-       if (!nr_deltas)
-               return;
-
-       /* Sort deltas by base SHA1/offset for fast searching */
-       qsort(deltas, nr_deltas, sizeof(struct delta_entry),
-             compare_delta_entry);
-
-       /*
-        * Second pass:
-        * - for all non-delta objects, look if it is used as a base for
-        *   deltas;
-        * - if used as a base, uncompress the object and apply all deltas,
-        *   recursively checking if the resulting object is used as a base
-        *   for some more deltas.
-        */
-       if (verbose)
-               progress = start_progress("Resolving deltas", nr_deltas);
-       for (i = 0; i < nr_objects; i++) {
-               struct object_entry *obj = &objects[i];
-               struct base_data base_obj;
-
-               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
-                       continue;
-               base_obj.obj = obj;
-               base_obj.data = NULL;
-               find_unresolved_deltas(&base_obj, NULL);
-               display_progress(progress, nr_resolved_deltas);
-       }
-}
-
-static int write_compressed(struct sha1file *f, void *in, unsigned int size)
-{
-       z_stream stream;
-       unsigned long maxsize;
-       void *out;
-
-       memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, zlib_compression_level);
-       maxsize = deflateBound(&stream, size);
-       out = xmalloc(maxsize);
-
-       /* Compress it */
-       stream.next_in = in;
-       stream.avail_in = size;
-       stream.next_out = out;
-       stream.avail_out = maxsize;
-       while (deflate(&stream, Z_FINISH) == Z_OK);
-       deflateEnd(&stream);
-
-       size = stream.total_out;
-       sha1write(f, out, size);
-       free(out);
-       return size;
-}
-
-static struct object_entry *append_obj_to_pack(struct sha1file *f,
-                              const unsigned char *sha1, void *buf,
-                              unsigned long size, enum object_type type)
-{
-       struct object_entry *obj = &objects[nr_objects++];
-       unsigned char header[10];
-       unsigned long s = size;
-       int n = 0;
-       unsigned char c = (type << 4) | (s & 15);
-       s >>= 4;
-       while (s) {
-               header[n++] = c | 0x80;
-               c = s & 0x7f;
-               s >>= 7;
-       }
-       header[n++] = c;
-       crc32_begin(f);
-       sha1write(f, header, n);
-       obj[0].size = size;
-       obj[0].hdr_size = n;
-       obj[0].type = type;
-       obj[0].real_type = type;
-       obj[1].idx.offset = obj[0].idx.offset + n;
-       obj[1].idx.offset += write_compressed(f, buf, size);
-       obj[0].idx.crc32 = crc32_end(f);
-       sha1flush(f);
-       hashcpy(obj->idx.sha1, sha1);
-       return obj;
-}
-
-static int delta_pos_compare(const void *_a, const void *_b)
-{
-       struct delta_entry *a = *(struct delta_entry **)_a;
-       struct delta_entry *b = *(struct delta_entry **)_b;
-       return a->obj_no - b->obj_no;
-}
-
-static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
-{
-       struct delta_entry **sorted_by_pos;
-       int i, n = 0;
-
-       /*
-        * Since many unresolved deltas may well be themselves base objects
-        * for more unresolved deltas, we really want to include the
-        * smallest number of base objects that would cover as much delta
-        * as possible by picking the
-        * trunc deltas first, allowing for other deltas to resolve without
-        * additional base objects.  Since most base objects are to be found
-        * before deltas depending on them, a good heuristic is to start
-        * resolving deltas in the same order as their position in the pack.
-        */
-       sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
-       for (i = 0; i < nr_deltas; i++) {
-               if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
-                       continue;
-               sorted_by_pos[n++] = &deltas[i];
-       }
-       qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
-
-       for (i = 0; i < n; i++) {
-               struct delta_entry *d = sorted_by_pos[i];
-               enum object_type type;
-               struct base_data base_obj;
-
-               if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
-                       continue;
-               base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
-               if (!base_obj.data)
-                       continue;
-
-               if (check_sha1_signature(d->base.sha1, base_obj.data,
-                               base_obj.size, typename(type)))
-                       die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
-               base_obj.obj = append_obj_to_pack(f, d->base.sha1,
-                                       base_obj.data, base_obj.size, type);
-               find_unresolved_deltas(&base_obj, NULL);
-               display_progress(progress, nr_resolved_deltas);
-       }
-       free(sorted_by_pos);
-}
-
-static void final(const char *final_pack_name, const char *curr_pack_name,
-                 const char *final_index_name, const char *curr_index_name,
-                 const char *keep_name, const char *keep_msg,
-                 unsigned char *sha1)
-{
-       const char *report = "pack";
-       char name[PATH_MAX];
-       int err;
-
-       if (!from_stdin) {
-               close(input_fd);
-       } else {
-               fsync_or_die(output_fd, curr_pack_name);
-               err = close(output_fd);
-               if (err)
-                       die_errno("error while closing pack file");
-       }
-
-       if (keep_msg) {
-               int keep_fd, keep_msg_len = strlen(keep_msg);
-
-               if (!keep_name)
-                       keep_fd = odb_pack_keep(name, sizeof(name), sha1);
-               else
-                       keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
-
-               if (keep_fd < 0) {
-                       if (errno != EEXIST)
-                               die_errno("cannot write keep file '%s'",
-                                         keep_name);
-               } else {
-                       if (keep_msg_len > 0) {
-                               write_or_die(keep_fd, keep_msg, keep_msg_len);
-                               write_or_die(keep_fd, "\n", 1);
-                       }
-                       if (close(keep_fd) != 0)
-                               die_errno("cannot close written keep file '%s'",
-                                   keep_name);
-                       report = "keep";
-               }
-       }
-
-       if (final_pack_name != curr_pack_name) {
-               if (!final_pack_name) {
-                       snprintf(name, sizeof(name), "%s/pack/pack-%s.pack",
-                                get_object_directory(), sha1_to_hex(sha1));
-                       final_pack_name = name;
-               }
-               if (move_temp_to_file(curr_pack_name, final_pack_name))
-                       die("cannot store pack file");
-       } else if (from_stdin)
-               chmod(final_pack_name, 0444);
-
-       if (final_index_name != curr_index_name) {
-               if (!final_index_name) {
-                       snprintf(name, sizeof(name), "%s/pack/pack-%s.idx",
-                                get_object_directory(), sha1_to_hex(sha1));
-                       final_index_name = name;
-               }
-               if (move_temp_to_file(curr_index_name, final_index_name))
-                       die("cannot store index file");
-       } else
-               chmod(final_index_name, 0444);
-
-       if (!from_stdin) {
-               printf("%s\n", sha1_to_hex(sha1));
-       } else {
-               char buf[48];
-               int len = snprintf(buf, sizeof(buf), "%s\t%s\n",
-                                  report, sha1_to_hex(sha1));
-               write_or_die(1, buf, len);
-
-               /*
-                * Let's just mimic git-unpack-objects here and write
-                * the last part of the input buffer to stdout.
-                */
-               while (input_len) {
-                       err = xwrite(1, input_buffer + input_offset, input_len);
-                       if (err <= 0)
-                               break;
-                       input_len -= err;
-                       input_offset += err;
-               }
-       }
-}
-
-static int git_index_pack_config(const char *k, const char *v, void *cb)
-{
-       if (!strcmp(k, "pack.indexversion")) {
-               pack_idx_default_version = git_config_int(k, v);
-               if (pack_idx_default_version > 2)
-                       die("bad pack.indexversion=%"PRIu32,
-                               pack_idx_default_version);
-               return 0;
-       }
-       return git_default_config(k, v, cb);
-}
-
-int main(int argc, char **argv)
-{
-       int i, fix_thin_pack = 0;
-       char *curr_pack, *pack_name = NULL;
-       char *curr_index, *index_name = NULL;
-       const char *keep_name = NULL, *keep_msg = NULL;
-       char *index_name_buf = NULL, *keep_name_buf = NULL;
-       struct pack_idx_entry **idx_objects;
-       unsigned char pack_sha1[20];
-
-       git_extract_argv0_path(argv[0]);
-
-       if (argc == 2 && !strcmp(argv[1], "-h"))
-               usage(index_pack_usage);
-
-       /*
-        * We wish to read the repository's config file if any, and
-        * for that it is necessary to call setup_git_directory_gently().
-        * However if the cwd was inside .git/objects/pack/ then we need
-        * to go back there or all the pack name arguments will be wrong.
-        * And in that case we cannot rely on any prefix returned by
-        * setup_git_directory_gently() either.
-        */
-       {
-               char cwd[PATH_MAX+1];
-               int nongit;
-
-               if (!getcwd(cwd, sizeof(cwd)-1))
-                       die("Unable to get current working directory");
-               setup_git_directory_gently(&nongit);
-               git_config(git_index_pack_config, NULL);
-               if (chdir(cwd))
-                       die("Cannot come back to cwd");
-       }
-
-       for (i = 1; i < argc; i++) {
-               char *arg = argv[i];
-
-               if (*arg == '-') {
-                       if (!strcmp(arg, "--stdin")) {
-                               from_stdin = 1;
-                       } else if (!strcmp(arg, "--fix-thin")) {
-                               fix_thin_pack = 1;
-                       } else if (!strcmp(arg, "--strict")) {
-                               strict = 1;
-                       } else if (!strcmp(arg, "--keep")) {
-                               keep_msg = "";
-                       } else if (!prefixcmp(arg, "--keep=")) {
-                               keep_msg = arg + 7;
-                       } else if (!prefixcmp(arg, "--pack_header=")) {
-                               struct pack_header *hdr;
-                               char *c;
-
-                               hdr = (struct pack_header *)input_buffer;
-                               hdr->hdr_signature = htonl(PACK_SIGNATURE);
-                               hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
-                               if (*c != ',')
-                                       die("bad %s", arg);
-                               hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
-                               if (*c)
-                                       die("bad %s", arg);
-                               input_len = sizeof(*hdr);
-                       } else if (!strcmp(arg, "-v")) {
-                               verbose = 1;
-                       } else if (!strcmp(arg, "-o")) {
-                               if (index_name || (i+1) >= argc)
-                                       usage(index_pack_usage);
-                               index_name = argv[++i];
-                       } else if (!prefixcmp(arg, "--index-version=")) {
-                               char *c;
-                               pack_idx_default_version = strtoul(arg + 16, &c, 10);
-                               if (pack_idx_default_version > 2)
-                                       die("bad %s", arg);
-                               if (*c == ',')
-                                       pack_idx_off32_limit = strtoul(c+1, &c, 0);
-                               if (*c || pack_idx_off32_limit & 0x80000000)
-                                       die("bad %s", arg);
-                       } else
-                               usage(index_pack_usage);
-                       continue;
-               }
-
-               if (pack_name)
-                       usage(index_pack_usage);
-               pack_name = arg;
-       }
-
-       if (!pack_name && !from_stdin)
-               usage(index_pack_usage);
-       if (fix_thin_pack && !from_stdin)
-               die("--fix-thin cannot be used without --stdin");
-       if (!index_name && pack_name) {
-               int len = strlen(pack_name);
-               if (!has_extension(pack_name, ".pack"))
-                       die("packfile name '%s' does not end with '.pack'",
-                           pack_name);
-               index_name_buf = xmalloc(len);
-               memcpy(index_name_buf, pack_name, len - 5);
-               strcpy(index_name_buf + len - 5, ".idx");
-               index_name = index_name_buf;
-       }
-       if (keep_msg && !keep_name && pack_name) {
-               int len = strlen(pack_name);
-               if (!has_extension(pack_name, ".pack"))
-                       die("packfile name '%s' does not end with '.pack'",
-                           pack_name);
-               keep_name_buf = xmalloc(len);
-               memcpy(keep_name_buf, pack_name, len - 5);
-               strcpy(keep_name_buf + len - 5, ".keep");
-               keep_name = keep_name_buf;
-       }
-
-       curr_pack = open_pack_file(pack_name);
-       parse_pack_header();
-       objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
-       deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
-       parse_pack_objects(pack_sha1);
-       if (nr_deltas == nr_resolved_deltas) {
-               stop_progress(&progress);
-               /* Flush remaining pack final 20-byte SHA1. */
-               flush();
-       } else {
-               if (fix_thin_pack) {
-                       struct sha1file *f;
-                       unsigned char read_sha1[20], tail_sha1[20];
-                       char msg[48];
-                       int nr_unresolved = nr_deltas - nr_resolved_deltas;
-                       int nr_objects_initial = nr_objects;
-                       if (nr_unresolved <= 0)
-                               die("confusion beyond insanity");
-                       objects = xrealloc(objects,
-                                          (nr_objects + nr_unresolved + 1)
-                                          * sizeof(*objects));
-                       f = sha1fd(output_fd, curr_pack);
-                       fix_unresolved_deltas(f, nr_unresolved);
-                       sprintf(msg, "completed with %d local objects",
-                               nr_objects - nr_objects_initial);
-                       stop_progress_msg(&progress, msg);
-                       sha1close(f, tail_sha1, 0);
-                       hashcpy(read_sha1, pack_sha1);
-                       fixup_pack_header_footer(output_fd, pack_sha1,
-                                                curr_pack, nr_objects,
-                                                read_sha1, consumed_bytes-20);
-                       if (hashcmp(read_sha1, tail_sha1) != 0)
-                               die("Unexpected tail checksum for %s "
-                                   "(disk corruption?)", curr_pack);
-               }
-               if (nr_deltas != nr_resolved_deltas)
-                       die("pack has %d unresolved deltas",
-                           nr_deltas - nr_resolved_deltas);
-       }
-       free(deltas);
-       if (strict)
-               check_objects();
-
-       idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
-       for (i = 0; i < nr_objects; i++)
-               idx_objects[i] = &objects[i].idx;
-       curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1);
-       free(idx_objects);
-
-       final(pack_name, curr_pack,
-               index_name, curr_index,
-               keep_name, keep_msg,
-               pack_sha1);
-       free(objects);
-       free(index_name_buf);
-       free(keep_name_buf);
-       if (pack_name == NULL)
-               free(curr_pack);
-       if (index_name == NULL)
-               free(curr_index);
-
-       return 0;
-}
index 2d6b6d6cb1d2bc2d334bf058feb3444e94b5a781..4c7f11ba84c67089dce7d725d87a4dd32a245c7f 100644 (file)
@@ -18,7 +18,8 @@ typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
                           mmfile_t *orig,
                           mmfile_t *src1, const char *name1,
                           mmfile_t *src2, const char *name2,
-                          int virtual_ancestor);
+                          int flag,
+                          int marker_size);
 
 struct ll_merge_driver {
        const char *name;
@@ -38,14 +39,14 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
                           mmfile_t *orig,
                           mmfile_t *src1, const char *name1,
                           mmfile_t *src2, const char *name2,
-                          int virtual_ancestor)
+                          int flag, int marker_size)
 {
        /*
         * The tentative merge result is "ours" for the final round,
         * or common ancestor for an internal merge.  Still return
         * "conflicted merge" status.
         */
-       mmfile_t *stolen = virtual_ancestor ? orig : src1;
+       mmfile_t *stolen = (flag & 01) ? orig : src1;
 
        result->ptr = stolen->ptr;
        result->size = stolen->size;
@@ -59,10 +60,11 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
                        mmfile_t *orig,
                        mmfile_t *src1, const char *name1,
                        mmfile_t *src2, const char *name2,
-                       int virtual_ancestor)
+                       int flag, int marker_size)
 {
-       xpparam_t xpp;
+       xmparam_t xmp;
        int style = 0;
+       int favor = (flag >> 1) & 03;
 
        if (buffer_is_binary(orig->ptr, orig->size) ||
            buffer_is_binary(src1->ptr, src1->size) ||
@@ -73,16 +75,18 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
                                       path,
                                       orig, src1, name1,
                                       src2, name2,
-                                      virtual_ancestor);
+                                      flag, marker_size);
        }
 
-       memset(&xpp, 0, sizeof(xpp));
+       memset(&xmp, 0, sizeof(xmp));
        if (git_xmerge_style >= 0)
                style = git_xmerge_style;
+       if (marker_size > 0)
+               xmp.marker_size = marker_size;
        return xdl_merge(orig,
                         src1, name1,
                         src2, name2,
-                        &xpp, XDL_MERGE_ZEALOUS | style,
+                        &xmp, XDL_MERGE_FLAGS(XDL_MERGE_ZEALOUS, style, favor),
                         result);
 }
 
@@ -92,11 +96,10 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
                          mmfile_t *orig,
                          mmfile_t *src1, const char *name1,
                          mmfile_t *src2, const char *name2,
-                         int virtual_ancestor)
+                         int flag, int marker_size)
 {
        char *src, *dst;
        long size;
-       const int marker_size = 7;
        int status, saved_style;
 
        /* We have to force the RCS "merge" style */
@@ -104,7 +107,7 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
        git_xmerge_style = 0;
        status = ll_xdl_merge(drv_unused, result, path_unused,
                              orig, src1, NULL, src2, NULL,
-                             virtual_ancestor);
+                             flag, marker_size);
        git_xmerge_style = saved_style;
        if (status <= 0)
                return status;
@@ -165,17 +168,18 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
                        mmfile_t *orig,
                        mmfile_t *src1, const char *name1,
                        mmfile_t *src2, const char *name2,
-                       int virtual_ancestor)
+                       int flag, int marker_size)
 {
-       char temp[3][50];
+       char temp[4][50];
        struct strbuf cmd = STRBUF_INIT;
        struct strbuf_expand_dict_entry dict[] = {
                { "O", temp[0] },
                { "A", temp[1] },
                { "B", temp[2] },
+               { "L", temp[3] },
                { NULL }
        };
-       const char *args[] = { "sh", "-c", NULL, NULL };
+       const char *args[] = { NULL, NULL };
        int status, fd, i;
        struct stat st;
 
@@ -187,11 +191,12 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
        create_temp(orig, temp[0]);
        create_temp(src1, temp[1]);
        create_temp(src2, temp[2]);
+       sprintf(temp[3], "%d", marker_size);
 
        strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict);
 
-       args[2] = cmd.buf;
-       status = run_command_v_opt(args, 0);
+       args[0] = cmd.buf;
+       status = run_command_v_opt(args, RUN_USING_SHELL);
        fd = open(temp[1], O_RDONLY);
        if (fd < 0)
                goto bad;
@@ -279,6 +284,7 @@ static int read_merge_config(const char *var, const char *value, void *cb)
                 *    %O - temporary file name for the merge base.
                 *    %A - temporary file name for our version.
                 *    %B - temporary file name for the other branches' version.
+                *    %L - conflict marker length
                 *
                 * The external merge driver should write the results in the
                 * file named by %A, and signal that it has done with zero exit
@@ -339,16 +345,13 @@ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr
        return &ll_merge_drv[LL_TEXT_MERGE];
 }
 
-static const char *git_path_check_merge(const char *path)
+static int git_path_check_merge(const char *path, struct git_attr_check check[2])
 {
-       static struct git_attr_check attr_merge_check;
-
-       if (!attr_merge_check.attr)
-               attr_merge_check.attr = git_attr("merge", 5);
-
-       if (git_checkattr(path, 1, &attr_merge_check))
-               return NULL;
-       return attr_merge_check.value;
+       if (!check[0].attr) {
+               check[0].attr = git_attr("merge");
+               check[1].attr = git_attr("conflict-marker-size");
+       }
+       return git_checkattr(path, 2, check);
 }
 
 int ll_merge(mmbuffer_t *result_buf,
@@ -356,18 +359,41 @@ int ll_merge(mmbuffer_t *result_buf,
             mmfile_t *ancestor,
             mmfile_t *ours, const char *our_label,
             mmfile_t *theirs, const char *their_label,
-            int virtual_ancestor)
+            int flag)
 {
-       const char *ll_driver_name;
+       static struct git_attr_check check[2];
+       const char *ll_driver_name = NULL;
+       int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
        const struct ll_merge_driver *driver;
-
-       ll_driver_name = git_path_check_merge(path);
+       int virtual_ancestor = flag & 01;
+
+       if (!git_path_check_merge(path, check)) {
+               ll_driver_name = check[0].value;
+               if (check[1].value) {
+                       marker_size = atoi(check[1].value);
+                       if (marker_size <= 0)
+                               marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+               }
+       }
        driver = find_ll_merge_driver(ll_driver_name);
-
        if (virtual_ancestor && driver->recursive)
                driver = find_ll_merge_driver(driver->recursive);
-       return driver->fn(driver, result_buf, path,
-                         ancestor,
-                         ours, our_label,
-                         theirs, their_label, virtual_ancestor);
+       return driver->fn(driver, result_buf, path, ancestor,
+                         ours, our_label, theirs, their_label,
+                         flag, marker_size);
+}
+
+int ll_merge_marker_size(const char *path)
+{
+       static struct git_attr_check check;
+       int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+
+       if (!check.attr)
+               check.attr = git_attr("conflict-marker-size");
+       if (!git_checkattr(path, 1, &check) && check.value) {
+               marker_size = atoi(check.value);
+               if (marker_size <= 0)
+                       marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+       }
+       return marker_size;
 }
index 5388422d091ede134d42406291989c49553f7428..57889227b1782d3792be2046fbb54bca67b779de 100644 (file)
@@ -10,6 +10,8 @@ int ll_merge(mmbuffer_t *result_buf,
             mmfile_t *ancestor,
             mmfile_t *ours, const char *our_label,
             mmfile_t *theirs, const char *their_label,
-            int virtual_ancestor);
+            int flag);
+
+int ll_merge_marker_size(const char *path);
 
 #endif
index 6851fa55a503bd24dd204934d7433e306996ce09..b0d74cdddee2407de590cb9f725e8e24093d9d35 100644 (file)
@@ -164,9 +164,10 @@ static char *unable_to_lock_message(const char *path, int err)
                    "If no other git process is currently running, this probably means a\n"
                    "git process crashed in this repository earlier. Make sure no other git\n"
                    "process is running and remove the file manually to continue.",
-                   path, strerror(err));
+                           make_nonrelative_path(path), strerror(err));
        } else
-               strbuf_addf(&buf, "Unable to create '%s.lock': %s", path, strerror(err));
+               strbuf_addf(&buf, "Unable to create '%s.lock': %s",
+                           make_nonrelative_path(path), strerror(err));
        return strbuf_detach(&buf, NULL);
 }
 
index f167c005bf9039f44d21ef5f32ab27db7784d642..b68c1fec9c2ca69f313bc3fa16461c6c9150c53d 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
@@ -243,8 +243,3 @@ int map_user(struct string_list *map,
        debug_mm("map_user:  --\n");
        return 0;
 }
-
-int map_email(struct string_list *map, const char *email, char *name, int maxlen)
-{
-       return map_user(map, (char *)email, 0, name, maxlen);
-}
index 4b2ca3a7de972c10f214b38a25be522abcbbafd0..d5c3664322023d6552cb24006d833aa08224ff6f 100644 (file)
--- a/mailmap.h
+++ b/mailmap.h
@@ -4,7 +4,6 @@
 int read_mailmap(struct string_list *map, char **repo_abbrev);
 void clear_mailmap(struct string_list *map);
 
-int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen);
 int map_user(struct string_list *mailmap,
             char *email, int maxlen_email, char *name, int maxlen_name);
 
index 0fd6df7d6ed839eaed536bc332312c2688a6bbad..26f7ed143e0649b6377ea427b5c57ca7ddb663d3 100644 (file)
@@ -185,7 +185,7 @@ static void match_trees(const unsigned char *hash1,
  * tree object by replacing it with another tree "hash2".
  */
 static int splice_tree(const unsigned char *hash1,
-                      char *prefix,
+                      const char *prefix,
                       const unsigned char *hash2,
                       unsigned char *result)
 {
@@ -264,6 +264,13 @@ void shift_tree(const unsigned char *hash1,
        char *del_prefix;
        int add_score, del_score;
 
+       /*
+        * NEEDSWORK: this limits the recursion depth to hardcoded
+        * value '2' to avoid excessive overhead.
+        */
+       if (!depth_limit)
+               depth_limit = 2;
+
        add_score = del_score = score_trees(hash1, hash2);
        add_prefix = xcalloc(1, 1);
        del_prefix = xcalloc(1, 1);
@@ -301,3 +308,63 @@ void shift_tree(const unsigned char *hash1,
 
        splice_tree(hash1, add_prefix, hash2, shifted);
 }
+
+/*
+ * The user says the trees will be shifted by this much.
+ * Unfortunately we cannot fundamentally tell which one to
+ * be prefixed, as recursive merge can work in either direction.
+ */
+void shift_tree_by(const unsigned char *hash1,
+                  const unsigned char *hash2,
+                  unsigned char *shifted,
+                  const char *shift_prefix)
+{
+       unsigned char sub1[20], sub2[20];
+       unsigned mode1, mode2;
+       unsigned candidate = 0;
+
+       /* Can hash2 be a tree at shift_prefix in tree hash1? */
+       if (!get_tree_entry(hash1, shift_prefix, sub1, &mode1) &&
+           S_ISDIR(mode1))
+               candidate |= 1;
+
+       /* Can hash1 be a tree at shift_prefix in tree hash2? */
+       if (!get_tree_entry(hash2, shift_prefix, sub2, &mode2) &&
+           S_ISDIR(mode2))
+               candidate |= 2;
+
+       if (candidate == 3) {
+               /* Both are plausible -- we need to evaluate the score */
+               int best_score = score_trees(hash1, hash2);
+               int score;
+
+               candidate = 0;
+               score = score_trees(sub1, hash2);
+               if (score > best_score) {
+                       candidate = 1;
+                       best_score = score;
+               }
+               score = score_trees(sub2, hash1);
+               if (score > best_score)
+                       candidate = 2;
+       }
+
+       if (!candidate) {
+               /* Neither is plausible -- do not shift */
+               hashcpy(shifted, hash2);
+               return;
+       }
+
+       if (candidate == 1)
+               /*
+                * shift tree2 down by adding shift_prefix above it
+                * to match tree1.
+                */
+               splice_tree(hash1, shift_prefix, hash2, shifted);
+       else
+               /*
+                * shift tree2 up by removing shift_prefix from it
+                * to match tree1.
+                */
+               hashcpy(shifted, sub2);
+}
index 3120a95f786eadd4cb5d167facc15714904e9665..fd34d76e1516b2c944778a11a5670d382f245873 100644 (file)
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "run-command.h"
 #include "xdiff-interface.h"
+#include "ll-merge.h"
 #include "blob.h"
 
 static int fill_mmfile_blob(mmfile_t *f, struct blob *obj)
@@ -24,16 +25,13 @@ static void free_mmfile(mmfile_t *f)
        free(f->ptr);
 }
 
-static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size)
+static void *three_way_filemerge(const char *path, mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size)
 {
-       mmbuffer_t res;
-       xpparam_t xpp;
        int merge_status;
+       mmbuffer_t res;
 
-       memset(&xpp, 0, sizeof(xpp));
-       merge_status = xdl_merge(base, our, ".our", their, ".their",
-               &xpp, XDL_MERGE_ZEALOUS, &res);
-
+       merge_status = ll_merge(&res, path, base,
+                               our, ".our", their, ".their", 0);
        if (merge_status < 0)
                return NULL;
 
@@ -75,7 +73,7 @@ static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2)
        return xdi_diff(f1, f2, &xpp, &xecfg, &ecb);
 }
 
-void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsigned long *size)
+void *merge_file(const char *path, struct blob *base, struct blob *our, struct blob *their, unsigned long *size)
 {
        void *res = NULL;
        mmfile_t f1, f2, common;
@@ -108,7 +106,7 @@ void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsign
                if (generate_common_file(&common, &f1, &f2) < 0)
                        goto out_free_f2_f1;
        }
-       res = three_way_filemerge(&common, &f1, &f2, size);
+       res = three_way_filemerge(path, &common, &f1, &f2, size);
        free_mmfile(&common);
 out_free_f2_f1:
        free_mmfile(&f2);
diff --git a/merge-index.c b/merge-index.c
deleted file mode 100644 (file)
index 19ddd03..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-#include "cache.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-
-static const char *pgm;
-static int one_shot, quiet;
-static int err;
-
-static int merge_entry(int pos, const char *path)
-{
-       int found;
-       const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL };
-       char hexbuf[4][60];
-       char ownbuf[4][60];
-
-       if (pos >= active_nr)
-               die("git merge-index: %s not in the cache", path);
-       found = 0;
-       do {
-               struct cache_entry *ce = active_cache[pos];
-               int stage = ce_stage(ce);
-
-               if (strcmp(ce->name, path))
-                       break;
-               found++;
-               strcpy(hexbuf[stage], sha1_to_hex(ce->sha1));
-               sprintf(ownbuf[stage], "%o", ce->ce_mode);
-               arguments[stage] = hexbuf[stage];
-               arguments[stage + 4] = ownbuf[stage];
-       } while (++pos < active_nr);
-       if (!found)
-               die("git merge-index: %s not in the cache", path);
-
-       if (run_command_v_opt(arguments, 0)) {
-               if (one_shot)
-                       err++;
-               else {
-                       if (!quiet)
-                               die("merge program failed");
-                       exit(1);
-               }
-       }
-       return found;
-}
-
-static void merge_file(const char *path)
-{
-       int pos = cache_name_pos(path, strlen(path));
-
-       /*
-        * If it already exists in the cache as stage0, it's
-        * already merged and there is nothing to do.
-        */
-       if (pos < 0)
-               merge_entry(-pos-1, path);
-}
-
-static void merge_all(void)
-{
-       int i;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (!ce_stage(ce))
-                       continue;
-               i += merge_entry(i, ce->name)-1;
-       }
-}
-
-int main(int argc, char **argv)
-{
-       int i, force_file = 0;
-
-       /* Without this we cannot rely on waitpid() to tell
-        * what happened to our children.
-        */
-       signal(SIGCHLD, SIG_DFL);
-
-       if (argc < 3)
-               usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
-
-       git_extract_argv0_path(argv[0]);
-
-       setup_git_directory();
-       read_cache();
-
-       i = 1;
-       if (!strcmp(argv[i], "-o")) {
-               one_shot = 1;
-               i++;
-       }
-       if (!strcmp(argv[i], "-q")) {
-               quiet = 1;
-               i++;
-       }
-       pgm = argv[i++];
-       for (; i < argc; i++) {
-               char *arg = argv[i];
-               if (!force_file && *arg == '-') {
-                       if (!strcmp(arg, "--")) {
-                               force_file = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "-a")) {
-                               merge_all();
-                               continue;
-                       }
-                       die("git merge-index: unknown option %s", arg);
-               }
-               merge_file(arg);
-       }
-       if (err && !quiet)
-               die("merge program failed");
-       return err;
-}
index 22a31ed5ff5d1affd54da43769a3982ba2ec1b75..cb53b01c19159e66ef265bde7feceabebab42232 100644 (file)
 #include "merge-recursive.h"
 #include "dir.h"
 
-static struct tree *shift_tree_object(struct tree *one, struct tree *two)
+static struct tree *shift_tree_object(struct tree *one, struct tree *two,
+                                     const char *subtree_shift)
 {
        unsigned char shifted[20];
 
-       /*
-        * NEEDSWORK: this limits the recursion depth to hardcoded
-        * value '2' to avoid excessive overhead.
-        */
-       shift_tree(one->object.sha1, two->object.sha1, shifted, 2);
+       if (!*subtree_shift) {
+               shift_tree(one->object.sha1, two->object.sha1, shifted, 0);
+       } else {
+               shift_tree_by(one->object.sha1, two->object.sha1, shifted,
+                             subtree_shift);
+       }
        if (!hashcmp(two->object.sha1, shifted))
                return two;
        return lookup_tree(shifted);
@@ -625,6 +627,23 @@ static int merge_3way(struct merge_options *o,
        mmfile_t orig, src1, src2;
        char *name1, *name2;
        int merge_status;
+       int favor;
+
+       if (o->call_depth)
+               favor = 0;
+       else {
+               switch (o->recursive_variant) {
+               case MERGE_RECURSIVE_OURS:
+                       favor = XDL_MERGE_FAVOR_OURS;
+                       break;
+               case MERGE_RECURSIVE_THEIRS:
+                       favor = XDL_MERGE_FAVOR_THEIRS;
+                       break;
+               default:
+                       favor = 0;
+                       break;
+               }
+       }
 
        if (strcmp(a->path, b->path)) {
                name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
@@ -640,7 +659,7 @@ static int merge_3way(struct merge_options *o,
 
        merge_status = ll_merge(result_buf, a->path, &orig,
                                &src1, name1, &src2, name2,
-                               o->call_depth);
+                               (!!o->call_depth) | (favor << 1));
 
        free(name1);
        free(name2);
@@ -1201,9 +1220,9 @@ int merge_trees(struct merge_options *o,
 {
        int code, clean;
 
-       if (o->subtree_merge) {
-               merge = shift_tree_object(head, merge);
-               common = shift_tree_object(head, common);
+       if (o->subtree_shift) {
+               merge = shift_tree_object(head, merge, o->subtree_shift);
+               common = shift_tree_object(head, common, o->subtree_shift);
        }
 
        if (sha_eq(common->object.sha1, merge->object.sha1)) {
index d8bc7299ee3a70484b2436afd3799fe6f7608422..be8410ad1803bc10e5dbf74f39eecdfed53469b1 100644 (file)
@@ -6,7 +6,12 @@
 struct merge_options {
        const char *branch1;
        const char *branch2;
-       unsigned subtree_merge : 1;
+       enum {
+               MERGE_RECURSIVE_NORMAL = 0,
+               MERGE_RECURSIVE_OURS,
+               MERGE_RECURSIVE_THEIRS,
+       } recursive_variant;
+       const char *subtree_shift;
        unsigned buffer_output : 1;
        int verbosity;
        int diff_rename_limit;
diff --git a/merge-tree.c b/merge-tree.c
deleted file mode 100644 (file)
index f01e7c8..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-#include "cache.h"
-#include "tree-walk.h"
-#include "xdiff-interface.h"
-#include "blob.h"
-#include "exec_cmd.h"
-
-static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
-static int resolve_directories = 1;
-
-struct merge_list {
-       struct merge_list *next;
-       struct merge_list *link;        /* other stages for this object */
-
-       unsigned int stage : 2,
-                    flags : 30;
-       unsigned int mode;
-       const char *path;
-       struct blob *blob;
-};
-
-static struct merge_list *merge_result, **merge_result_end = &merge_result;
-
-static void add_merge_entry(struct merge_list *entry)
-{
-       *merge_result_end = entry;
-       merge_result_end = &entry->next;
-}
-
-static void merge_trees(struct tree_desc t[3], const char *base);
-
-static const char *explanation(struct merge_list *entry)
-{
-       switch (entry->stage) {
-       case 0:
-               return "merged";
-       case 3:
-               return "added in remote";
-       case 2:
-               if (entry->link)
-                       return "added in both";
-               return "added in local";
-       }
-
-       /* Existed in base */
-       entry = entry->link;
-       if (!entry)
-               return "removed in both";
-
-       if (entry->link)
-               return "changed in both";
-
-       if (entry->stage == 3)
-               return "removed in local";
-       return "removed in remote";
-}
-
-extern void *merge_file(struct blob *, struct blob *, struct blob *, unsigned long *);
-
-static void *result(struct merge_list *entry, unsigned long *size)
-{
-       enum object_type type;
-       struct blob *base, *our, *their;
-
-       if (!entry->stage)
-               return read_sha1_file(entry->blob->object.sha1, &type, size);
-       base = NULL;
-       if (entry->stage == 1) {
-               base = entry->blob;
-               entry = entry->link;
-       }
-       our = NULL;
-       if (entry && entry->stage == 2) {
-               our = entry->blob;
-               entry = entry->link;
-       }
-       their = NULL;
-       if (entry)
-               their = entry->blob;
-       return merge_file(base, our, their, size);
-}
-
-static void *origin(struct merge_list *entry, unsigned long *size)
-{
-       enum object_type type;
-       while (entry) {
-               if (entry->stage == 2)
-                       return read_sha1_file(entry->blob->object.sha1, &type, size);
-               entry = entry->link;
-       }
-       return NULL;
-}
-
-static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf)
-{
-       int i;
-       for (i = 0; i < nbuf; i++)
-               printf("%.*s", (int) mb[i].size, mb[i].ptr);
-       return 0;
-}
-
-static void show_diff(struct merge_list *entry)
-{
-       unsigned long size;
-       mmfile_t src, dst;
-       xpparam_t xpp;
-       xdemitconf_t xecfg;
-       xdemitcb_t ecb;
-
-       xpp.flags = XDF_NEED_MINIMAL;
-       memset(&xecfg, 0, sizeof(xecfg));
-       xecfg.ctxlen = 3;
-       ecb.outf = show_outf;
-       ecb.priv = NULL;
-
-       src.ptr = origin(entry, &size);
-       if (!src.ptr)
-               size = 0;
-       src.size = size;
-       dst.ptr = result(entry, &size);
-       if (!dst.ptr)
-               size = 0;
-       dst.size = size;
-       xdi_diff(&src, &dst, &xpp, &xecfg, &ecb);
-       free(src.ptr);
-       free(dst.ptr);
-}
-
-static void show_result_list(struct merge_list *entry)
-{
-       printf("%s\n", explanation(entry));
-       do {
-               struct merge_list *link = entry->link;
-               static const char *desc[4] = { "result", "base", "our", "their" };
-               printf("  %-6s %o %s %s\n", desc[entry->stage], entry->mode, sha1_to_hex(entry->blob->object.sha1), entry->path);
-               entry = link;
-       } while (entry);
-}
-
-static void show_result(void)
-{
-       struct merge_list *walk;
-
-       walk = merge_result;
-       while (walk) {
-               show_result_list(walk);
-               show_diff(walk);
-               walk = walk->next;
-       }
-}
-
-/* An empty entry never compares same, not even to another empty entry */
-static int same_entry(struct name_entry *a, struct name_entry *b)
-{
-       return  a->sha1 &&
-               b->sha1 &&
-               !hashcmp(a->sha1, b->sha1) &&
-               a->mode == b->mode;
-}
-
-static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path)
-{
-       struct merge_list *res = xcalloc(1, sizeof(*res));
-
-       res->stage = stage;
-       res->path = path;
-       res->mode = mode;
-       res->blob = lookup_blob(sha1);
-       return res;
-}
-
-static char *traverse_path(const struct traverse_info *info, const struct name_entry *n)
-{
-       char *path = xmalloc(traverse_path_len(info, n) + 1);
-       return make_traverse_path(path, info, n);
-}
-
-static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result)
-{
-       struct merge_list *orig, *final;
-       const char *path;
-
-       /* If it's already branch1, don't bother showing it */
-       if (!branch1)
-               return;
-
-       path = traverse_path(info, result);
-       orig = create_entry(2, branch1->mode, branch1->sha1, path);
-       final = create_entry(0, result->mode, result->sha1, path);
-
-       final->link = orig;
-
-       add_merge_entry(final);
-}
-
-static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3])
-{
-       char *newbase;
-       struct name_entry *p;
-       struct tree_desc t[3];
-       void *buf0, *buf1, *buf2;
-
-       if (!resolve_directories)
-               return 0;
-       p = n;
-       if (!p->mode) {
-               p++;
-               if (!p->mode)
-                       p++;
-       }
-       if (!S_ISDIR(p->mode))
-               return 0;
-       newbase = traverse_path(info, p);
-       buf0 = fill_tree_descriptor(t+0, n[0].sha1);
-       buf1 = fill_tree_descriptor(t+1, n[1].sha1);
-       buf2 = fill_tree_descriptor(t+2, n[2].sha1);
-       merge_trees(t, newbase);
-
-       free(buf0);
-       free(buf1);
-       free(buf2);
-       free(newbase);
-       return 1;
-}
-
-
-static struct merge_list *link_entry(unsigned stage, const struct traverse_info *info, struct name_entry *n, struct merge_list *entry)
-{
-       const char *path;
-       struct merge_list *link;
-
-       if (!n->mode)
-               return entry;
-       if (entry)
-               path = entry->path;
-       else
-               path = traverse_path(info, n);
-       link = create_entry(stage, n->mode, n->sha1, path);
-       link->link = entry;
-       return link;
-}
-
-static void unresolved(const struct traverse_info *info, struct name_entry n[3])
-{
-       struct merge_list *entry = NULL;
-
-       if (unresolved_directory(info, n))
-               return;
-
-       /*
-        * Do them in reverse order so that the resulting link
-        * list has the stages in order - link_entry adds new
-        * links at the front.
-        */
-       entry = link_entry(3, info, n + 2, entry);
-       entry = link_entry(2, info, n + 1, entry);
-       entry = link_entry(1, info, n + 0, entry);
-
-       add_merge_entry(entry);
-}
-
-/*
- * Merge two trees together (t[1] and t[2]), using a common base (t[0])
- * as the origin.
- *
- * This walks the (sorted) trees in lock-step, checking every possible
- * name. Note that directories automatically sort differently from other
- * files (see "base_name_compare"), so you'll never see file/directory
- * conflicts, because they won't ever compare the same.
- *
- * IOW, if a directory changes to a filename, it will automatically be
- * seen as the directory going away, and the filename being created.
- *
- * Think of this as a three-way diff.
- *
- * The output will be either:
- *  - successful merge
- *      "0 mode sha1 filename"
- *    NOTE NOTE NOTE! FIXME! We really really need to walk the index
- *    in parallel with this too!
- *
- *  - conflict:
- *     "1 mode sha1 filename"
- *     "2 mode sha1 filename"
- *     "3 mode sha1 filename"
- *    where not all of the 1/2/3 lines may exist, of course.
- *
- * The successful merge rules are the same as for the three-way merge
- * in git-read-tree.
- */
-static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info)
-{
-       /* Same in both? */
-       if (same_entry(entry+1, entry+2)) {
-               if (entry[0].sha1) {
-                       resolve(info, NULL, entry+1);
-                       return mask;
-               }
-       }
-
-       if (same_entry(entry+0, entry+1)) {
-               if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) {
-                       resolve(info, entry+1, entry+2);
-                       return mask;
-               }
-       }
-
-       if (same_entry(entry+0, entry+2)) {
-               if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) {
-                       resolve(info, NULL, entry+1);
-                       return mask;
-               }
-       }
-
-       unresolved(info, entry);
-       return mask;
-}
-
-static void merge_trees(struct tree_desc t[3], const char *base)
-{
-       struct traverse_info info;
-
-       setup_traverse_info(&info, base);
-       info.fn = threeway_callback;
-       traverse_trees(3, t, &info);
-}
-
-static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
-{
-       unsigned char sha1[20];
-       void *buf;
-
-       if (get_sha1(rev, sha1))
-               die("unknown rev %s", rev);
-       buf = fill_tree_descriptor(desc, sha1);
-       if (!buf)
-               die("%s is not a tree", rev);
-       return buf;
-}
-
-int main(int argc, char **argv)
-{
-       struct tree_desc t[3];
-       void *buf1, *buf2, *buf3;
-
-       if (argc != 4)
-               usage(merge_tree_usage);
-
-       git_extract_argv0_path(argv[0]);
-
-       setup_git_directory();
-
-       buf1 = get_tree_descriptor(t+0, argv[1]);
-       buf2 = get_tree_descriptor(t+1, argv[2]);
-       buf3 = get_tree_descriptor(t+2, argv[3]);
-       merge_trees(t, "");
-       free(buf1);
-       free(buf2);
-       free(buf3);
-
-       show_result();
-       return 0;
-}
diff --git a/mktag.c b/mktag.c
deleted file mode 100644 (file)
index a3b4270..0000000
--- a/mktag.c
+++ /dev/null
@@ -1,183 +0,0 @@
-#include "cache.h"
-#include "tag.h"
-#include "exec_cmd.h"
-
-/*
- * A signature file has a very simple fixed format: four lines
- * of "object <sha1>" + "type <typename>" + "tag <tagname>" +
- * "tagger <committer>", followed by a blank line, a free-form tag
- * message and a signature block that git itself doesn't care about,
- * but that can be verified with gpg or similar.
- *
- * The first four lines are guaranteed to be at least 83 bytes:
- * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
- * shortest possible type-line, "tag .\n" at 6 bytes is the shortest
- * single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is
- * the shortest possible tagger-line.
- */
-
-/*
- * We refuse to tag something we can't verify. Just because.
- */
-static int verify_object(const unsigned char *sha1, const char *expected_type)
-{
-       int ret = -1;
-       enum object_type type;
-       unsigned long size;
-       const unsigned char *repl;
-       void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl);
-
-       if (buffer) {
-               if (type == type_from_string(expected_type))
-                       ret = check_sha1_signature(repl, buffer, size, expected_type);
-               free(buffer);
-       }
-       return ret;
-}
-
-#ifdef NO_C99_FORMAT
-#define PD_FMT "%d"
-#else
-#define PD_FMT "%td"
-#endif
-
-static int verify_tag(char *buffer, unsigned long size)
-{
-       int typelen;
-       char type[20];
-       unsigned char sha1[20];
-       const char *object, *type_line, *tag_line, *tagger_line, *lb, *rb;
-       size_t len;
-
-       if (size < 84)
-               return error("wanna fool me ? you obviously got the size wrong !");
-
-       buffer[size] = 0;
-
-       /* Verify object line */
-       object = buffer;
-       if (memcmp(object, "object ", 7))
-               return error("char%d: does not start with \"object \"", 0);
-
-       if (get_sha1_hex(object + 7, sha1))
-               return error("char%d: could not get SHA1 hash", 7);
-
-       /* Verify type line */
-       type_line = object + 48;
-       if (memcmp(type_line - 1, "\ntype ", 6))
-               return error("char%d: could not find \"\\ntype \"", 47);
-
-       /* Verify tag-line */
-       tag_line = strchr(type_line, '\n');
-       if (!tag_line)
-               return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer);
-       tag_line++;
-       if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
-               return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer);
-
-       /* Get the actual type */
-       typelen = tag_line - type_line - strlen("type \n");
-       if (typelen >= sizeof(type))
-               return error("char" PD_FMT ": type too long", type_line+5 - buffer);
-
-       memcpy(type, type_line+5, typelen);
-       type[typelen] = 0;
-
-       /* Verify that the object matches */
-       if (verify_object(sha1, type))
-               return error("char%d: could not verify object %s", 7, sha1_to_hex(sha1));
-
-       /* Verify the tag-name: we don't allow control characters or spaces in it */
-       tag_line += 4;
-       for (;;) {
-               unsigned char c = *tag_line++;
-               if (c == '\n')
-                       break;
-               if (c > ' ')
-                       continue;
-               return error("char" PD_FMT ": could not verify tag name", tag_line - buffer);
-       }
-
-       /* Verify the tagger line */
-       tagger_line = tag_line;
-
-       if (memcmp(tagger_line, "tagger ", 7))
-               return error("char" PD_FMT ": could not find \"tagger \"",
-                       tagger_line - buffer);
-
-       /*
-        * Check for correct form for name and email
-        * i.e. " <" followed by "> " on _this_ line
-        * No angle brackets within the name or email address fields.
-        * No spaces within the email address field.
-        */
-       tagger_line += 7;
-       if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) ||
-               strpbrk(tagger_line, "<>\n") != lb+1 ||
-               strpbrk(lb+2, "><\n ") != rb)
-               return error("char" PD_FMT ": malformed tagger field",
-                       tagger_line - buffer);
-
-       /* Check for author name, at least one character, space is acceptable */
-       if (lb == tagger_line)
-               return error("char" PD_FMT ": missing tagger name",
-                       tagger_line - buffer);
-
-       /* timestamp, 1 or more digits followed by space */
-       tagger_line = rb + 2;
-       if (!(len = strspn(tagger_line, "0123456789")))
-               return error("char" PD_FMT ": missing tag timestamp",
-                       tagger_line - buffer);
-       tagger_line += len;
-       if (*tagger_line != ' ')
-               return error("char" PD_FMT ": malformed tag timestamp",
-                       tagger_line - buffer);
-       tagger_line++;
-
-       /* timezone, 5 digits [+-]hhmm, max. 1400 */
-       if (!((tagger_line[0] == '+' || tagger_line[0] == '-') &&
-             strspn(tagger_line+1, "0123456789") == 4 &&
-             tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400))
-               return error("char" PD_FMT ": malformed tag timezone",
-                       tagger_line - buffer);
-       tagger_line += 6;
-
-       /* Verify the blank line separating the header from the body */
-       if (*tagger_line != '\n')
-               return error("char" PD_FMT ": trailing garbage in tag header",
-                       tagger_line - buffer);
-
-       /* The actual stuff afterwards we don't care about.. */
-       return 0;
-}
-
-#undef PD_FMT
-
-int main(int argc, char **argv)
-{
-       struct strbuf buf = STRBUF_INIT;
-       unsigned char result_sha1[20];
-
-       if (argc != 1)
-               usage("git mktag < signaturefile");
-
-       git_extract_argv0_path(argv[0]);
-
-       setup_git_directory();
-
-       if (strbuf_read(&buf, 0, 4096) < 0) {
-               die_errno("could not read from stdin");
-       }
-
-       /* Verify it for some basic sanity: it needs to start with
-          "object <sha1>\ntype\ntagger " */
-       if (verify_tag(buf.buf, buf.len) < 0)
-               die("invalid tag signature file");
-
-       if (write_sha1_file(buf.buf, buf.len, tag_type, result_sha1) < 0)
-               die("unable to write tag file");
-
-       strbuf_release(&buf);
-       printf("%s\n", sha1_to_hex(result_sha1));
-       return 0;
-}
index fe8eaaf19f71b48d9acba83594d918fc4875f5c4..3ca92c4c4def46af10556dbe9b3f48774b9a4a35 100644 (file)
--- a/object.c
+++ b/object.c
@@ -217,27 +217,6 @@ struct object_list *object_list_insert(struct object *item,
         return new_list;
 }
 
-void object_list_append(struct object *item,
-                       struct object_list **list_p)
-{
-       while (*list_p) {
-               list_p = &((*list_p)->next);
-       }
-       *list_p = xmalloc(sizeof(struct object_list));
-       (*list_p)->next = NULL;
-       (*list_p)->item = item;
-}
-
-unsigned object_list_length(struct object_list *list)
-{
-       unsigned ret = 0;
-       while (list) {
-               list = list->next;
-               ret++;
-       }
-       return ret;
-}
-
 int object_list_contains(struct object_list *list, struct object *obj)
 {
        while (list) {
index 89dd0c47a6c86fd3a63370c84e574e799830e1d3..82877c831ca7fe39abbb98805f5801bdc6fcac9c 100644 (file)
--- a/object.h
+++ b/object.h
@@ -72,11 +72,6 @@ struct object *lookup_unknown_object(const unsigned  char *sha1);
 struct object_list *object_list_insert(struct object *item,
                                       struct object_list **list_p);
 
-void object_list_append(struct object *item,
-                       struct object_list **list_p);
-
-unsigned object_list_length(struct object_list *list);
-
 int object_list_contains(struct object_list *list, struct object *obj);
 
 /* Object array handling .. */
diff --git a/pack-redundant.c b/pack-redundant.c
deleted file mode 100644 (file)
index 21c61db..0000000
+++ /dev/null
@@ -1,700 +0,0 @@
-/*
-*
-* Copyright 2005, Lukas Sandstrom <lukass@etek.chalmers.se>
-*
-* This file is licensed under the GPL v2.
-*
-*/
-
-#include "cache.h"
-#include "exec_cmd.h"
-
-#define BLKSIZE 512
-
-static const char pack_redundant_usage[] =
-"git pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
-
-static int load_all_packs, verbose, alt_odb;
-
-struct llist_item {
-       struct llist_item *next;
-       const unsigned char *sha1;
-};
-static struct llist {
-       struct llist_item *front;
-       struct llist_item *back;
-       size_t size;
-} *all_objects; /* all objects which must be present in local packfiles */
-
-static struct pack_list {
-       struct pack_list *next;
-       struct packed_git *pack;
-       struct llist *unique_objects;
-       struct llist *all_objects;
-} *local_packs = NULL, *altodb_packs = NULL;
-
-struct pll {
-       struct pll *next;
-       struct pack_list *pl;
-};
-
-static struct llist_item *free_nodes;
-
-static inline void llist_item_put(struct llist_item *item)
-{
-       item->next = free_nodes;
-       free_nodes = item;
-}
-
-static inline struct llist_item *llist_item_get(void)
-{
-       struct llist_item *new;
-       if ( free_nodes ) {
-               new = free_nodes;
-               free_nodes = free_nodes->next;
-       } else {
-               int i = 1;
-               new = xmalloc(sizeof(struct llist_item) * BLKSIZE);
-               for (; i < BLKSIZE; i++)
-                       llist_item_put(&new[i]);
-       }
-       return new;
-}
-
-static void llist_free(struct llist *list)
-{
-       while ((list->back = list->front)) {
-               list->front = list->front->next;
-               llist_item_put(list->back);
-       }
-       free(list);
-}
-
-static inline void llist_init(struct llist **list)
-{
-       *list = xmalloc(sizeof(struct llist));
-       (*list)->front = (*list)->back = NULL;
-       (*list)->size = 0;
-}
-
-static struct llist * llist_copy(struct llist *list)
-{
-       struct llist *ret;
-       struct llist_item *new, *old, *prev;
-
-       llist_init(&ret);
-
-       if ((ret->size = list->size) == 0)
-               return ret;
-
-       new = ret->front = llist_item_get();
-       new->sha1 = list->front->sha1;
-
-       old = list->front->next;
-       while (old) {
-               prev = new;
-               new = llist_item_get();
-               prev->next = new;
-               new->sha1 = old->sha1;
-               old = old->next;
-       }
-       new->next = NULL;
-       ret->back = new;
-
-       return ret;
-}
-
-static inline struct llist_item *llist_insert(struct llist *list,
-                                             struct llist_item *after,
-                                              const unsigned char *sha1)
-{
-       struct llist_item *new = llist_item_get();
-       new->sha1 = sha1;
-       new->next = NULL;
-
-       if (after != NULL) {
-               new->next = after->next;
-               after->next = new;
-               if (after == list->back)
-                       list->back = new;
-       } else {/* insert in front */
-               if (list->size == 0)
-                       list->back = new;
-               else
-                       new->next = list->front;
-               list->front = new;
-       }
-       list->size++;
-       return new;
-}
-
-static inline struct llist_item *llist_insert_back(struct llist *list,
-                                                  const unsigned char *sha1)
-{
-       return llist_insert(list, list->back, sha1);
-}
-
-static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
-                       const unsigned char *sha1, struct llist_item *hint)
-{
-       struct llist_item *prev = NULL, *l;
-
-       l = (hint == NULL) ? list->front : hint;
-       while (l) {
-               int cmp = hashcmp(l->sha1, sha1);
-               if (cmp > 0) { /* we insert before this entry */
-                       return llist_insert(list, prev, sha1);
-               }
-               if (!cmp) { /* already exists */
-                       return l;
-               }
-               prev = l;
-               l = l->next;
-       }
-       /* insert at the end */
-       return llist_insert_back(list, sha1);
-}
-
-/* returns a pointer to an item in front of sha1 */
-static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *sha1, struct llist_item *hint)
-{
-       struct llist_item *prev, *l;
-
-redo_from_start:
-       l = (hint == NULL) ? list->front : hint;
-       prev = NULL;
-       while (l) {
-               int cmp = hashcmp(l->sha1, sha1);
-               if (cmp > 0) /* not in list, since sorted */
-                       return prev;
-               if (!cmp) { /* found */
-                       if (prev == NULL) {
-                               if (hint != NULL && hint != list->front) {
-                                       /* we don't know the previous element */
-                                       hint = NULL;
-                                       goto redo_from_start;
-                               }
-                               list->front = l->next;
-                       } else
-                               prev->next = l->next;
-                       if (l == list->back)
-                               list->back = prev;
-                       llist_item_put(l);
-                       list->size--;
-                       return prev;
-               }
-               prev = l;
-               l = l->next;
-       }
-       return prev;
-}
-
-/* computes A\B */
-static void llist_sorted_difference_inplace(struct llist *A,
-                                    struct llist *B)
-{
-       struct llist_item *hint, *b;
-
-       hint = NULL;
-       b = B->front;
-
-       while (b) {
-               hint = llist_sorted_remove(A, b->sha1, hint);
-               b = b->next;
-       }
-}
-
-static inline struct pack_list * pack_list_insert(struct pack_list **pl,
-                                          struct pack_list *entry)
-{
-       struct pack_list *p = xmalloc(sizeof(struct pack_list));
-       memcpy(p, entry, sizeof(struct pack_list));
-       p->next = *pl;
-       *pl = p;
-       return p;
-}
-
-static inline size_t pack_list_size(struct pack_list *pl)
-{
-       size_t ret = 0;
-       while (pl) {
-               ret++;
-               pl = pl->next;
-       }
-       return ret;
-}
-
-static struct pack_list * pack_list_difference(const struct pack_list *A,
-                                              const struct pack_list *B)
-{
-       struct pack_list *ret;
-       const struct pack_list *pl;
-
-       if (A == NULL)
-               return NULL;
-
-       pl = B;
-       while (pl != NULL) {
-               if (A->pack == pl->pack)
-                       return pack_list_difference(A->next, B);
-               pl = pl->next;
-       }
-       ret = xmalloc(sizeof(struct pack_list));
-       memcpy(ret, A, sizeof(struct pack_list));
-       ret->next = pack_list_difference(A->next, B);
-       return ret;
-}
-
-static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
-{
-       unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
-       const unsigned char *p1_base, *p2_base;
-       struct llist_item *p1_hint = NULL, *p2_hint = NULL;
-
-       p1_base = p1->pack->index_data;
-       p2_base = p2->pack->index_data;
-       p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8);
-       p2_base += 256 * 4 + ((p2->pack->index_version < 2) ? 4 : 8);
-       p1_step = (p1->pack->index_version < 2) ? 24 : 20;
-       p2_step = (p2->pack->index_version < 2) ? 24 : 20;
-
-       while (p1_off < p1->pack->num_objects * p1_step &&
-              p2_off < p2->pack->num_objects * p2_step)
-       {
-               int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
-               /* cmp ~ p1 - p2 */
-               if (cmp == 0) {
-                       p1_hint = llist_sorted_remove(p1->unique_objects,
-                                       p1_base + p1_off, p1_hint);
-                       p2_hint = llist_sorted_remove(p2->unique_objects,
-                                       p1_base + p1_off, p2_hint);
-                       p1_off += p1_step;
-                       p2_off += p2_step;
-                       continue;
-               }
-               if (cmp < 0) { /* p1 has the object, p2 doesn't */
-                       p1_off += p1_step;
-               } else { /* p2 has the object, p1 doesn't */
-                       p2_off += p2_step;
-               }
-       }
-}
-
-static void pll_free(struct pll *l)
-{
-       struct pll *old;
-       struct pack_list *opl;
-
-       while (l) {
-               old = l;
-               while (l->pl) {
-                       opl = l->pl;
-                       l->pl = opl->next;
-                       free(opl);
-               }
-               l = l->next;
-               free(old);
-       }
-}
-
-/* all the permutations have to be free()d at the same time,
- * since they refer to each other
- */
-static struct pll * get_permutations(struct pack_list *list, int n)
-{
-       struct pll *subset, *ret = NULL, *new_pll = NULL, *pll;
-
-       if (list == NULL || pack_list_size(list) < n || n == 0)
-               return NULL;
-
-       if (n == 1) {
-               while (list) {
-                       new_pll = xmalloc(sizeof(pll));
-                       new_pll->pl = NULL;
-                       pack_list_insert(&new_pll->pl, list);
-                       new_pll->next = ret;
-                       ret = new_pll;
-                       list = list->next;
-               }
-               return ret;
-       }
-
-       while (list->next) {
-               subset = get_permutations(list->next, n - 1);
-               while (subset) {
-                       new_pll = xmalloc(sizeof(pll));
-                       new_pll->pl = subset->pl;
-                       pack_list_insert(&new_pll->pl, list);
-                       new_pll->next = ret;
-                       ret = new_pll;
-                       subset = subset->next;
-               }
-               list = list->next;
-       }
-       return ret;
-}
-
-static int is_superset(struct pack_list *pl, struct llist *list)
-{
-       struct llist *diff;
-
-       diff = llist_copy(list);
-
-       while (pl) {
-               llist_sorted_difference_inplace(diff, pl->all_objects);
-               if (diff->size == 0) { /* we're done */
-                       llist_free(diff);
-                       return 1;
-               }
-               pl = pl->next;
-       }
-       llist_free(diff);
-       return 0;
-}
-
-static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
-{
-       size_t ret = 0;
-       unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
-       const unsigned char *p1_base, *p2_base;
-
-       p1_base = p1->index_data;
-       p2_base = p2->index_data;
-       p1_base += 256 * 4 + ((p1->index_version < 2) ? 4 : 8);
-       p2_base += 256 * 4 + ((p2->index_version < 2) ? 4 : 8);
-       p1_step = (p1->index_version < 2) ? 24 : 20;
-       p2_step = (p2->index_version < 2) ? 24 : 20;
-
-       while (p1_off < p1->num_objects * p1_step &&
-              p2_off < p2->num_objects * p2_step)
-       {
-               int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
-               /* cmp ~ p1 - p2 */
-               if (cmp == 0) {
-                       ret++;
-                       p1_off += p1_step;
-                       p2_off += p2_step;
-                       continue;
-               }
-               if (cmp < 0) { /* p1 has the object, p2 doesn't */
-                       p1_off += p1_step;
-               } else { /* p2 has the object, p1 doesn't */
-                       p2_off += p2_step;
-               }
-       }
-       return ret;
-}
-
-/* another O(n^2) function ... */
-static size_t get_pack_redundancy(struct pack_list *pl)
-{
-       struct pack_list *subset;
-       size_t ret = 0;
-
-       if (pl == NULL)
-               return 0;
-
-       while ((subset = pl->next)) {
-               while (subset) {
-                       ret += sizeof_union(pl->pack, subset->pack);
-                       subset = subset->next;
-               }
-               pl = pl->next;
-       }
-       return ret;
-}
-
-static inline off_t pack_set_bytecount(struct pack_list *pl)
-{
-       off_t ret = 0;
-       while (pl) {
-               ret += pl->pack->pack_size;
-               ret += pl->pack->index_size;
-               pl = pl->next;
-       }
-       return ret;
-}
-
-static void minimize(struct pack_list **min)
-{
-       struct pack_list *pl, *unique = NULL,
-               *non_unique = NULL, *min_perm = NULL;
-       struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm;
-       struct llist *missing;
-       off_t min_perm_size = 0, perm_size;
-       int n;
-
-       pl = local_packs;
-       while (pl) {
-               if (pl->unique_objects->size)
-                       pack_list_insert(&unique, pl);
-               else
-                       pack_list_insert(&non_unique, pl);
-               pl = pl->next;
-       }
-       /* find out which objects are missing from the set of unique packs */
-       missing = llist_copy(all_objects);
-       pl = unique;
-       while (pl) {
-               llist_sorted_difference_inplace(missing, pl->all_objects);
-               pl = pl->next;
-       }
-
-       /* return if there are no objects missing from the unique set */
-       if (missing->size == 0) {
-               *min = unique;
-               return;
-       }
-
-       /* find the permutations which contain all missing objects */
-       for (n = 1; n <= pack_list_size(non_unique) && !perm_ok; n++) {
-               perm_all = perm = get_permutations(non_unique, n);
-               while (perm) {
-                       if (is_superset(perm->pl, missing)) {
-                               new_perm = xmalloc(sizeof(struct pll));
-                               memcpy(new_perm, perm, sizeof(struct pll));
-                               new_perm->next = perm_ok;
-                               perm_ok = new_perm;
-                       }
-                       perm = perm->next;
-               }
-               if (perm_ok)
-                       break;
-               pll_free(perm_all);
-       }
-       if (perm_ok == NULL)
-               die("Internal error: No complete sets found!");
-
-       /* find the permutation with the smallest size */
-       perm = perm_ok;
-       while (perm) {
-               perm_size = pack_set_bytecount(perm->pl);
-               if (!min_perm_size || min_perm_size > perm_size) {
-                       min_perm_size = perm_size;
-                       min_perm = perm->pl;
-               }
-               perm = perm->next;
-       }
-       *min = min_perm;
-       /* add the unique packs to the list */
-       pl = unique;
-       while (pl) {
-               pack_list_insert(min, pl);
-               pl = pl->next;
-       }
-}
-
-static void load_all_objects(void)
-{
-       struct pack_list *pl = local_packs;
-       struct llist_item *hint, *l;
-
-       llist_init(&all_objects);
-
-       while (pl) {
-               hint = NULL;
-               l = pl->all_objects->front;
-               while (l) {
-                       hint = llist_insert_sorted_unique(all_objects,
-                                                         l->sha1, hint);
-                       l = l->next;
-               }
-               pl = pl->next;
-       }
-       /* remove objects present in remote packs */
-       pl = altodb_packs;
-       while (pl) {
-               llist_sorted_difference_inplace(all_objects, pl->all_objects);
-               pl = pl->next;
-       }
-}
-
-/* this scales like O(n^2) */
-static void cmp_local_packs(void)
-{
-       struct pack_list *subset, *pl = local_packs;
-
-       while ((subset = pl)) {
-               while ((subset = subset->next))
-                       cmp_two_packs(pl, subset);
-               pl = pl->next;
-       }
-}
-
-static void scan_alt_odb_packs(void)
-{
-       struct pack_list *local, *alt;
-
-       alt = altodb_packs;
-       while (alt) {
-               local = local_packs;
-               while (local) {
-                       llist_sorted_difference_inplace(local->unique_objects,
-                                                       alt->all_objects);
-                       local = local->next;
-               }
-               llist_sorted_difference_inplace(all_objects, alt->all_objects);
-               alt = alt->next;
-       }
-}
-
-static struct pack_list * add_pack(struct packed_git *p)
-{
-       struct pack_list l;
-       unsigned long off = 0, step;
-       const unsigned char *base;
-
-       if (!p->pack_local && !(alt_odb || verbose))
-               return NULL;
-
-       l.pack = p;
-       llist_init(&l.all_objects);
-
-       if (open_pack_index(p))
-               return NULL;
-
-       base = p->index_data;
-       base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
-       step = (p->index_version < 2) ? 24 : 20;
-       while (off < p->num_objects * step) {
-               llist_insert_back(l.all_objects, base + off);
-               off += step;
-       }
-       /* this list will be pruned in cmp_two_packs later */
-       l.unique_objects = llist_copy(l.all_objects);
-       if (p->pack_local)
-               return pack_list_insert(&local_packs, &l);
-       else
-               return pack_list_insert(&altodb_packs, &l);
-}
-
-static struct pack_list * add_pack_file(char *filename)
-{
-       struct packed_git *p = packed_git;
-
-       if (strlen(filename) < 40)
-               die("Bad pack filename: %s", filename);
-
-       while (p) {
-               if (strstr(p->pack_name, filename))
-                       return add_pack(p);
-               p = p->next;
-       }
-       die("Filename %s not found in packed_git", filename);
-}
-
-static void load_all(void)
-{
-       struct packed_git *p = packed_git;
-
-       while (p) {
-               add_pack(p);
-               p = p->next;
-       }
-}
-
-int main(int argc, char **argv)
-{
-       int i;
-       struct pack_list *min, *red, *pl;
-       struct llist *ignore;
-       unsigned char *sha1;
-       char buf[42]; /* 40 byte sha1 + \n + \0 */
-
-       git_extract_argv0_path(argv[0]);
-
-       if (argc == 2 && !strcmp(argv[1], "-h"))
-               usage(pack_redundant_usage);
-
-       setup_git_directory();
-
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "--all")) {
-                       load_all_packs = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--verbose")) {
-                       verbose = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--alt-odb")) {
-                       alt_odb = 1;
-                       continue;
-               }
-               if (*arg == '-')
-                       usage(pack_redundant_usage);
-               else
-                       break;
-       }
-
-       prepare_packed_git();
-
-       if (load_all_packs)
-               load_all();
-       else
-               while (*(argv + i) != NULL)
-                       add_pack_file(*(argv + i++));
-
-       if (local_packs == NULL)
-               die("Zero packs found!");
-
-       load_all_objects();
-
-       cmp_local_packs();
-       if (alt_odb)
-               scan_alt_odb_packs();
-
-       /* ignore objects given on stdin */
-       llist_init(&ignore);
-       if (!isatty(0)) {
-               while (fgets(buf, sizeof(buf), stdin)) {
-                       sha1 = xmalloc(20);
-                       if (get_sha1_hex(buf, sha1))
-                               die("Bad sha1 on stdin: %s", buf);
-                       llist_insert_sorted_unique(ignore, sha1, NULL);
-               }
-       }
-       llist_sorted_difference_inplace(all_objects, ignore);
-       pl = local_packs;
-       while (pl) {
-               llist_sorted_difference_inplace(pl->unique_objects, ignore);
-               pl = pl->next;
-       }
-
-       minimize(&min);
-
-       if (verbose) {
-               fprintf(stderr, "There are %lu packs available in alt-odbs.\n",
-                       (unsigned long)pack_list_size(altodb_packs));
-               fprintf(stderr, "The smallest (bytewise) set of packs is:\n");
-               pl = min;
-               while (pl) {
-                       fprintf(stderr, "\t%s\n", pl->pack->pack_name);
-                       pl = pl->next;
-               }
-               fprintf(stderr, "containing %lu duplicate objects "
-                               "with a total size of %lukb.\n",
-                       (unsigned long)get_pack_redundancy(min),
-                       (unsigned long)pack_set_bytecount(min)/1024);
-               fprintf(stderr, "A total of %lu unique objects were considered.\n",
-                       (unsigned long)all_objects->size);
-               fprintf(stderr, "Redundant packs (with indexes):\n");
-       }
-       pl = red = pack_list_difference(local_packs, min);
-       while (pl) {
-               printf("%s\n%s\n",
-                      sha1_pack_index_name(pl->pack->sha1),
-                      pl->pack->pack_name);
-               pl = pl->next;
-       }
-       if (verbose)
-               fprintf(stderr, "%luMB of redundant packs in total.\n",
-                       (unsigned long)pack_set_bytecount(red)/(1024*1024));
-
-       return 0;
-}
index 741efcd93bd2a2c105bfe2d771d32857ce2ae816..9f47cf9961212242bc01082944e83fb54da2a515 100644 (file)
@@ -17,8 +17,8 @@ static int sha1_compare(const void *_a, const void *_b)
  * the SHA1 hash of sorted object names. The objects array passed in
  * will be sorted by SHA1 on exit.
  */
-char *write_idx_file(char *index_name, struct pack_idx_entry **objects,
-                    int nr_objects, unsigned char *sha1)
+const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects,
+                          int nr_objects, unsigned char *sha1)
 {
        struct sha1file *f;
        struct pack_idx_entry **sorted_by_sha, **list, **last;
diff --git a/pack.h b/pack.h
index a883334b269c76d8de1395adf2b8f3d0d7e8564f..b759a23d4d022c4acef893ec35a5dd4e2324715c 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -55,7 +55,7 @@ struct pack_idx_entry {
        off_t offset;
 };
 
-extern char *write_idx_file(char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
+extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
 extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr);
 extern int verify_pack(struct packed_git *);
 extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t);
diff --git a/pager.c b/pager.c
index 92c03f654abd0333bd0dd48b4aebf9ae42ac4de5..2c7e8ecb3c0860b00113e348008415ca28a7ff72 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -28,7 +28,7 @@ static void pager_preexec(void)
 }
 #endif
 
-static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
+static const char *pager_argv[] = { NULL, NULL };
 static struct child_process pager_process;
 
 static void wait_for_pager(void)
@@ -81,7 +81,8 @@ void setup_pager(void)
        spawned_pager = 1; /* means we are emitting to terminal */
 
        /* spawn the pager */
-       pager_argv[2] = pager;
+       pager_argv[0] = pager;
+       pager_process.use_shell = 1;
        pager_process.argv = pager_argv;
        pager_process.in = -1;
        if (!getenv("LESS")) {
index f5594114ede8a50090c8b97987b52a660235fa56..d218122af5c2c0cddd857fce0ae5064bf32f6387 100644 (file)
@@ -3,6 +3,9 @@
 #include "cache.h"
 #include "commit.h"
 
+static int parse_options_usage(const char * const *usagestr,
+                              const struct option *opts);
+
 #define OPT_SHORT 1
 #define OPT_UNSET 2
 
@@ -560,8 +563,8 @@ void usage_msg_opt(const char *msg,
        usage_with_options(usagestr, options);
 }
 
-int parse_options_usage(const char * const *usagestr,
-                       const struct option *opts)
+static int parse_options_usage(const char * const *usagestr,
+                              const struct option *opts)
 {
        return usage_with_options_internal(usagestr, opts, 0);
 }
@@ -633,3 +636,10 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
        commit_list_insert(commit, opt->value);
        return 0;
 }
+
+int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
+{
+       int *target = opt->value;
+       *target = unset ? 2 : 1;
+       return 0;
+}
index f295a2cf858f4dbebc62e956878544ffdb7fbe58..0c996916b6044989f0e2945881c7c12f7292d5c1 100644 (file)
@@ -123,6 +123,8 @@ struct option {
                                      (h), PARSE_OPT_NOARG, NULL, (p) }
 #define OPT_INTEGER(s, l, v, h)     { OPTION_INTEGER, (s), (l), (v), "n", (h) }
 #define OPT_STRING(s, l, v, a, h)   { OPTION_STRING,  (s), (l), (v), (a), (h) }
+#define OPT_UYN(s, l, v, h)         { OPTION_CALLBACK, (s), (l), (v), NULL, \
+                                     (h), PARSE_OPT_NOARG, &parse_opt_tertiary }
 #define OPT_DATE(s, l, v, h) \
        { OPTION_CALLBACK, (s), (l), (v), "time",(h), 0, \
          parse_opt_approxidate_cb }
@@ -171,9 +173,6 @@ struct parse_opt_ctx_t {
        const char *prefix;
 };
 
-extern int parse_options_usage(const char * const *usagestr,
-                              const struct option *opts);
-
 extern void parse_options_start(struct parse_opt_ctx_t *ctx,
                                int argc, const char **argv, const char *prefix,
                                int flags);
@@ -190,6 +189,7 @@ extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
 extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
 extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
 extern int parse_opt_with_commit(const struct option *, const char *, int);
+extern int parse_opt_tertiary(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var)  OPT_BOOLEAN('v', "verbose", (var), "be verbose")
 #define OPT__QUIET(var)    OPT_BOOLEAN('q', "quiet",   (var), "be quiet")
index e02e13bd4eb2a92626c2d6f9cbf264abb15de9c5..d218faa02bd12b0e6a0df298a6a0e5787e46d93f 100644 (file)
@@ -33,8 +33,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
 
        /* now the result size */
        size = get_delta_hdr_size(&data, top);
-       dst_buf = xmalloc(size + 1);
-       dst_buf[size] = 0;
+       dst_buf = xmallocz(size);
 
        out = dst_buf;
        while (data < top) {
diff --git a/patch-id.c b/patch-id.c
deleted file mode 100644 (file)
index 0df4cb0..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-#include "cache.h"
-#include "exec_cmd.h"
-
-static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
-{
-       unsigned char result[20];
-       char name[50];
-
-       if (!patchlen)
-               return;
-
-       git_SHA1_Final(result, c);
-       memcpy(name, sha1_to_hex(id), 41);
-       printf("%s %s\n", sha1_to_hex(result), name);
-       git_SHA1_Init(c);
-}
-
-static int remove_space(char *line)
-{
-       char *src = line;
-       char *dst = line;
-       unsigned char c;
-
-       while ((c = *src++) != '\0') {
-               if (!isspace(c))
-                       *dst++ = c;
-       }
-       return dst - line;
-}
-
-static void generate_id_list(void)
-{
-       static unsigned char sha1[20];
-       static char line[1000];
-       git_SHA_CTX ctx;
-       int patchlen = 0;
-
-       git_SHA1_Init(&ctx);
-       while (fgets(line, sizeof(line), stdin) != NULL) {
-               unsigned char n[20];
-               char *p = line;
-               int len;
-
-               if (!memcmp(line, "diff-tree ", 10))
-                       p += 10;
-               else if (!memcmp(line, "commit ", 7))
-                       p += 7;
-
-               if (!get_sha1_hex(p, n)) {
-                       flush_current_id(patchlen, sha1, &ctx);
-                       hashcpy(sha1, n);
-                       patchlen = 0;
-                       continue;
-               }
-
-               /* Ignore commit comments */
-               if (!patchlen && memcmp(line, "diff ", 5))
-                       continue;
-
-               /* Ignore git-diff index header */
-               if (!memcmp(line, "index ", 6))
-                       continue;
-
-               /* Ignore line numbers when computing the SHA1 of the patch */
-               if (!memcmp(line, "@@ -", 4))
-                       continue;
-
-               /* Compute the sha without whitespace */
-               len = remove_space(line);
-               patchlen += len;
-               git_SHA1_Update(&ctx, line, len);
-       }
-       flush_current_id(patchlen, sha1, &ctx);
-}
-
-static const char patch_id_usage[] = "git patch-id < patch";
-
-int main(int argc, char **argv)
-{
-       if (argc != 1)
-               usage(patch_id_usage);
-
-       git_extract_argv0_path(argv[0]);
-
-       generate_id_list();
-       return 0;
-}
index 92899333c2d8edbed71fdd3a43e19f25a10e5b03..e3d0bda31a98372eb9b6a8c2cd0fd65917a9dbde 100644 (file)
@@ -47,6 +47,8 @@ static void *preload_thread(void *_data)
 
                if (ce_stage(ce))
                        continue;
+               if (S_ISGITLINK(ce->ce_mode))
+                       continue;
                if (ce_uptodate(ce))
                        continue;
                if (!ce_path_match(ce, p->pathspec))
index b2ee7fe9de2830dbc276b49d54a18d1130b751a2..d493cade26890d3e16ea072ced8e0f95679f5670 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -83,7 +83,7 @@ static int get_one_line(const char *msg)
 }
 
 /* High bit set, or ISO-2022-INT */
-int non_ascii(int ch)
+static int non_ascii(int ch)
 {
        return !isascii(ch) || ch == '\033';
 }
diff --git a/quote.c b/quote.c
index 848d174cc56be32483ba6c73f9b06bc398ed6437..fc93435727db3b0634c390965258200c61d8b59b 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -72,7 +72,7 @@ void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen)
        }
 }
 
-char *sq_dequote_step(char *arg, char **next)
+static char *sq_dequote_step(char *arg, char **next)
 {
        char *dst = arg;
        char *src = arg;
@@ -213,7 +213,7 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
                int ch;
 
                len = next_quote_pos(p, maxlen);
-               if (len == maxlen || !p[len])
+               if (len == maxlen || (maxlen < 0 && !p[len]))
                        break;
 
                if (!no_dq && p == name)
@@ -223,6 +223,8 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
                EMIT('\\');
                p += len;
                ch = (unsigned char)*p++;
+               if (maxlen >= 0)
+                       maxlen -= len + 1;
                if (sq_lookup[ch] >= ' ') {
                        EMIT(sq_lookup[ch]);
                } else {
diff --git a/quote.h b/quote.h
index 66730f2bff3cee42bc7c670e2a6d7da240db1d08..f83eb233c4b153c4c073b3ccd5bf2f5dd926b739 100644 (file)
--- a/quote.h
+++ b/quote.h
@@ -45,7 +45,6 @@ extern char *sq_dequote(char *);
  * next argument that should be passed as first parameter. When there
  * is no more argument to be dequoted, "next" is updated to point to NULL.
  */
-extern char *sq_dequote_step(char *arg, char **next);
 extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc);
 
 extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp);
index 9033dd3ab938e2ee7b4248ff97645414ee3688db..f1f789b7b87643245f1772d15b3f1cb321af324c 100644 (file)
 #include "dir.h"
 #include "tree.h"
 #include "commit.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "revision.h"
 #include "blob.h"
+#include "resolve-undo.h"
+
+static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
 
 /* Index extensions.
  *
@@ -26,6 +26,7 @@
 
 #define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) )
 #define CACHE_EXT_TREE 0x54524545      /* "TREE" */
+#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */
 
 struct index_state the_index;
 
@@ -156,7 +157,7 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
        return 0;
 }
 
-int is_empty_blob_sha1(const unsigned char *sha1)
+static int is_empty_blob_sha1(const unsigned char *sha1)
 {
        static const unsigned char empty_blob_sha1[20] = {
                0xe6,0x9d,0xe2,0x9b,0xb2,0xd1,0xd6,0x43,0x4b,0x8b,
@@ -259,12 +260,17 @@ int ie_match_stat(const struct index_state *istate,
 {
        unsigned int changed;
        int ignore_valid = options & CE_MATCH_IGNORE_VALID;
+       int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
        int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY;
 
        /*
         * If it's marked as always valid in the index, it's
         * valid whatever the checked-out copy says.
+        *
+        * skip-worktree has the same effect with higher precedence
         */
+       if (!ignore_skip_worktree && ce_skip_worktree(ce))
+               return 0;
        if (!ignore_valid && (ce->ce_flags & CE_VALID))
                return 0;
 
@@ -449,6 +455,7 @@ int remove_index_entry_at(struct index_state *istate, int pos)
 {
        struct cache_entry *ce = istate->cache[pos];
 
+       record_resolve_undo(istate, ce);
        remove_name_hash(ce);
        istate->cache_changed = 1;
        istate->cache_nr--;
@@ -564,7 +571,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
        int size, namelen, was_same;
        mode_t st_mode = st->st_mode;
        struct cache_entry *ce, *alias;
-       unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY;
+       unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE|CE_MATCH_RACY_IS_DIRTY;
        int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND);
        int pretend = flags & ADD_CACHE_PRETEND;
        int intent_only = flags & ADD_CACHE_INTENT;
@@ -605,7 +612,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
        if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) {
                /* Nothing changed, really */
                free(ce);
-               ce_mark_uptodate(alias);
+               if (!S_ISGITLINK(alias->ce_mode))
+                       ce_mark_uptodate(alias);
                alias->ce_flags |= CE_ADDED;
                return 0;
        }
@@ -1000,14 +1008,20 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
        struct cache_entry *updated;
        int changed, size;
        int ignore_valid = options & CE_MATCH_IGNORE_VALID;
+       int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
 
        if (ce_uptodate(ce))
                return ce;
 
        /*
-        * CE_VALID means the user promised us that the change to
-        * the work tree does not matter and told us not to worry.
+        * CE_VALID or CE_SKIP_WORKTREE means the user promised us
+        * that the change to the work tree does not matter and told
+        * us not to worry.
         */
+       if (!ignore_skip_worktree && ce_skip_worktree(ce)) {
+               ce_mark_uptodate(ce);
+               return ce;
+       }
        if (!ignore_valid && (ce->ce_flags & CE_VALID)) {
                ce_mark_uptodate(ce);
                return ce;
@@ -1037,7 +1051,8 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
                         * because CE_UPTODATE flag is in-core only;
                         * we are not going to write this change out.
                         */
-                       ce_mark_uptodate(ce);
+                       if (!S_ISGITLINK(ce->ce_mode))
+                               ce_mark_uptodate(ce);
                        return ce;
                }
        }
@@ -1141,7 +1156,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
        return has_errors;
 }
 
-struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
+static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
 {
        return refresh_cache_ent(&the_index, ce, really, NULL);
 }
@@ -1170,6 +1185,9 @@ static int read_index_extension(struct index_state *istate,
        case CACHE_EXT_TREE:
                istate->cache_tree = cache_tree_read(data, sz);
                break;
+       case CACHE_EXT_RESOLVE_UNDO:
+               istate->resolve_undo = resolve_undo_read(data, sz);
+               break;
        default:
                if (*ext < 'A' || 'Z' < *ext)
                        return error("index uses %.4s extension, which we do not understand",
@@ -1349,6 +1367,7 @@ int is_index_unborn(struct index_state *istate)
 
 int discard_index(struct index_state *istate)
 {
+       resolve_undo_clear_index(istate);
        istate->cache_nr = 0;
        istate->cache_changed = 0;
        istate->timestamp.sec = 0;
@@ -1574,6 +1593,17 @@ int write_index(struct index_state *istate, int newfd)
                if (err)
                        return -1;
        }
+       if (istate->resolve_undo) {
+               struct strbuf sb = STRBUF_INIT;
+
+               resolve_undo_write(&sb, istate->resolve_undo);
+               err = write_index_ext_header(&c, newfd, CACHE_EXT_RESOLVE_UNDO,
+                                            sb.len) < 0
+                       || ce_write(&c, newfd, sb.buf, sb.len) < 0;
+               strbuf_release(&sb);
+               if (err)
+                       return -1;
+       }
 
        if (ce_flush(&c, newfd) || fstat(newfd, &st))
                return -1;
@@ -1606,9 +1636,8 @@ int read_index_unmerged(struct index_state *istate)
                len = strlen(ce->name);
                size = cache_entry_size(len);
                new_ce = xcalloc(1, size);
-               hashcpy(new_ce->sha1, ce->sha1);
                memcpy(new_ce->name, ce->name, len);
-               new_ce->ce_flags = create_ce_flags(len, 0);
+               new_ce->ce_flags = create_ce_flags(len, 0) | CE_CONFLICTED;
                new_ce->ce_mode = ce->ce_mode;
                if (add_index_entry(istate, new_ce, 0))
                        return error("%s: cannot drop to stage #0",
@@ -1618,81 +1647,6 @@ int read_index_unmerged(struct index_state *istate)
        return unmerged;
 }
 
-struct update_callback_data
-{
-       int flags;
-       int add_errors;
-};
-
-static void update_callback(struct diff_queue_struct *q,
-                           struct diff_options *opt, void *cbdata)
-{
-       int i;
-       struct update_callback_data *data = cbdata;
-
-       for (i = 0; i < q->nr; i++) {
-               struct diff_filepair *p = q->queue[i];
-               const char *path = p->one->path;
-               switch (p->status) {
-               default:
-                       die("unexpected diff status %c", p->status);
-               case DIFF_STATUS_UNMERGED:
-                       /*
-                        * ADD_CACHE_IGNORE_REMOVAL is unset if "git
-                        * add -u" is calling us, In such a case, a
-                        * missing work tree file needs to be removed
-                        * if there is an unmerged entry at stage #2,
-                        * but such a diff record is followed by
-                        * another with DIFF_STATUS_DELETED (and if
-                        * there is no stage #2, we won't see DELETED
-                        * nor MODIFIED).  We can simply continue
-                        * either way.
-                        */
-                       if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL))
-                               continue;
-                       /*
-                        * Otherwise, it is "git add path" is asking
-                        * to explicitly add it; we fall through.  A
-                        * missing work tree file is an error and is
-                        * caught by add_file_to_index() in such a
-                        * case.
-                        */
-               case DIFF_STATUS_MODIFIED:
-               case DIFF_STATUS_TYPE_CHANGED:
-                       if (add_file_to_index(&the_index, path, data->flags)) {
-                               if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
-                                       die("updating files failed");
-                               data->add_errors++;
-                       }
-                       break;
-               case DIFF_STATUS_DELETED:
-                       if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
-                               break;
-                       if (!(data->flags & ADD_CACHE_PRETEND))
-                               remove_file_from_index(&the_index, path);
-                       if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
-                               printf("remove '%s'\n", path);
-                       break;
-               }
-       }
-}
-
-int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
-{
-       struct update_callback_data data;
-       struct rev_info rev;
-       init_revisions(&rev, prefix);
-       setup_revisions(0, NULL, &rev, NULL);
-       rev.prune_data = pathspec;
-       rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
-       rev.diffopt.format_callback = update_callback;
-       data.flags = flags;
-       data.add_errors = 0;
-       rev.diffopt.format_callback_data = &data;
-       run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
-       return !!data.add_errors;
-}
-
 /*
  * Returns 1 if the path is an "other" path with respect to
  * the index; that is, the path is not mentioned in the index at all,
diff --git a/refs.c b/refs.c
index 3e73a0a36dbea4046e5b7b0550b2f4bb67539439..503a8c2bd0fa7e4fb825d543e007995701150948 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -519,6 +519,13 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
        return ref;
 }
 
+/* The argument to filter_refs */
+struct ref_filter {
+       const char *pattern;
+       each_ref_fn *fn;
+       void *cb_data;
+};
+
 int read_ref(const char *ref, unsigned char *sha1)
 {
        if (resolve_ref(ref, sha1, 1, NULL))
@@ -545,6 +552,15 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
        return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
 }
 
+static int filter_refs(const char *ref, const unsigned char *sha, int flags,
+       void *data)
+{
+       struct ref_filter *filter = (struct ref_filter *)data;
+       if (fnmatch(filter->pattern, ref, 0))
+               return 0;
+       return filter->fn(ref, sha, flags, filter->cb_data);
+}
+
 int peel_ref(const char *ref, unsigned char *sha1)
 {
        int flag;
@@ -674,6 +690,43 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
        return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data);
 }
 
+int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
+       const char *prefix, void *cb_data)
+{
+       struct strbuf real_pattern = STRBUF_INIT;
+       struct ref_filter filter;
+       const char *has_glob_specials;
+       int ret;
+
+       if (!prefix && prefixcmp(pattern, "refs/"))
+               strbuf_addstr(&real_pattern, "refs/");
+       else if (prefix)
+               strbuf_addstr(&real_pattern, prefix);
+       strbuf_addstr(&real_pattern, pattern);
+
+       has_glob_specials = strpbrk(pattern, "?*[");
+       if (!has_glob_specials) {
+               /* Append impiled '/' '*' if not present. */
+               if (real_pattern.buf[real_pattern.len - 1] != '/')
+                       strbuf_addch(&real_pattern, '/');
+               /* No need to check for '*', there is none. */
+               strbuf_addch(&real_pattern, '*');
+       }
+
+       filter.pattern = real_pattern.buf;
+       filter.fn = fn;
+       filter.cb_data = cb_data;
+       ret = for_each_ref(filter_refs, &filter);
+
+       strbuf_release(&real_pattern);
+       return ret;
+}
+
+int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
+{
+       return for_each_glob_ref_in(fn, pattern, NULL, cb_data);
+}
+
 int for_each_rawref(each_ref_fn fn, void *cb_data)
 {
        return do_for_each_ref("refs/", fn, 0,
diff --git a/refs.h b/refs.h
index e141991851be60334ea23cc6055b329c36cd272f..f7648b9bd3b719936024678246d0603028e72aa7 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -25,6 +25,8 @@ extern int for_each_tag_ref(each_ref_fn, void *);
 extern int for_each_branch_ref(each_ref_fn, void *);
 extern int for_each_remote_ref(each_ref_fn, void *);
 extern int for_each_replace_ref(each_ref_fn, void *);
+extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);
+extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);
 
 /* can be used to learn about broken ref and symref */
 extern int for_each_rawref(each_ref_fn, void *);
index 3edbf5717c94f3123a97939acf265cc8334ca899..a904164e425097380781c1ecd9bb13f4feec0de6 100644 (file)
@@ -304,6 +304,7 @@ struct rpc_state {
        int out;
        struct strbuf result;
        unsigned gzip_request : 1;
+       unsigned initial_buffer : 1;
 };
 
 static size_t rpc_out(void *ptr, size_t eltsize,
@@ -314,6 +315,7 @@ static size_t rpc_out(void *ptr, size_t eltsize,
        size_t avail = rpc->len - rpc->pos;
 
        if (!avail) {
+               rpc->initial_buffer = 0;
                avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
                if (!avail)
                        return 0;
@@ -328,6 +330,29 @@ static size_t rpc_out(void *ptr, size_t eltsize,
        return avail;
 }
 
+#ifndef NO_CURL_IOCTL
+static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
+{
+       struct rpc_state *rpc = clientp;
+
+       switch (cmd) {
+       case CURLIOCMD_NOP:
+               return CURLIOE_OK;
+
+       case CURLIOCMD_RESTARTREAD:
+               if (rpc->initial_buffer) {
+                       rpc->pos = 0;
+                       return CURLIOE_OK;
+               }
+               fprintf(stderr, "Unable to rewind rpc post data - try increasing http.postBuffer\n");
+               return CURLIOE_FAILRESTART;
+
+       default:
+               return CURLIOE_UNKNOWNCMD;
+       }
+}
+#endif
+
 static size_t rpc_in(const void *ptr, size_t eltsize,
                size_t nmemb, void *buffer_)
 {
@@ -384,8 +409,13 @@ static int post_rpc(struct rpc_state *rpc)
                 */
                headers = curl_slist_append(headers, "Expect: 100-continue");
                headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
+               rpc->initial_buffer = 1;
                curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
                curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc);
+#ifndef NO_CURL_IOCTL
+               curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, rpc_ioctl);
+               curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, rpc);
+#endif
                if (options.verbosity > 1) {
                        fprintf(stderr, "POST %s (chunked)\n", rpc->service_name);
                        fflush(stderr);
index b979a9642b81b456ab92cdf57e8eda944f01f92e..c70181cdc621b27ed02aba17b3e4f7ab64518e9f 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -53,6 +53,11 @@ static struct rewrites rewrites_push;
 #define BUF_SIZE (2048)
 static char buffer[BUF_SIZE];
 
+static int valid_remote(const struct remote *remote)
+{
+       return (!!remote->url) || (!!remote->foreign_vcs);
+}
+
 static const char *alias_url(const char *url, struct rewrites *r)
 {
        int i, j;
@@ -441,6 +446,8 @@ static int handle_config(const char *key, const char *value, void *cb)
        } else if (!strcmp(subkey, ".proxy")) {
                return git_config_string((const char **)&remote->http_proxy,
                                         key, value);
+       } else if (!strcmp(subkey, ".vcs")) {
+               return git_config_string(&remote->foreign_vcs, key, value);
        }
        return 0;
 }
@@ -668,6 +675,16 @@ static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
        return parse_refspec_internal(nr_refspec, refspec, 0, 0);
 }
 
+void free_refspec(int nr_refspec, struct refspec *refspec)
+{
+       int i;
+       for (i = 0; i < nr_refspec; i++) {
+               free(refspec[i].src);
+               free(refspec[i].dst);
+       }
+       free(refspec);
+}
+
 static int valid_remote_nick(const char *name)
 {
        if (!name[0] || is_dot_or_dotdot(name))
@@ -690,14 +707,14 @@ struct remote *remote_get(const char *name)
 
        ret = make_remote(name, 0);
        if (valid_remote_nick(name)) {
-               if (!ret->url)
+               if (!valid_remote(ret))
                        read_remotes_file(ret);
-               if (!ret->url)
+               if (!valid_remote(ret))
                        read_branches_file(ret);
        }
-       if (name_given && !ret->url)
+       if (name_given && !valid_remote(ret))
                add_url_alias(ret, name);
-       if (!ret->url)
+       if (!valid_remote(ret))
                return NULL;
        ret->fetch = parse_fetch_refspec(ret->fetch_refspec_nr, ret->fetch_refspec);
        ret->push = parse_push_refspec(ret->push_refspec_nr, ret->push_refspec);
@@ -810,6 +827,23 @@ static int match_name_with_pattern(const char *key, const char *name,
        return ret;
 }
 
+char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
+                    const char *name)
+{
+       int i;
+       char *ret = NULL;
+       for (i = 0; i < nr_refspec; i++) {
+               struct refspec *refspec = refspecs + i;
+               if (refspec->pattern) {
+                       if (match_name_with_pattern(refspec->src, name,
+                                                   refspec->dst, &ret))
+                               return ret;
+               } else if (!strcmp(refspec->src, name))
+                       return strdup(refspec->dst);
+       }
+       return NULL;
+}
+
 int remote_find_tracking(struct remote *remote, struct refspec *refspec)
 {
        int find_src = refspec->src == NULL;
@@ -1213,6 +1247,56 @@ int match_refs(struct ref *src, struct ref **dst,
        return 0;
 }
 
+void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
+       int force_update)
+{
+       struct ref *ref;
+
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (ref->peer_ref)
+                       hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
+               else if (!send_mirror)
+                       continue;
+
+               ref->deletion = is_null_sha1(ref->new_sha1);
+               if (!ref->deletion &&
+                       !hashcmp(ref->old_sha1, ref->new_sha1)) {
+                       ref->status = REF_STATUS_UPTODATE;
+                       continue;
+               }
+
+               /* This part determines what can overwrite what.
+                * The rules are:
+                *
+                * (0) you can always use --force or +A:B notation to
+                *     selectively force individual ref pairs.
+                *
+                * (1) if the old thing does not exist, it is OK.
+                *
+                * (2) if you do not have the old thing, you are not allowed
+                *     to overwrite it; you would not know what you are losing
+                *     otherwise.
+                *
+                * (3) if both new and old are commit-ish, and new is a
+                *     descendant of old, it is OK.
+                *
+                * (4) regardless of all of the above, removing :B is
+                *     always allowed.
+                */
+
+               ref->nonfastforward =
+                       !ref->deletion &&
+                       !is_null_sha1(ref->old_sha1) &&
+                       (!has_sha1_file(ref->old_sha1)
+                         || !ref_newer(ref->new_sha1, ref->old_sha1));
+
+               if (ref->nonfastforward && !ref->force && !force_update) {
+                       ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
+                       continue;
+               }
+       }
+}
+
 struct branch *branch_get(const char *name)
 {
        struct branch *ret;
index d0aba81ead1847e43a971362659abf1c1737c12f..6e13643cabb6fa9a5b619f53dd148345d9161ad4 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -11,6 +11,8 @@ struct remote {
        const char *name;
        int origin;
 
+       const char *foreign_vcs;
+
        const char **url;
        int url_nr;
        int url_alloc;
@@ -89,8 +91,15 @@ void ref_remove_duplicates(struct ref *ref_map);
 int valid_fetch_refspec(const char *refspec);
 struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
 
+void free_refspec(int nr_refspec, struct refspec *refspec);
+
+char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
+                    const char *name);
+
 int match_refs(struct ref *src, struct ref **dst,
               int nr_refspec, const char **refspec, int all);
+void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
+       int force_update);
 
 /*
  * Given a list of the remote refs and the specification of things to
index 29f95f657d04300e375d007e9d01832b89311602..d1d3e753955146cadfaf6da274487a4a369f0521 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -1,8 +1,11 @@
 #include "cache.h"
 #include "string-list.h"
 #include "rerere.h"
-#include "xdiff/xdiff.h"
 #include "xdiff-interface.h"
+#include "dir.h"
+#include "resolve-undo.h"
+#include "ll-merge.h"
+#include "attr.h"
 
 /* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
 static int rerere_enabled = -1;
@@ -83,61 +86,106 @@ static inline void ferr_puts(const char *s, FILE *fp, int *err)
        ferr_write(s, strlen(s), fp, err);
 }
 
-static int handle_file(const char *path,
-        unsigned char *sha1, const char *output)
+struct rerere_io {
+       int (*getline)(struct strbuf *, struct rerere_io *);
+       FILE *output;
+       int wrerror;
+       /* some more stuff */
+};
+
+static void rerere_io_putstr(const char *str, struct rerere_io *io)
+{
+       if (io->output)
+               ferr_puts(str, io->output, &io->wrerror);
+}
+
+static void rerere_io_putconflict(int ch, int size, struct rerere_io *io)
+{
+       char buf[64];
+
+       while (size) {
+               if (size < sizeof(buf) - 2) {
+                       memset(buf, ch, size);
+                       buf[size] = '\n';
+                       buf[size + 1] = '\0';
+                       size = 0;
+               } else {
+                       int sz = sizeof(buf) - 1;
+                       if (size <= sz)
+                               sz -= (sz - size) + 1;
+                       memset(buf, ch, sz);
+                       buf[sz] = '\0';
+                       size -= sz;
+               }
+               rerere_io_putstr(buf, io);
+       }
+}
+
+static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io)
+{
+       if (io->output)
+               ferr_write(mem, sz, io->output, &io->wrerror);
+}
+
+struct rerere_io_file {
+       struct rerere_io io;
+       FILE *input;
+};
+
+static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_)
+{
+       struct rerere_io_file *io = (struct rerere_io_file *)io_;
+       return strbuf_getwholeline(sb, io->input, '\n');
+}
+
+static int is_cmarker(char *buf, int marker_char, int marker_size, int want_sp)
+{
+       while (marker_size--)
+               if (*buf++ != marker_char)
+                       return 0;
+       if (want_sp && *buf != ' ')
+               return 0;
+       return isspace(*buf);
+}
+
+static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size)
 {
        git_SHA_CTX ctx;
-       char buf[1024];
        int hunk_no = 0;
        enum {
                RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL,
        } hunk = RR_CONTEXT;
        struct strbuf one = STRBUF_INIT, two = STRBUF_INIT;
-       FILE *f = fopen(path, "r");
-       FILE *out = NULL;
-       int wrerror = 0;
-
-       if (!f)
-               return error("Could not open %s", path);
-
-       if (output) {
-               out = fopen(output, "w");
-               if (!out) {
-                       fclose(f);
-                       return error("Could not write %s", output);
-               }
-       }
+       struct strbuf buf = STRBUF_INIT;
 
        if (sha1)
                git_SHA1_Init(&ctx);
 
-       while (fgets(buf, sizeof(buf), f)) {
-               if (!prefixcmp(buf, "<<<<<<< ")) {
+       while (!io->getline(&buf, io)) {
+               if (is_cmarker(buf.buf, '<', marker_size, 1)) {
                        if (hunk != RR_CONTEXT)
                                goto bad;
                        hunk = RR_SIDE_1;
-               } else if (!prefixcmp(buf, "|||||||") && isspace(buf[7])) {
+               } else if (is_cmarker(buf.buf, '|', marker_size, 0)) {
                        if (hunk != RR_SIDE_1)
                                goto bad;
                        hunk = RR_ORIGINAL;
-               } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) {
+               } else if (is_cmarker(buf.buf, '=', marker_size, 0)) {
                        if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
                                goto bad;
                        hunk = RR_SIDE_2;
-               } else if (!prefixcmp(buf, ">>>>>>> ")) {
+               } else if (is_cmarker(buf.buf, '>', marker_size, 1)) {
                        if (hunk != RR_SIDE_2)
                                goto bad;
                        if (strbuf_cmp(&one, &two) > 0)
                                strbuf_swap(&one, &two);
                        hunk_no++;
                        hunk = RR_CONTEXT;
-                       if (out) {
-                               ferr_puts("<<<<<<<\n", out, &wrerror);
-                               ferr_write(one.buf, one.len, out, &wrerror);
-                               ferr_puts("=======\n", out, &wrerror);
-                               ferr_write(two.buf, two.len, out, &wrerror);
-                               ferr_puts(">>>>>>>\n", out, &wrerror);
-                       }
+                       rerere_io_putconflict('<', marker_size, io);
+                       rerere_io_putmem(one.buf, one.len, io);
+                       rerere_io_putconflict('=', marker_size, io);
+                       rerere_io_putmem(two.buf, two.len, io);
+                       rerere_io_putconflict('>', marker_size, io);
                        if (sha1) {
                                git_SHA1_Update(&ctx, one.buf ? one.buf : "",
                                            one.len + 1);
@@ -147,13 +195,13 @@ static int handle_file(const char *path,
                        strbuf_reset(&one);
                        strbuf_reset(&two);
                } else if (hunk == RR_SIDE_1)
-                       strbuf_addstr(&one, buf);
+                       strbuf_addstr(&one, buf.buf);
                else if (hunk == RR_ORIGINAL)
                        ; /* discard */
                else if (hunk == RR_SIDE_2)
-                       strbuf_addstr(&two, buf);
-               else if (out)
-                       ferr_puts(buf, out, &wrerror);
+                       strbuf_addstr(&two, buf.buf);
+               else
+                       rerere_io_putstr(buf.buf, io);
                continue;
        bad:
                hunk = 99; /* force error exit */
@@ -161,23 +209,135 @@ static int handle_file(const char *path,
        }
        strbuf_release(&one);
        strbuf_release(&two);
+       strbuf_release(&buf);
 
-       fclose(f);
-       if (wrerror)
-               error("There were errors while writing %s (%s)",
-                     path, strerror(wrerror));
-       if (out && fclose(out))
-               wrerror = error("Failed to flush %s: %s",
-                               path, strerror(errno));
        if (sha1)
                git_SHA1_Final(sha1, &ctx);
-       if (hunk != RR_CONTEXT) {
+       if (hunk != RR_CONTEXT)
+               return -1;
+       return hunk_no;
+}
+
+static int handle_file(const char *path, unsigned char *sha1, const char *output)
+{
+       int hunk_no = 0;
+       struct rerere_io_file io;
+       int marker_size = ll_merge_marker_size(path);
+
+       memset(&io, 0, sizeof(io));
+       io.io.getline = rerere_file_getline;
+       io.input = fopen(path, "r");
+       io.io.wrerror = 0;
+       if (!io.input)
+               return error("Could not open %s", path);
+
+       if (output) {
+               io.io.output = fopen(output, "w");
+               if (!io.io.output) {
+                       fclose(io.input);
+                       return error("Could not write %s", output);
+               }
+       }
+
+       hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
+
+       fclose(io.input);
+       if (io.io.wrerror)
+               error("There were errors while writing %s (%s)",
+                     path, strerror(io.io.wrerror));
+       if (io.io.output && fclose(io.io.output))
+               io.io.wrerror = error("Failed to flush %s: %s",
+                                     path, strerror(errno));
+
+       if (hunk_no < 0) {
                if (output)
                        unlink_or_warn(output);
                return error("Could not parse conflict hunks in %s", path);
        }
-       if (wrerror)
+       if (io.io.wrerror)
+               return -1;
+       return hunk_no;
+}
+
+struct rerere_io_mem {
+       struct rerere_io io;
+       struct strbuf input;
+};
+
+static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)
+{
+       struct rerere_io_mem *io = (struct rerere_io_mem *)io_;
+       char *ep;
+       size_t len;
+
+       strbuf_release(sb);
+       if (!io->input.len)
+               return -1;
+       ep = strchrnul(io->input.buf, '\n');
+       if (*ep == '\n')
+               ep++;
+       len = ep - io->input.buf;
+       strbuf_add(sb, io->input.buf, len);
+       strbuf_remove(&io->input, 0, len);
+       return 0;
+}
+
+static int handle_cache(const char *path, unsigned char *sha1, const char *output)
+{
+       mmfile_t mmfile[3];
+       mmbuffer_t result = {NULL, 0};
+       struct cache_entry *ce;
+       int pos, len, i, hunk_no;
+       struct rerere_io_mem io;
+       int marker_size = ll_merge_marker_size(path);
+
+       /*
+        * Reproduce the conflicted merge in-core
+        */
+       len = strlen(path);
+       pos = cache_name_pos(path, len);
+       if (0 <= pos)
                return -1;
+       pos = -pos - 1;
+
+       for (i = 0; i < 3; i++) {
+               enum object_type type;
+               unsigned long size;
+
+               mmfile[i].size = 0;
+               mmfile[i].ptr = NULL;
+               if (active_nr <= pos)
+                       break;
+               ce = active_cache[pos++];
+               if (ce_namelen(ce) != len || memcmp(ce->name, path, len)
+                   || ce_stage(ce) != i + 1)
+                       break;
+               mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);
+               mmfile[i].size = size;
+       }
+       for (i = 0; i < 3; i++) {
+               if (!mmfile[i].ptr && !mmfile[i].size)
+                       mmfile[i].ptr = xstrdup("");
+       }
+       ll_merge(&result, path, &mmfile[0],
+                &mmfile[1], "ours",
+                &mmfile[2], "theirs", 0);
+       for (i = 0; i < 3; i++)
+               free(mmfile[i].ptr);
+
+       memset(&io, 0, sizeof(io));
+       io.io.getline = rerere_mem_getline;
+       if (output)
+               io.io.output = fopen(output, "w");
+       else
+               io.io.output = NULL;
+       strbuf_init(&io.input, 0);
+       strbuf_attach(&io.input, result.ptr, result.size, result.size);
+
+       hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
+       strbuf_release(&io.input);
+       if (io.io.output)
+               fclose(io.io.output);
        return hunk_no;
 }
 
@@ -206,7 +366,6 @@ static int merge(const char *name, const char *path)
        int ret;
        mmfile_t cur, base, other;
        mmbuffer_t result = {NULL, 0};
-       xpparam_t xpp = {XDF_NEED_MINIMAL};
 
        if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0)
                return 1;
@@ -215,8 +374,7 @@ static int merge(const char *name, const char *path)
                        read_mmfile(&base, rerere_path(name, "preimage")) ||
                        read_mmfile(&other, rerere_path(name, "postimage")))
                return 1;
-       ret = xdl_merge(&base, &cur, "", &other, "",
-                       &xpp, XDL_MERGE_ZEALOUS, &result);
+       ret = ll_merge(&result, path, &base, &cur, "", &other, "", 0);
        if (!ret) {
                FILE *f = fopen(path, "w");
                if (!f)
@@ -367,7 +525,7 @@ static int is_rerere_enabled(void)
        return 1;
 }
 
-int setup_rerere(struct string_list *merge_rr)
+int setup_rerere(struct string_list *merge_rr, int flags)
 {
        int fd;
 
@@ -375,6 +533,8 @@ int setup_rerere(struct string_list *merge_rr)
        if (!is_rerere_enabled())
                return -1;
 
+       if (flags & (RERERE_AUTOUPDATE|RERERE_NOAUTOUPDATE))
+               rerere_autoupdate = !!(flags & RERERE_AUTOUPDATE);
        merge_rr_path = git_pathdup("MERGE_RR");
        fd = hold_lock_file_for_update(&write_lock, merge_rr_path,
                                       LOCK_DIE_ON_ERROR);
@@ -382,13 +542,62 @@ int setup_rerere(struct string_list *merge_rr)
        return fd;
 }
 
-int rerere(void)
+int rerere(int flags)
 {
        struct string_list merge_rr = { NULL, 0, 0, 1 };
        int fd;
 
-       fd = setup_rerere(&merge_rr);
+       fd = setup_rerere(&merge_rr, flags);
        if (fd < 0)
                return 0;
        return do_plain_rerere(&merge_rr, fd);
 }
+
+static int rerere_forget_one_path(const char *path, struct string_list *rr)
+{
+       const char *filename;
+       char *hex;
+       unsigned char sha1[20];
+       int ret;
+
+       ret = handle_cache(path, sha1, NULL);
+       if (ret < 1)
+               return error("Could not parse conflict hunks in '%s'", path);
+       hex = xstrdup(sha1_to_hex(sha1));
+       filename = rerere_path(hex, "postimage");
+       if (unlink(filename))
+               return (errno == ENOENT
+                       ? error("no remembered resolution for %s", path)
+                       : error("cannot unlink %s: %s", filename, strerror(errno)));
+
+       handle_cache(path, sha1, rerere_path(hex, "preimage"));
+       fprintf(stderr, "Updated preimage for '%s'\n", path);
+
+
+       string_list_insert(path, rr)->util = hex;
+       fprintf(stderr, "Forgot resolution for %s\n", path);
+       return 0;
+}
+
+int rerere_forget(const char **pathspec)
+{
+       int i, fd;
+       struct string_list conflict = { NULL, 0, 0, 1 };
+       struct string_list merge_rr = { NULL, 0, 0, 1 };
+
+       if (read_cache() < 0)
+               return error("Could not read index");
+
+       fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE);
+
+       unmerge_cache(pathspec);
+       find_conflict(&conflict);
+       for (i = 0; i < conflict.nr; i++) {
+               struct string_list_item *it = &conflict.items[i];
+               if (!match_pathspec(pathspec, it->string, strlen(it->string),
+                                   0, NULL))
+                       continue;
+               rerere_forget_one_path(it->string, &merge_rr);
+       }
+       return write_rr(&merge_rr, fd);
+}
index 13313f3f2b2cc6a8d895305b7ac92c12c1753682..eaa9004dcdbb2f0dfd446e1ac68237c6dff18530 100644 (file)
--- a/rerere.h
+++ b/rerere.h
@@ -3,9 +3,16 @@
 
 #include "string-list.h"
 
-extern int setup_rerere(struct string_list *);
-extern int rerere(void);
+#define RERERE_AUTOUPDATE   01
+#define RERERE_NOAUTOUPDATE 02
+
+extern int setup_rerere(struct string_list *, int);
+extern int rerere(int);
 extern const char *rerere_path(const char *hex, const char *file);
 extern int has_rerere_resolution(const char *hex);
+extern int rerere_forget(const char **);
+
+#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \
+       "update the index with reused conflict resolution if possible")
 
 #endif
diff --git a/resolve-undo.c b/resolve-undo.c
new file mode 100644 (file)
index 0000000..0f50ee0
--- /dev/null
@@ -0,0 +1,176 @@
+#include "cache.h"
+#include "dir.h"
+#include "resolve-undo.h"
+#include "string-list.h"
+
+/* The only error case is to run out of memory in string-list */
+void record_resolve_undo(struct index_state *istate, struct cache_entry *ce)
+{
+       struct string_list_item *lost;
+       struct resolve_undo_info *ui;
+       struct string_list *resolve_undo;
+       int stage = ce_stage(ce);
+
+       if (!stage)
+               return;
+
+       if (!istate->resolve_undo) {
+               resolve_undo = xcalloc(1, sizeof(*resolve_undo));
+               resolve_undo->strdup_strings = 1;
+               istate->resolve_undo = resolve_undo;
+       }
+       resolve_undo = istate->resolve_undo;
+       lost = string_list_insert(ce->name, resolve_undo);
+       if (!lost->util)
+               lost->util = xcalloc(1, sizeof(*ui));
+       ui = lost->util;
+       hashcpy(ui->sha1[stage - 1], ce->sha1);
+       ui->mode[stage - 1] = ce->ce_mode;
+}
+
+static int write_one(struct string_list_item *item, void *cbdata)
+{
+       struct strbuf *sb = cbdata;
+       struct resolve_undo_info *ui = item->util;
+       int i;
+
+       if (!ui)
+               return 0;
+       strbuf_addstr(sb, item->string);
+       strbuf_addch(sb, 0);
+       for (i = 0; i < 3; i++)
+               strbuf_addf(sb, "%o%c", ui->mode[i], 0);
+       for (i = 0; i < 3; i++) {
+               if (!ui->mode[i])
+                       continue;
+               strbuf_add(sb, ui->sha1[i], 20);
+       }
+       return 0;
+}
+
+void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo)
+{
+       for_each_string_list(write_one, resolve_undo, sb);
+}
+
+struct string_list *resolve_undo_read(const char *data, unsigned long size)
+{
+       struct string_list *resolve_undo;
+       size_t len;
+       char *endptr;
+       int i;
+
+       resolve_undo = xcalloc(1, sizeof(*resolve_undo));
+       resolve_undo->strdup_strings = 1;
+
+       while (size) {
+               struct string_list_item *lost;
+               struct resolve_undo_info *ui;
+
+               len = strlen(data) + 1;
+               if (size <= len)
+                       goto error;
+               lost = string_list_insert(data, resolve_undo);
+               if (!lost->util)
+                       lost->util = xcalloc(1, sizeof(*ui));
+               ui = lost->util;
+               size -= len;
+               data += len;
+
+               for (i = 0; i < 3; i++) {
+                       ui->mode[i] = strtoul(data, &endptr, 8);
+                       if (!endptr || endptr == data || *endptr)
+                               goto error;
+                       len = (endptr + 1) - (char*)data;
+                       if (size <= len)
+                               goto error;
+                       size -= len;
+                       data += len;
+               }
+
+               for (i = 0; i < 3; i++) {
+                       if (!ui->mode[i])
+                               continue;
+                       if (size < 20)
+                               goto error;
+                       hashcpy(ui->sha1[i], (const unsigned char *)data);
+                       size -= 20;
+                       data += 20;
+               }
+       }
+       return resolve_undo;
+
+error:
+       string_list_clear(resolve_undo, 1);
+       error("Index records invalid resolve-undo information");
+       return NULL;
+}
+
+void resolve_undo_clear_index(struct index_state *istate)
+{
+       struct string_list *resolve_undo = istate->resolve_undo;
+       if (!resolve_undo)
+               return;
+       string_list_clear(resolve_undo, 1);
+       free(resolve_undo);
+       istate->resolve_undo = NULL;
+       istate->cache_changed = 1;
+}
+
+int unmerge_index_entry_at(struct index_state *istate, int pos)
+{
+       struct cache_entry *ce;
+       struct string_list_item *item;
+       struct resolve_undo_info *ru;
+       int i, err = 0;
+
+       if (!istate->resolve_undo)
+               return pos;
+
+       ce = istate->cache[pos];
+       if (ce_stage(ce)) {
+               /* already unmerged */
+               while ((pos < istate->cache_nr) &&
+                      ! strcmp(istate->cache[pos]->name, ce->name))
+                       pos++;
+               return pos - 1; /* return the last entry processed */
+       }
+       item = string_list_lookup(ce->name, istate->resolve_undo);
+       if (!item)
+               return pos;
+       ru = item->util;
+       if (!ru)
+               return pos;
+       remove_index_entry_at(istate, pos);
+       for (i = 0; i < 3; i++) {
+               struct cache_entry *nce;
+               if (!ru->mode[i])
+                       continue;
+               nce = make_cache_entry(ru->mode[i], ru->sha1[i],
+                                      ce->name, i + 1, 0);
+               if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) {
+                       err = 1;
+                       error("cannot unmerge '%s'", ce->name);
+               }
+       }
+       if (err)
+               return pos;
+       free(ru);
+       item->util = NULL;
+       return unmerge_index_entry_at(istate, pos);
+}
+
+void unmerge_index(struct index_state *istate, const char **pathspec)
+{
+       int i;
+
+       if (!istate->resolve_undo)
+               return;
+
+       for (i = 0; i < istate->cache_nr; i++) {
+               struct cache_entry *ce = istate->cache[i];
+               if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL))
+                       continue;
+               i = unmerge_index_entry_at(istate, i);
+       }
+}
diff --git a/resolve-undo.h b/resolve-undo.h
new file mode 100644 (file)
index 0000000..8458769
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef RESOLVE_UNDO_H
+#define RESOLVE_UNDO_H
+
+struct resolve_undo_info {
+       unsigned int mode[3];
+       unsigned char sha1[3][20];
+};
+
+extern void record_resolve_undo(struct index_state *, struct cache_entry *);
+extern void resolve_undo_write(struct strbuf *, struct string_list *);
+extern struct string_list *resolve_undo_read(const char *, unsigned long);
+extern void resolve_undo_clear_index(struct index_state *);
+extern int unmerge_index_entry_at(struct index_state *, int);
+extern void unmerge_index(struct index_state *, const char **);
+
+#endif
index 34f9ab98d1872770ef4d54014c31ece6eb7c8c39..3ba6d991f6e9789949c314c2981dfc6b208a6f66 100644 (file)
@@ -134,10 +134,20 @@ static void add_pending_object_with_mode(struct rev_info *revs, struct object *o
 {
        if (revs->no_walk && (obj->flags & UNINTERESTING))
                revs->no_walk = 0;
-       if (revs->reflog_info && obj->type == OBJ_COMMIT &&
-                       add_reflog_for_walk(revs->reflog_info,
-                               (struct commit *)obj, name))
-               return;
+       if (revs->reflog_info && obj->type == OBJ_COMMIT) {
+               struct strbuf buf = STRBUF_INIT;
+               int len = interpret_branch_name(name, &buf);
+               int st;
+
+               if (0 < len && name[len] && buf.len)
+                       strbuf_addstr(&buf, name + len);
+               st = add_reflog_for_walk(revs->reflog_info,
+                                        (struct commit *)obj,
+                                        buf.buf[0] ? buf.buf: name);
+               strbuf_release(&buf);
+               if (st)
+                       return;
+       }
        add_object_array_with_mode(obj, name, &revs->pending, mode);
 }
 
@@ -268,7 +278,7 @@ static int tree_difference = REV_TREE_SAME;
 static void file_add_remove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *fullpath)
+                   const char *fullpath, unsigned dirty_submodule)
 {
        int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD;
 
@@ -281,7 +291,8 @@ static void file_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
-                const char *fullpath)
+                const char *fullpath,
+                unsigned old_dirty_submodule, unsigned new_dirty_submodule)
 {
        tree_difference = REV_TREE_DIFFERENT;
        DIFF_OPT_SET(options, HAS_CHANGES);
@@ -699,12 +710,18 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag,
        return 0;
 }
 
+static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs,
+       unsigned flags)
+{
+       cb->all_revs = revs;
+       cb->all_flags = flags;
+}
+
 static void handle_refs(struct rev_info *revs, unsigned flags,
                int (*for_each)(each_ref_fn, void *))
 {
        struct all_refs_cb cb;
-       cb.all_revs = revs;
-       cb.all_flags = flags;
+       init_all_refs_cb(&cb, revs, flags);
        for_each(handle_one_ref, &cb);
 }
 
@@ -791,7 +808,7 @@ void init_revisions(struct rev_info *revs, const char *prefix)
        revs->ignore_merges = 1;
        revs->simplify_history = 1;
        DIFF_OPT_SET(&revs->pruning, RECURSIVE);
-       DIFF_OPT_SET(&revs->pruning, QUIET);
+       DIFF_OPT_SET(&revs->pruning, QUICK);
        revs->pruning.add_remove = file_add_remove;
        revs->pruning.change = file_change;
        revs->lifo = 1;
@@ -1361,6 +1378,30 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                handle_refs(revs, flags, for_each_remote_ref);
                                continue;
                        }
+                       if (!prefixcmp(arg, "--glob=")) {
+                               struct all_refs_cb cb;
+                               init_all_refs_cb(&cb, revs, flags);
+                               for_each_glob_ref(handle_one_ref, arg + 7, &cb);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--branches=")) {
+                               struct all_refs_cb cb;
+                               init_all_refs_cb(&cb, revs, flags);
+                               for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--tags=")) {
+                               struct all_refs_cb cb;
+                               init_all_refs_cb(&cb, revs, flags);
+                               for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb);
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--remotes=")) {
+                               struct all_refs_cb cb;
+                               init_all_refs_cb(&cb, revs, flags);
+                               for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb);
+                               continue;
+                       }
                        if (!strcmp(arg, "--reflog")) {
                                handle_reflog(revs, flags);
                                continue;
index cf2d8f7fae1356e50736cb9d599625df79738a2a..2feb493951322617692085998ac8507cdba9dd30 100644 (file)
@@ -8,12 +8,130 @@ static inline void close_pair(int fd[2])
        close(fd[1]);
 }
 
+#ifndef WIN32
 static inline void dup_devnull(int to)
 {
        int fd = open("/dev/null", O_RDWR);
        dup2(fd, to);
        close(fd);
 }
+#endif
+
+static const char **prepare_shell_cmd(const char **argv)
+{
+       int argc, nargc = 0;
+       const char **nargv;
+
+       for (argc = 0; argv[argc]; argc++)
+               ; /* just counting */
+       /* +1 for NULL, +3 for "sh -c" plus extra $0 */
+       nargv = xmalloc(sizeof(*nargv) * (argc + 1 + 3));
+
+       if (argc < 1)
+               die("BUG: shell command is empty");
+
+       if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) {
+               nargv[nargc++] = "sh";
+               nargv[nargc++] = "-c";
+
+               if (argc < 2)
+                       nargv[nargc++] = argv[0];
+               else {
+                       struct strbuf arg0 = STRBUF_INIT;
+                       strbuf_addf(&arg0, "%s \"$@\"", argv[0]);
+                       nargv[nargc++] = strbuf_detach(&arg0, NULL);
+               }
+       }
+
+       for (argc = 0; argv[argc]; argc++)
+               nargv[nargc++] = argv[argc];
+       nargv[nargc] = NULL;
+
+       return nargv;
+}
+
+#ifndef WIN32
+static int execv_shell_cmd(const char **argv)
+{
+       const char **nargv = prepare_shell_cmd(argv);
+       trace_argv_printf(nargv, "trace: exec:");
+       execvp(nargv[0], (char **)nargv);
+       free(nargv);
+       return -1;
+}
+#endif
+
+#ifndef WIN32
+static int child_err = 2;
+static int child_notifier = -1;
+
+static void notify_parent(void)
+{
+       write(child_notifier, "", 1);
+}
+
+static NORETURN void die_child(const char *err, va_list params)
+{
+       char msg[4096];
+       int len = vsnprintf(msg, sizeof(msg), err, params);
+       if (len > sizeof(msg))
+               len = sizeof(msg);
+
+       write(child_err, "fatal: ", 7);
+       write(child_err, msg, len);
+       write(child_err, "\n", 1);
+       exit(128);
+}
+
+static inline void set_cloexec(int fd)
+{
+       int flags = fcntl(fd, F_GETFD);
+       if (flags >= 0)
+               fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
+}
+#endif
+
+static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
+{
+       int status, code = -1;
+       pid_t waiting;
+       int failed_errno = 0;
+
+       while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
+               ;       /* nothing */
+
+       if (waiting < 0) {
+               failed_errno = errno;
+               error("waitpid for %s failed: %s", argv0, strerror(errno));
+       } else if (waiting != pid) {
+               error("waitpid is confused (%s)", argv0);
+       } else if (WIFSIGNALED(status)) {
+               code = WTERMSIG(status);
+               error("%s died of signal %d", argv0, code);
+               /*
+                * This return value is chosen so that code & 0xff
+                * mimics the exit code that a POSIX shell would report for
+                * a program that died from this signal.
+                */
+               code -= 128;
+       } else if (WIFEXITED(status)) {
+               code = WEXITSTATUS(status);
+               /*
+                * Convert special exit code when execvp failed.
+                */
+               if (code == 127) {
+                       code = -1;
+                       failed_errno = ENOENT;
+                       if (!silent_exec_failure)
+                               error("cannot run %s: %s", argv0,
+                                       strerror(ENOENT));
+               }
+       } else {
+               error("waitpid is confused (%s)", argv0);
+       }
+       errno = failed_errno;
+       return code;
+}
 
 int start_command(struct child_process *cmd)
 {
@@ -76,9 +194,30 @@ fail_pipe:
        trace_argv_printf(cmd->argv, "trace: run_command:");
 
 #ifndef WIN32
+{
+       int notify_pipe[2];
+       if (pipe(notify_pipe))
+               notify_pipe[0] = notify_pipe[1] = -1;
+
        fflush(NULL);
        cmd->pid = fork();
        if (!cmd->pid) {
+               /*
+                * Redirect the channel to write syscall error messages to
+                * before redirecting the process's stderr so that all die()
+                * in subsequent call paths use the parent's stderr.
+                */
+               if (cmd->no_stderr || need_err) {
+                       child_err = dup(2);
+                       set_cloexec(child_err);
+               }
+               set_die_routine(die_child);
+
+               close(notify_pipe[0]);
+               set_cloexec(notify_pipe[1]);
+               child_notifier = notify_pipe[1];
+               atexit(notify_parent);
+
                if (cmd->no_stdin)
                        dup_devnull(0);
                else if (need_in) {
@@ -119,58 +258,82 @@ fail_pipe:
                                        unsetenv(*cmd->env);
                        }
                }
-               if (cmd->preexec_cb)
+               if (cmd->preexec_cb) {
+                       /*
+                        * We cannot predict what the pre-exec callback does.
+                        * Forgo parent notification.
+                        */
+                       close(child_notifier);
+                       child_notifier = -1;
+
                        cmd->preexec_cb();
+               }
                if (cmd->git_cmd) {
                        execv_git_cmd(cmd->argv);
+               } else if (cmd->use_shell) {
+                       execv_shell_cmd(cmd->argv);
                } else {
                        execvp(cmd->argv[0], (char *const*) cmd->argv);
                }
-               trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0],
-                               strerror(errno));
-               exit(127);
+               /*
+                * Do not check for cmd->silent_exec_failure; the parent
+                * process will check it when it sees this exit code.
+                */
+               if (errno == ENOENT)
+                       exit(127);
+               else
+                       die_errno("cannot exec '%s'", cmd->argv[0]);
        }
        if (cmd->pid < 0)
                error("cannot fork() for %s: %s", cmd->argv[0],
                        strerror(failed_errno = errno));
+
+       /*
+        * Wait for child's execvp. If the execvp succeeds (or if fork()
+        * failed), EOF is seen immediately by the parent. Otherwise, the
+        * child process sends a single byte.
+        * Note that use of this infrastructure is completely advisory,
+        * therefore, we keep error checks minimal.
+        */
+       close(notify_pipe[1]);
+       if (read(notify_pipe[0], &notify_pipe[1], 1) == 1) {
+               /*
+                * At this point we know that fork() succeeded, but execvp()
+                * failed. Errors have been reported to our stderr.
+                */
+               wait_or_whine(cmd->pid, cmd->argv[0],
+                             cmd->silent_exec_failure);
+               failed_errno = errno;
+               cmd->pid = -1;
+       }
+       close(notify_pipe[0]);
+}
 #else
 {
-       int s0 = -1, s1 = -1, s2 = -1;  /* backups of stdin, stdout, stderr */
+       int fhin = 0, fhout = 1, fherr = 2;
        const char **sargv = cmd->argv;
        char **env = environ;
 
-       if (cmd->no_stdin) {
-               s0 = dup(0);
-               dup_devnull(0);
-       } else if (need_in) {
-               s0 = dup(0);
-               dup2(fdin[0], 0);
-       } else if (cmd->in) {
-               s0 = dup(0);
-               dup2(cmd->in, 0);
-       }
-
-       if (cmd->no_stderr) {
-               s2 = dup(2);
-               dup_devnull(2);
-       } else if (need_err) {
-               s2 = dup(2);
-               dup2(fderr[1], 2);
-       }
-
-       if (cmd->no_stdout) {
-               s1 = dup(1);
-               dup_devnull(1);
-       } else if (cmd->stdout_to_stderr) {
-               s1 = dup(1);
-               dup2(2, 1);
-       } else if (need_out) {
-               s1 = dup(1);
-               dup2(fdout[1], 1);
-       } else if (cmd->out > 1) {
-               s1 = dup(1);
-               dup2(cmd->out, 1);
-       }
+       if (cmd->no_stdin)
+               fhin = open("/dev/null", O_RDWR);
+       else if (need_in)
+               fhin = dup(fdin[0]);
+       else if (cmd->in)
+               fhin = dup(cmd->in);
+
+       if (cmd->no_stderr)
+               fherr = open("/dev/null", O_RDWR);
+       else if (need_err)
+               fherr = dup(fderr[1]);
+
+       if (cmd->no_stdout)
+               fhout = open("/dev/null", O_RDWR);
+       else if (cmd->stdout_to_stderr)
+               fhout = dup(fherr);
+       else if (need_out)
+               fhout = dup(fdout[1]);
+       else if (cmd->out > 1)
+               fhout = dup(cmd->out);
 
        if (cmd->dir)
                die("chdir in start_command() not implemented");
@@ -179,9 +342,12 @@ fail_pipe:
 
        if (cmd->git_cmd) {
                cmd->argv = prepare_git_cmd(cmd->argv);
+       } else if (cmd->use_shell) {
+               cmd->argv = prepare_shell_cmd(cmd->argv);
        }
 
-       cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env);
+       cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env,
+                                 fhin, fhout, fherr);
        failed_errno = errno;
        if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))
                error("cannot spawn %s: %s", cmd->argv[0], strerror(errno));
@@ -192,12 +358,12 @@ fail_pipe:
                free(cmd->argv);
 
        cmd->argv = sargv;
-       if (s0 >= 0)
-               dup2(s0, 0), close(s0);
-       if (s1 >= 0)
-               dup2(s1, 1), close(s1);
-       if (s2 >= 0)
-               dup2(s2, 2), close(s2);
+       if (fhin != 0)
+               close(fhin);
+       if (fhout != 1)
+               close(fhout);
+       if (fherr != 2)
+               close(fherr);
 }
 #endif
 
@@ -232,48 +398,6 @@ fail_pipe:
        return 0;
 }
 
-static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
-{
-       int status, code = -1;
-       pid_t waiting;
-       int failed_errno = 0;
-
-       while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
-               ;       /* nothing */
-
-       if (waiting < 0) {
-               failed_errno = errno;
-               error("waitpid for %s failed: %s", argv0, strerror(errno));
-       } else if (waiting != pid) {
-               error("waitpid is confused (%s)", argv0);
-       } else if (WIFSIGNALED(status)) {
-               code = WTERMSIG(status);
-               error("%s died of signal %d", argv0, code);
-               /*
-                * This return value is chosen so that code & 0xff
-                * mimics the exit code that a POSIX shell would report for
-                * a program that died from this signal.
-                */
-               code -= 128;
-       } else if (WIFEXITED(status)) {
-               code = WEXITSTATUS(status);
-               /*
-                * Convert special exit code when execvp failed.
-                */
-               if (code == 127) {
-                       code = -1;
-                       failed_errno = ENOENT;
-                       if (!silent_exec_failure)
-                               error("cannot run %s: %s", argv0,
-                                       strerror(ENOENT));
-               }
-       } else {
-               error("waitpid is confused (%s)", argv0);
-       }
-       errno = failed_errno;
-       return code;
-}
-
 int finish_command(struct child_process *cmd)
 {
        return wait_or_whine(cmd->pid, cmd->argv[0], cmd->silent_exec_failure);
@@ -297,6 +421,7 @@ static void prepare_run_command_v_opt(struct child_process *cmd,
        cmd->git_cmd = opt & RUN_GIT_CMD ? 1 : 0;
        cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
        cmd->silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0;
+       cmd->use_shell = opt & RUN_USING_SHELL ? 1 : 0;
 }
 
 int run_command_v_opt(const char **argv, int opt)
index fb342090e3cac49f41689f9610bfe2d6c87c87c1..967ba8cc09786934724132b629587419f195b245 100644 (file)
@@ -33,6 +33,7 @@ struct child_process {
        unsigned git_cmd:1; /* if this is to be git sub-command */
        unsigned silent_exec_failure:1;
        unsigned stdout_to_stderr:1;
+       unsigned use_shell:1;
        void (*preexec_cb)(void);
 };
 
@@ -46,6 +47,7 @@ extern int run_hook(const char *index_file, const char *name, ...);
 #define RUN_GIT_CMD         2  /*If this is to be git sub-command */
 #define RUN_COMMAND_STDOUT_TO_STDERR 4
 #define RUN_SILENT_EXEC_FAILURE 8
+#define RUN_USING_SHELL 16
 int run_command_v_opt(const char **argv, int opt);
 
 /*
diff --git a/setup.c b/setup.c
index 2cf0f1993718fd737cd87e79d2f4950832c59c9f..710e2f3008c79c08cdc507288881c9a58311283a 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -77,6 +77,18 @@ int check_filename(const char *prefix, const char *arg)
        die_errno("failed to stat '%s'", arg);
 }
 
+static void NORETURN die_verify_filename(const char *prefix, const char *arg)
+{
+       unsigned char sha1[20];
+       unsigned mode;
+       /* try a detailed diagnostic ... */
+       get_sha1_with_mode_1(arg, sha1, &mode, 0, prefix);
+       /* ... or fall back the most general message. */
+       die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
+           "Use '--' to separate paths from revisions", arg);
+
+}
+
 /*
  * Verify a filename that we got as an argument for a pathspec
  * entry. Note that a filename that begins with "-" never verifies
@@ -90,8 +102,7 @@ void verify_filename(const char *prefix, const char *arg)
                die("bad flag '%s' used after filename", arg);
        if (check_filename(prefix, arg))
                return;
-       die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
-           "Use '--' to separate paths from revisions", arg);
+       die_verify_filename(prefix, arg);
 }
 
 /*
@@ -252,6 +263,8 @@ static int check_repository_format_gently(int *nongit_ok)
 const char *read_gitfile_gently(const char *path)
 {
        char *buf;
+       char *dir;
+       const char *slash;
        struct stat st;
        int fd;
        size_t len;
@@ -276,9 +289,23 @@ const char *read_gitfile_gently(const char *path)
        if (len < 9)
                die("No path in gitfile: %s", path);
        buf[len] = '\0';
-       if (!is_git_directory(buf + 8))
-               die("Not a git repository: %s", buf + 8);
-       path = make_absolute_path(buf + 8);
+       dir = buf + 8;
+
+       if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) {
+               size_t pathlen = slash+1 - path;
+               size_t dirlen = pathlen + len - 8;
+               dir = xmalloc(dirlen + 1);
+               strncpy(dir, path, pathlen);
+               strncpy(dir + pathlen, buf + 8, len - 8);
+               dir[dirlen] = '\0';
+               free(buf);
+               buf = dir;
+       }
+
+       if (!is_git_directory(dir))
+               die("Not a git repository: %s", dir);
+       path = make_absolute_path(dir);
+
        free(buf);
        return path;
 }
index 63981fb3fd9cfa6cca4126eba5a964b449c62444..657825e14ef78c19649779fe89e1d09ae672901a 100644 (file)
@@ -35,54 +35,6 @@ static size_t sz_fmt(size_t s) { return s; }
 
 const unsigned char null_sha1[20];
 
-const signed char hexval_table[256] = {
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 00-07 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 08-0f */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 10-17 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 18-1f */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 20-27 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 28-2f */
-         0,  1,  2,  3,  4,  5,  6,  7,                /* 30-37 */
-         8,  9, -1, -1, -1, -1, -1, -1,                /* 38-3f */
-        -1, 10, 11, 12, 13, 14, 15, -1,                /* 40-47 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 48-4f */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 50-57 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 58-5f */
-        -1, 10, 11, 12, 13, 14, 15, -1,                /* 60-67 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 68-67 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 70-77 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 78-7f */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 80-87 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 88-8f */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 90-97 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* 98-9f */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* a0-a7 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* a8-af */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* b0-b7 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* b8-bf */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* c0-c7 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* c8-cf */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* d0-d7 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* d8-df */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* e0-e7 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* e8-ef */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* f0-f7 */
-        -1, -1, -1, -1, -1, -1, -1, -1,                /* f8-ff */
-};
-
-int get_sha1_hex(const char *hex, unsigned char *sha1)
-{
-       int i;
-       for (i = 0; i < 20; i++) {
-               unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
-               if (val & ~0xff)
-                       return -1;
-               *sha1++ = val;
-               hex += 2;
-       }
-       return 0;
-}
-
 static inline int offset_1st_component(const char *path)
 {
        if (has_dos_drive_prefix(path))
@@ -133,24 +85,6 @@ int safe_create_leading_directories_const(const char *path)
        return result;
 }
 
-char *sha1_to_hex(const unsigned char *sha1)
-{
-       static int bufno;
-       static char hexbuffer[4][50];
-       static const char hex[] = "0123456789abcdef";
-       char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
-       int i;
-
-       for (i = 0; i < 20; i++) {
-               unsigned int val = *sha1++;
-               *buf++ = hex[val >> 4];
-               *buf++ = hex[val & 0xf];
-       }
-       *buf = '\0';
-
-       return buffer;
-}
-
 static void fill_sha1_path(char *pathbuf, const unsigned char *sha1)
 {
        int i;
@@ -1232,7 +1166,7 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon
 static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
 {
        int bytes = strlen(buffer) + 1;
-       unsigned char *buf = xmalloc(1+size);
+       unsigned char *buf = xmallocz(size);
        unsigned long n;
        int status = Z_OK;
 
@@ -1260,7 +1194,6 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size
                while (status == Z_OK)
                        status = git_inflate(stream, Z_FINISH);
        }
-       buf[size] = 0;
        if (status == Z_STREAM_END && !stream->avail_in) {
                git_inflate_end(stream);
                return buf;
@@ -1583,8 +1516,7 @@ static void *unpack_compressed_entry(struct packed_git *p,
        z_stream stream;
        unsigned char *buffer, *in;
 
-       buffer = xmalloc(size + 1);
-       buffer[size] = 0;
+       buffer = xmallocz(size);
        memset(&stream, 0, sizeof(stream));
        stream.next_out = buffer;
        stream.avail_out = size + 1;
@@ -2458,14 +2390,6 @@ int has_pack_index(const unsigned char *sha1)
        return 1;
 }
 
-int has_pack_file(const unsigned char *sha1)
-{
-       struct stat st;
-       if (stat(sha1_pack_name(sha1), &st))
-               return 0;
-       return 1;
-}
-
 int has_sha1_pack(const unsigned char *sha1)
 {
        struct pack_entry e;
index 44bb62d270739a232e87c90c05ce89fcc86bc15b..77299257bf3aa91079d5b883c6676afa6fd2d01c 100644 (file)
@@ -5,6 +5,7 @@
 #include "blob.h"
 #include "tree-walk.h"
 #include "refs.h"
+#include "remote.h"
 
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
@@ -240,7 +241,8 @@ static int ambiguous_path(const char *path, int len)
 
 /*
  * *string and *len will only be substituted, and *string returned (for
- * later free()ing) if the string passed in is of the form @{-<n>}.
+ * later free()ing) if the string passed in is a magic short-hand form
+ * to name a branch.
  */
 static char *substitute_branch_name(const char **string, int *len)
 {
@@ -323,6 +325,20 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
        return logs_found;
 }
 
+static inline int upstream_mark(const char *string, int len)
+{
+       const char *suffix[] = { "@{upstream}", "@{u}" };
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(suffix); i++) {
+               int suffix_len = strlen(suffix[i]);
+               if (suffix_len <= len
+                   && !memcmp(string, suffix[i], suffix_len))
+                       return suffix_len;
+       }
+       return 0;
+}
+
 static int get_sha1_1(const char *name, int len, unsigned char *sha1);
 
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
@@ -340,8 +356,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        if (len && str[len-1] == '}') {
                for (at = len-2; at >= 0; at--) {
                        if (str[at] == '@' && str[at+1] == '{') {
-                               reflog_len = (len-1) - (at+2);
-                               len = at;
+                               if (!upstream_mark(str + at, len - at)) {
+                                       reflog_len = (len-1) - (at+2);
+                                       len = at;
+                               }
                                break;
                        }
                }
@@ -381,6 +399,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                unsigned long co_time;
                int co_tz, co_cnt;
 
+               /* a @{-N} placed anywhere except the start is an error */
+               if (str[at+2] == '-')
+                       return -1;
+
                /* Is it asking for N-th entry, or approxidate? */
                for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
                        char ch = str[at+2+i];
@@ -395,9 +417,12 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                } else if (0 <= nth)
                        at_time = 0;
                else {
+                       int errors = 0;
                        char *tmp = xstrndup(str + at + 2, reflog_len);
-                       at_time = approxidate(tmp);
+                       at_time = approxidate_careful(tmp, &errors);
                        free(tmp);
+                       if (errors)
+                               return -1;
                }
                if (read_ref_at(real_ref, at_time, nth, sha1, NULL,
                                &co_time, &co_tz, &co_cnt)) {
@@ -740,17 +765,10 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
 }
 
 /*
- * This reads "@{-N}" syntax, finds the name of the Nth previous
- * branch we were on, and places the name of the branch in the given
- * buf and returns the number of characters parsed if successful.
- *
- * If the input is not of the accepted format, it returns a negative
- * number to signal an error.
- *
- * If the input was ok but there are not N branch switches in the
- * reflog, it returns 0.
+ * Parse @{-N} syntax, return the number of characters parsed
+ * if successful; otherwise signal an error with negative value.
  */
-int interpret_branch_name(const char *name, struct strbuf *buf)
+static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
 {
        long nth;
        int i, retval;
@@ -794,6 +812,122 @@ release_return:
        return retval;
 }
 
+int get_sha1_mb(const char *name, unsigned char *sha1)
+{
+       struct commit *one, *two;
+       struct commit_list *mbs;
+       unsigned char sha1_tmp[20];
+       const char *dots;
+       int st;
+
+       dots = strstr(name, "...");
+       if (!dots)
+               return get_sha1(name, sha1);
+       if (dots == name)
+               st = get_sha1("HEAD", sha1_tmp);
+       else {
+               struct strbuf sb;
+               strbuf_init(&sb, dots - name);
+               strbuf_add(&sb, name, dots - name);
+               st = get_sha1(sb.buf, sha1_tmp);
+               strbuf_release(&sb);
+       }
+       if (st)
+               return st;
+       one = lookup_commit_reference_gently(sha1_tmp, 0);
+       if (!one)
+               return -1;
+
+       if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp))
+               return -1;
+       two = lookup_commit_reference_gently(sha1_tmp, 0);
+       if (!two)
+               return -1;
+       mbs = get_merge_bases(one, two, 1);
+       if (!mbs || mbs->next)
+               st = -1;
+       else {
+               st = 0;
+               hashcpy(sha1, mbs->item->object.sha1);
+       }
+       free_commit_list(mbs);
+       return st;
+}
+
+/*
+ * This reads short-hand syntax that not only evaluates to a commit
+ * object name, but also can act as if the end user spelled the name
+ * of the branch from the command line.
+ *
+ * - "@{-N}" finds the name of the Nth previous branch we were on, and
+ *   places the name of the branch in the given buf and returns the
+ *   number of characters parsed if successful.
+ *
+ * - "<branch>@{upstream}" finds the name of the other ref that
+ *   <branch> is configured to merge with (missing <branch> defaults
+ *   to the current branch), and places the name of the branch in the
+ *   given buf and returns the number of characters parsed if
+ *   successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
+ */
+int interpret_branch_name(const char *name, struct strbuf *buf)
+{
+       char *cp;
+       struct branch *upstream;
+       int namelen = strlen(name);
+       int len = interpret_nth_prior_checkout(name, buf);
+       int tmp_len;
+
+       if (!len)
+               return len; /* syntax Ok, not enough switches */
+       if (0 < len && len == namelen)
+               return len; /* consumed all */
+       else if (0 < len) {
+               /* we have extra data, which might need further processing */
+               struct strbuf tmp = STRBUF_INIT;
+               int used = buf->len;
+               int ret;
+
+               strbuf_add(buf, name + len, namelen - len);
+               ret = interpret_branch_name(buf->buf, &tmp);
+               /* that data was not interpreted, remove our cruft */
+               if (ret < 0) {
+                       strbuf_setlen(buf, used);
+                       return len;
+               }
+               strbuf_reset(buf);
+               strbuf_addbuf(buf, &tmp);
+               strbuf_release(&tmp);
+               /* tweak for size of {-N} versus expanded ref name */
+               return ret - used + len;
+       }
+
+       cp = strchr(name, '@');
+       if (!cp)
+               return -1;
+       tmp_len = upstream_mark(cp, namelen - (cp - name));
+       if (!tmp_len)
+               return -1;
+       len = cp + tmp_len - name;
+       cp = xstrndup(name, cp - name);
+       upstream = branch_get(*cp ? cp : NULL);
+       if (!upstream
+           || !upstream->merge
+           || !upstream->merge[0]->dst)
+               return error("No upstream branch found for '%s'", cp);
+       free(cp);
+       cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
+       strbuf_reset(buf);
+       strbuf_addstr(buf, cp);
+       free(cp);
+       return len;
+}
+
 /*
  * This is like "get_sha1_basic()", except it allows "sha1 expressions",
  * notably "xyz^" for "parent of xyz"
@@ -804,7 +938,96 @@ int get_sha1(const char *name, unsigned char *sha1)
        return get_sha1_with_mode(name, sha1, &unused);
 }
 
-int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
+/* Must be called only when object_name:filename doesn't exist. */
+static void diagnose_invalid_sha1_path(const char *prefix,
+                                      const char *filename,
+                                      const unsigned char *tree_sha1,
+                                      const char *object_name)
+{
+       struct stat st;
+       unsigned char sha1[20];
+       unsigned mode;
+
+       if (!prefix)
+               prefix = "";
+
+       if (!lstat(filename, &st))
+               die("Path '%s' exists on disk, but not in '%s'.",
+                   filename, object_name);
+       if (errno == ENOENT || errno == ENOTDIR) {
+               char *fullname = xmalloc(strlen(filename)
+                                            + strlen(prefix) + 1);
+               strcpy(fullname, prefix);
+               strcat(fullname, filename);
+
+               if (!get_tree_entry(tree_sha1, fullname,
+                                   sha1, &mode)) {
+                       die("Path '%s' exists, but not '%s'.\n"
+                           "Did you mean '%s:%s'?",
+                           fullname,
+                           filename,
+                           object_name,
+                           fullname);
+               }
+               die("Path '%s' does not exist in '%s'",
+                   filename, object_name);
+       }
+}
+
+/* Must be called only when :stage:filename doesn't exist. */
+static void diagnose_invalid_index_path(int stage,
+                                       const char *prefix,
+                                       const char *filename)
+{
+       struct stat st;
+       struct cache_entry *ce;
+       int pos;
+       unsigned namelen = strlen(filename);
+       unsigned fullnamelen;
+       char *fullname;
+
+       if (!prefix)
+               prefix = "";
+
+       /* Wrong stage number? */
+       pos = cache_name_pos(filename, namelen);
+       if (pos < 0)
+               pos = -pos - 1;
+       ce = active_cache[pos];
+       if (ce_namelen(ce) == namelen &&
+           !memcmp(ce->name, filename, namelen))
+               die("Path '%s' is in the index, but not at stage %d.\n"
+                   "Did you mean ':%d:%s'?",
+                   filename, stage,
+                   ce_stage(ce), filename);
+
+       /* Confusion between relative and absolute filenames? */
+       fullnamelen = namelen + strlen(prefix);
+       fullname = xmalloc(fullnamelen + 1);
+       strcpy(fullname, prefix);
+       strcat(fullname, filename);
+       pos = cache_name_pos(fullname, fullnamelen);
+       if (pos < 0)
+               pos = -pos - 1;
+       ce = active_cache[pos];
+       if (ce_namelen(ce) == fullnamelen &&
+           !memcmp(ce->name, fullname, fullnamelen))
+               die("Path '%s' is in the index, but not '%s'.\n"
+                   "Did you mean ':%d:%s'?",
+                   fullname, filename,
+                   ce_stage(ce), fullname);
+
+       if (!lstat(filename, &st))
+               die("Path '%s' exists on disk, but not in the index.", filename);
+       if (errno == ENOENT || errno == ENOTDIR)
+               die("Path '%s' does not exist (neither on disk nor in the index).",
+                   filename);
+
+       free(fullname);
+}
+
+
+int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix)
 {
        int ret, bracket_depth;
        int namelen = strlen(name);
@@ -850,6 +1073,8 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
                        }
                        pos++;
                }
+               if (!gently)
+                       diagnose_invalid_index_path(stage, prefix, cp);
                return -1;
        }
        for (cp = name, bracket_depth = 0; *cp; cp++) {
@@ -862,9 +1087,25 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
        }
        if (*cp == ':') {
                unsigned char tree_sha1[20];
-               if (!get_sha1_1(name, cp-name, tree_sha1))
-                       return get_tree_entry(tree_sha1, cp+1, sha1,
-                                             mode);
+               char *object_name = NULL;
+               if (!gently) {
+                       object_name = xmalloc(cp-name+1);
+                       strncpy(object_name, name, cp-name);
+                       object_name[cp-name] = '\0';
+               }
+               if (!get_sha1_1(name, cp-name, tree_sha1)) {
+                       const char *filename = cp+1;
+                       ret = get_tree_entry(tree_sha1, filename, sha1, mode);
+                       if (!gently) {
+                               diagnose_invalid_sha1_path(prefix, filename,
+                                                          tree_sha1, object_name);
+                               free(object_name);
+                       }
+                       return ret;
+               } else {
+                       if (!gently)
+                               die("Invalid object name '%s'.", object_name);
+               }
        }
        return ret;
 }
index 63f9da53237d4233bede66a28e4bcf27d5b44af1..4c0ac138aff7b5add73e17fc9c3c4e409fd918e2 100644 (file)
@@ -48,7 +48,7 @@ int main(int argc, char **argv)
                        unsigned char sha1[20];
                        uint32_t crc;
                        uint32_t off;
-               } *entries = xmalloc(nr * sizeof(entries[0]));
+               } *entries = malloc(nr * sizeof(entries[0]));
                for (i = 0; i < nr; i++)
                        if (fread(entries[i].sha1, 20, 1, stdin) != 1)
                                die("unable to read sha1 %u/%u", i, nr);
index a6153dca278abe957254fa091fdcd8eb13b90b25..bc3a0802ea7e7b1743602972de182391b4bf0b3f 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -10,6 +10,15 @@ int prefixcmp(const char *str, const char *prefix)
                        return (unsigned char)*prefix - (unsigned char)*str;
 }
 
+int suffixcmp(const char *str, const char *suffix)
+{
+       int len = strlen(str), suflen = strlen(suffix);
+       if (len < suflen)
+               return -1;
+       else
+               return strcmp(str + len - suflen, suffix);
+}
+
 /*
  * Used as the default ->buf value, so that people can always assume
  * buf is non NULL and ->buf is NUL terminated even for a freshly
@@ -91,13 +100,6 @@ void strbuf_ltrim(struct strbuf *sb)
        sb->buf[sb->len] = '\0';
 }
 
-void strbuf_tolower(struct strbuf *sb)
-{
-       int i;
-       for (i = 0; i < sb->len; i++)
-               sb->buf[i] = tolower(sb->buf[i]);
-}
-
 struct strbuf **strbuf_split(const struct strbuf *sb, int delim)
 {
        int alloc = 2, pos = 0;
@@ -227,6 +229,12 @@ void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
                        break;
                format = percent + 1;
 
+               if (*format == '%') {
+                       strbuf_addch(sb, '%');
+                       format++;
+                       continue;
+               }
+
                consumed = fn(sb, format, context);
                if (consumed)
                        format += consumed;
@@ -251,6 +259,17 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder,
        return 0;
 }
 
+void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src)
+{
+       int i, len = src->len;
+
+       for (i = 0; i < len; i++) {
+               if (src->buf[i] == '%')
+                       strbuf_addch(dst, '%');
+               strbuf_addch(dst, src->buf[i]);
+       }
+}
+
 size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
 {
        size_t res;
index fa07ecf094bd3ef8997958fde29199f8fb6421b9..fac2dbc24fc654e58840727204043a198d065921 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -81,7 +81,6 @@ extern void strbuf_trim(struct strbuf *);
 extern void strbuf_rtrim(struct strbuf *);
 extern void strbuf_ltrim(struct strbuf *);
 extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
-extern void strbuf_tolower(struct strbuf *);
 
 extern struct strbuf **strbuf_split(const struct strbuf *, int delim);
 extern void strbuf_list_free(struct strbuf **);
@@ -105,6 +104,7 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
        strbuf_add(sb, s, strlen(s));
 }
 static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
+       strbuf_grow(sb, sb2->len);
        strbuf_add(sb, sb2->buf, sb2->len);
 }
 extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
@@ -116,6 +116,7 @@ struct strbuf_expand_dict_entry {
        const char *value;
 };
 extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context);
+extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
 
 __attribute__((format (printf,2,3)))
 extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
index 14bbc477decc32618139e912fda22e585d815159..6569cf607b18b84f39ebee613471f270e42cfbdc 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef PATH_LIST_H
-#define PATH_LIST_H
+#ifndef STRING_LIST_H
+#define STRING_LIST_H
 
 struct string_list_item {
        char *string;
@@ -39,4 +39,4 @@ struct string_list_item *string_list_append(const char *string, struct string_li
 void sort_string_list(struct string_list *list);
 int unsorted_string_list_has_string(struct string_list *list, const char *string);
 
-#endif /* PATH_LIST_H */
+#endif /* STRING_LIST_H */
index 86aad653b7c113106e365a9d4e4c38b3e7737457..7d70c4f7bfe2749953726fecb27144a9588a326f 100644 (file)
@@ -4,22 +4,25 @@
 #include "diff.h"
 #include "commit.h"
 #include "revision.h"
+#include "run-command.h"
 
-int add_submodule_odb(const char *path)
+static int add_submodule_odb(const char *path)
 {
        struct strbuf objects_directory = STRBUF_INIT;
        struct alternate_object_database *alt_odb;
+       int ret = 0;
 
        strbuf_addf(&objects_directory, "%s/.git/objects/", path);
-       if (!is_directory(objects_directory.buf))
-               return -1;
-
+       if (!is_directory(objects_directory.buf)) {
+               ret = -1;
+               goto done;
+       }
        /* avoid adding it twice */
        for (alt_odb = alt_odb_list; alt_odb; alt_odb = alt_odb->next)
                if (alt_odb->name - alt_odb->base == objects_directory.len &&
                                !strncmp(alt_odb->base, objects_directory.buf,
                                        objects_directory.len))
-                       return 0;
+                       goto done;
 
        alt_odb = xmalloc(objects_directory.len + 42 + sizeof(*alt_odb));
        alt_odb->next = alt_odb_list;
@@ -30,11 +33,14 @@ int add_submodule_odb(const char *path)
        alt_odb->name[41] = '\0';
        alt_odb_list = alt_odb;
        prepare_alt_odb();
-       return 0;
+done:
+       strbuf_release(&objects_directory);
+       return ret;
 }
 
 void show_submodule_summary(FILE *f, const char *path,
                unsigned char one[20], unsigned char two[20],
+               unsigned dirty_submodule,
                const char *del, const char *add, const char *reset)
 {
        struct rev_info rev;
@@ -84,6 +90,8 @@ void show_submodule_summary(FILE *f, const char *path,
        if (!fast_backward && !fast_forward)
                strbuf_addch(&sb, '.');
        strbuf_addf(&sb, "%s", find_unique_abbrev(two, DEFAULT_ABBREV));
+       if (dirty_submodule)
+               strbuf_add(&sb, "-dirty", 6);
        if (message)
                strbuf_addf(&sb, " %s\n", message);
        else
@@ -112,3 +120,54 @@ void show_submodule_summary(FILE *f, const char *path,
        }
        strbuf_release(&sb);
 }
+
+int is_submodule_modified(const char *path)
+{
+       int len;
+       struct child_process cp;
+       const char *argv[] = {
+               "status",
+               "--porcelain",
+               NULL,
+       };
+       char *env[4];
+       struct strbuf buf = STRBUF_INIT;
+
+       strbuf_addf(&buf, "%s/.git/", path);
+       if (!is_directory(buf.buf)) {
+               strbuf_release(&buf);
+               /* The submodule is not checked out, so it is not modified */
+               return 0;
+
+       }
+       strbuf_reset(&buf);
+
+       strbuf_addf(&buf, "GIT_WORK_TREE=%s", path);
+       env[0] = strbuf_detach(&buf, NULL);
+       strbuf_addf(&buf, "GIT_DIR=%s/.git", path);
+       env[1] = strbuf_detach(&buf, NULL);
+       strbuf_addf(&buf, "GIT_INDEX_FILE");
+       env[2] = strbuf_detach(&buf, NULL);
+       env[3] = NULL;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.argv = argv;
+       cp.env = (const char *const *)env;
+       cp.git_cmd = 1;
+       cp.no_stdin = 1;
+       cp.out = -1;
+       if (start_command(&cp))
+               die("Could not run git status --porcelain");
+
+       len = strbuf_read(&buf, cp.out, 1024);
+       close(cp.out);
+
+       if (finish_command(&cp))
+               die("git status --porcelain failed");
+
+       free(env[0]);
+       free(env[1]);
+       free(env[2]);
+       strbuf_release(&buf);
+       return len != 0;
+}
index 4c0269d67933659da5ff81e53c21b5f25ca8cf9d..233696555e913d20b6a6c7c21942586c93595541 100644 (file)
@@ -3,6 +3,8 @@
 
 void show_submodule_summary(FILE *f, const char *path,
                unsigned char one[20], unsigned char two[20],
+               unsigned dirty_submodule,
                const char *del, const char *add, const char *reset);
+int is_submodule_modified(const char *path);
 
 #endif
index 7b0a86d35776e8695423c13403c9f4fa3465017d..88601200114f857a57214a9bf0d02ffd39a83504 100644 (file)
@@ -179,37 +179,6 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len,
        return ret_flags;
 }
 
-/*
- * Invalidate the given 'name' from the cache, if 'name' matches
- * completely with the cache.
- */
-void invalidate_lstat_cache(const char *name, int len)
-{
-       int match_len, previous_slash;
-       struct cache_def *cache = &default_cache;       /* FIXME */
-
-       match_len = longest_path_match(name, len, cache->path, cache->len,
-                                      &previous_slash);
-       if (len == match_len) {
-               if ((cache->track_flags & FL_DIR) && previous_slash > 0) {
-                       cache->path[previous_slash] = '\0';
-                       cache->len = previous_slash;
-                       cache->flags = FL_DIR;
-               } else {
-                       reset_lstat_cache(cache);
-               }
-       }
-}
-
-/*
- * Completely clear the contents of the cache
- */
-void clear_lstat_cache(void)
-{
-       struct cache_def *cache = &default_cache;       /* FIXME */
-       reset_lstat_cache(cache);
-}
-
 #define USE_ONLY_LSTAT  0
 
 /*
index 4e1d7dd1833f43404a00b41d55800b9fe27d1807..dcd3ebb5f2dcdbf15ca0e4a043b45cd2fc36cbb5 100644 (file)
--- a/t/README
+++ b/t/README
@@ -75,6 +75,15 @@ appropriately before running "make".
        As the names depend on the tests' file names, it is safe to
        run the tests with this option in parallel.
 
+--with-dashes::
+       By default tests are run without dashed forms of
+       commands (like git-commit) in the PATH (it only uses
+       wrappers from ../bin-wrappers).  Use this option to include
+       the build directory (..) in the PATH, which contains all
+       the dashed forms of commands.  This option is currently
+       implied by other options like --valgrind and
+       GIT_TEST_INSTALLED.
+
 You can also set the GIT_TEST_INSTALLED environment variable to
 the bindir of an existing git installation to test that installation.
 You still need to have built this git sandbox, from which various
index 4bddeb591ecc17ec532164d0d6cf1ad1a54eb996..75a35fcd0699940aea51534b5fe954847bed7bb9 100644 (file)
@@ -1,7 +1,5 @@
 :
 
-_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"
 sanitize_diff_raw='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]*  / X X \1#       /'
 compare_diff_raw () {
     # When heuristics are improved, the score numbers would change.
index 76d8b7b803b4134b65f2d4f624e29223dda2a7ee..5a734b1b7b2df4a5c5c35f5347c618f3735317ac 100644 (file)
@@ -25,6 +25,7 @@ our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/git-favicon.png';
 our \$projects_list = '';
 our \$export_ok = '';
 our \$strict_export = '';
+our \$maxload = undef;
 
 EOF
 
index 6765b08065e8959dcec38940188818b952d2fcd0..28aff887b5a92ec5919f4005010ef64f85d908e9 100644 (file)
@@ -12,16 +12,29 @@ fi
 
 HTTPD_PARA=""
 
+for DEFAULT_HTTPD_PATH in '/usr/sbin/httpd' '/usr/sbin/apache2'
+do
+       if test -x "$DEFAULT_HTTPD_PATH"
+       then
+               break
+       fi
+done
+
+for DEFAULT_HTTPD_MODULE_PATH in '/usr/libexec/apache2' \
+                                '/usr/lib/apache2/modules' \
+                                '/usr/lib64/httpd/modules' \
+                                '/usr/lib/httpd/modules'
+do
+       if test -d "$DEFAULT_HTTPD_MODULE_PATH"
+       then
+               break
+       fi
+done
+
 case $(uname) in
        Darwin)
-               DEFAULT_HTTPD_PATH='/usr/sbin/httpd'
-               DEFAULT_HTTPD_MODULE_PATH='/usr/libexec/apache2'
                HTTPD_PARA="$HTTPD_PARA -DDarwin"
        ;;
-       *)
-               DEFAULT_HTTPD_PATH='/usr/sbin/apache2'
-               DEFAULT_HTTPD_MODULE_PATH='/usr/lib/apache2/modules'
-       ;;
 esac
 
 LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"}
@@ -49,6 +62,11 @@ then
                        say "skipping test, at least Apache version 2 is required"
                        test_done
                fi
+               if ! test -d "$DEFAULT_HTTPD_MODULE_PATH"
+               then
+                       say "Apache module directory not found.  Skipping tests."
+                       test_done
+               fi
 
                LIB_HTTPD_MODULE_PATH="$DEFAULT_HTTPD_MODULE_PATH"
        fi
index 0fe3fd0d012159f6abe7f7d8b006d3f8e59cbab3..4961505d1dd99da529b43abd42522e6a005961b1 100644 (file)
@@ -22,8 +22,13 @@ Alias /dumb/ www/
 
 <Location /smart/>
        SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
+       SetEnv GIT_HTTP_EXPORT_ALL
+</Location>
+<Location /smart_noexport/>
+       SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
 </Location>
 ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/
+ScriptAlias /smart_noexport/ ${GIT_EXEC_PATH}/git-http-backend/
 <Directory ${GIT_EXEC_PATH}>
        Options None
 </Directory>
index 62f452c8ea2c2718fff300d39531fbc4c2e6a44a..6aefe27593e89d57f075bc0d3dbc5a1104d874b7 100644 (file)
@@ -2,21 +2,33 @@
 
 # After setting the fake editor with this function, you can
 #
-# - override the commit message with $FAKE_COMMIT_MESSAGE,
+# - override the commit message with $FAKE_COMMIT_MESSAGE
 # - amend the commit message with $FAKE_COMMIT_AMEND
 # - check that non-commit messages have a certain line count with $EXPECT_COUNT
-# - rewrite a rebase -i script with $FAKE_LINES in the form
+# - check the commit count in the commit message header with $EXPECT_HEADER_COUNT
+# - rewrite a rebase -i script as directed by $FAKE_LINES.
+#   $FAKE_LINES consists of a sequence of words separated by spaces.
+#   The following word combinations are possible:
 #
-#      "[<lineno1>] [<lineno2>]..."
+#   "<lineno>" -- add a "pick" line with the SHA1 taken from the
+#       specified line.
 #
-#   If a line number is prefixed with "squash", "edit", or "reword", the
-#   respective line's command will be replaced with the specified one.
+#   "<cmd> <lineno>" -- add a line with the specified command
+#       ("squash", "fixup", "edit", or "reword") and the SHA1 taken
+#       from the specified line.
+#
+#   "#" -- Add a comment line.
+#
+#   ">" -- Add a blank line.
 
 set_fake_editor () {
        echo "#!$SHELL_PATH" >fake-editor.sh
        cat >> fake-editor.sh <<\EOF
 case "$1" in
 */COMMIT_EDITMSG)
+       test -z "$EXPECT_HEADER_COUNT" ||
+               test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1")" ||
+               exit
        test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
        test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
        exit
@@ -28,19 +40,24 @@ test -z "$EXPECT_COUNT" ||
 test -z "$FAKE_LINES" && exit
 grep -v '^#' < "$1" > "$1".tmp
 rm -f "$1"
+echo 'rebase -i script before editing:'
 cat "$1".tmp
 action=pick
 for line in $FAKE_LINES; do
        case $line in
-       squash|edit|reword)
+       squash|fixup|edit|reword)
                action="$line";;
+       "#")
+               echo '# comment' >> "$1";;
+       ">")
+               echo >> "$1";;
        *)
-               echo sed -n "${line}s/^pick/$action/p"
-               sed -n "${line}p" < "$1".tmp
                sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
                action=pick;;
        esac
 done
+echo 'rebase -i script after editing:'
+cat "$1"
 EOF
 
        test_set_editor "$(pwd)/fake-editor.sh"
index 4e72b53140bd35db87a6c873eda9e75e896e1cdd..c3e7e322a8d62b988999b166d2abcf08930adbf7 100755 (executable)
@@ -4,21 +4,8 @@ test_description='CRLF conversion'
 
 . ./test-lib.sh
 
-q_to_nul () {
-       perl -pe 'y/Q/\000/'
-}
-
-q_to_cr () {
-       tr Q '\015'
-}
-
-append_cr () {
-       sed -e 's/$/Q/' | tr Q '\015'
-}
-
-remove_cr () {
-       tr '\015' Q <"$1" | grep Q >/dev/null &&
-       tr '\015' Q <"$1" | sed -ne 's/Q$//p'
+has_cr() {
+       tr '\015' Q <"$1" | grep Q >/dev/null
 }
 
 test_expect_success setup '
@@ -156,7 +143,7 @@ test_expect_success 'checkout with autocrlf=true' '
 
        for f in one dir/two
        do
-               remove_cr "$f" >tmp && mv -f tmp $f &&
+               remove_cr <"$f" >tmp && mv -f tmp $f &&
                git update-index -- $f || {
                        echo "Eh? $f"
                        false
@@ -180,7 +167,7 @@ test_expect_success 'checkout with autocrlf=input' '
 
        for f in one dir/two
        do
-               if remove_cr "$f" >/dev/null
+               if has_cr "$f"
                then
                        echo "Eh? $f"
                        false
@@ -245,7 +232,7 @@ test_expect_success 'apply patch (autocrlf=true)' '
        git read-tree --reset -u HEAD &&
 
        git apply patch.file &&
-       test "$patched" = "`remove_cr one | git hash-object --stdin`" || {
+       test "$patched" = "`remove_cr <one | git hash-object --stdin`" || {
                echo "Eh?  apply without index"
                false
        }
@@ -272,7 +259,7 @@ test_expect_success 'apply patch --index (autocrlf=true)' '
 
        git apply --index patch.file &&
        test "$patched" = `git rev-parse :one` &&
-       test "$patched" = "`remove_cr one | git hash-object --stdin`" || {
+       test "$patched" = "`remove_cr <one | git hash-object --stdin`" || {
                echo "Eh?  apply with --index"
                false
        }
@@ -285,7 +272,7 @@ test_expect_success '.gitattributes says two is binary' '
        git config core.autocrlf true &&
        git read-tree --reset -u HEAD &&
 
-       if remove_cr dir/two >/dev/null
+       if has_cr dir/two
        then
                echo "Huh?"
                false
@@ -293,7 +280,7 @@ test_expect_success '.gitattributes says two is binary' '
                : happy
        fi &&
 
-       if remove_cr one >/dev/null
+       if has_cr one
        then
                : happy
        else
@@ -301,7 +288,7 @@ test_expect_success '.gitattributes says two is binary' '
                false
        fi &&
 
-       if remove_cr three >/dev/null
+       if has_cr three
        then
                echo "Huh?"
                false
@@ -316,7 +303,7 @@ test_expect_success '.gitattributes says two is input' '
        echo "two crlf=input" >.gitattributes &&
        git read-tree --reset -u HEAD &&
 
-       if remove_cr dir/two >/dev/null
+       if has_cr dir/two
        then
                echo "Huh?"
                false
@@ -331,7 +318,7 @@ test_expect_success '.gitattributes says two and three are text' '
        echo "t* crlf" >.gitattributes &&
        git read-tree --reset -u HEAD &&
 
-       if remove_cr dir/two >/dev/null
+       if has_cr dir/two
        then
                : happy
        else
@@ -339,7 +326,7 @@ test_expect_success '.gitattributes says two and three are text' '
                false
        fi &&
 
-       if remove_cr three >/dev/null
+       if has_cr three
        then
                : happy
        else
@@ -357,14 +344,14 @@ test_expect_success 'in-tree .gitattributes (1)' '
        rm -rf tmp one dir .gitattributes patch.file three &&
        git read-tree --reset -u HEAD &&
 
-       if remove_cr one >/dev/null
+       if has_cr one
        then
                echo "Eh? one should not have CRLF"
                false
        else
                : happy
        fi &&
-       remove_cr three >/dev/null || {
+       has_cr three || {
                echo "Eh? three should still have CRLF"
                false
        }
@@ -376,14 +363,14 @@ test_expect_success 'in-tree .gitattributes (2)' '
        git read-tree --reset HEAD &&
        git checkout-index -f -q -u -a &&
 
-       if remove_cr one >/dev/null
+       if has_cr one
        then
                echo "Eh? one should not have CRLF"
                false
        else
                : happy
        fi &&
-       remove_cr three >/dev/null || {
+       has_cr three || {
                echo "Eh? three should still have CRLF"
                false
        }
@@ -396,14 +383,14 @@ test_expect_success 'in-tree .gitattributes (3)' '
        git checkout-index -u .gitattributes &&
        git checkout-index -u one dir/two three &&
 
-       if remove_cr one >/dev/null
+       if has_cr one
        then
                echo "Eh? one should not have CRLF"
                false
        else
                : happy
        fi &&
-       remove_cr three >/dev/null || {
+       has_cr three || {
                echo "Eh? three should still have CRLF"
                false
        }
@@ -416,14 +403,14 @@ test_expect_success 'in-tree .gitattributes (4)' '
        git checkout-index -u one dir/two three &&
        git checkout-index -u .gitattributes &&
 
-       if remove_cr one >/dev/null
+       if has_cr one
        then
                echo "Eh? one should not have CRLF"
                false
        else
                : happy
        fi &&
-       remove_cr three >/dev/null || {
+       has_cr three || {
                echo "Eh? three should still have CRLF"
                false
        }
@@ -456,7 +443,7 @@ test_expect_success 'checkout when deleting .gitattributes' '
 
        git checkout master~1 &&
        git checkout master &&
-       remove_cr .file2 >/dev/null
+       has_cr .file2
 
 '
 
index 8fc39d77cec6168dae930beef785597dace24aa3..6cb8d60ea2649495c0e3c8bbb8b7cc75c36799b7 100755 (executable)
@@ -4,7 +4,8 @@ test_description='blob conversion via gitattributes'
 
 . ./test-lib.sh
 
-cat <<\EOF >rot13.sh
+cat <<EOF >rot13.sh
+#!$SHELL_PATH
 tr \
   'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \
   'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
index f1e1d48869a25c6ab1d0b570eb4c0e28afc5fa05..7af3fbcc7b9e5e61e41205accaa6d81e85cae6cf 100755 (executable)
@@ -12,7 +12,7 @@ test_expect_success setup '
        test_tick &&
        git commit -m Initial &&
 
-       sed -e "s/\$/\r/" "$TEST_DIRECTORY"/t0022-crlf-rename.sh >elpmas &&
+       append_cr <"$TEST_DIRECTORY"/t0022-crlf-rename.sh >elpmas &&
        git add elpmas &&
        rm -f sample &&
 
diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh
new file mode 100755 (executable)
index 0000000..10b26e4
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Ilari Liusvaara
+#
+
+test_description='Test run command'
+
+. ./test-lib.sh
+
+test_expect_success 'start_command reports ENOENT' '
+       test-run-command start-command-ENOENT ./does-not-exist
+'
+
+test_done
diff --git a/t/t0101-at-syntax.sh b/t/t0101-at-syntax.sh
new file mode 100755 (executable)
index 0000000..a1998b5
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+test_description='various @{whatever} syntax tests'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit one &&
+       test_commit two
+'
+
+check_at() {
+       echo "$2" >expect &&
+       git log -1 --format=%s "$1" >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success '@{0} shows current' '
+       check_at @{0} two
+'
+
+test_expect_success '@{1} shows old' '
+       check_at @{1} one
+'
+
+test_expect_success '@{now} shows current' '
+       check_at @{now} two
+'
+
+test_expect_success '@{2001-09-17} (before the first commit) shows old' '
+       check_at @{2001-09-17} one
+'
+
+test_expect_success 'silly approxidates work' '
+       check_at @{3.hot.dogs.on.2001-09-17} one
+'
+
+test_expect_success 'notice misspelled upstream' '
+       test_must_fail git log -1 --format=%s @{usptream}
+'
+
+test_expect_success 'complain about total nonsense' '
+       test_must_fail git log -1 --format=%s @{utter.bogosity}
+'
+
+test_done
index 22ba7a5442c587f4536aad5668df43661231de56..4f171722d9e1dda7668f54b4104267eb70d91c67 100755 (executable)
@@ -126,9 +126,6 @@ cat >expected <<\EOF
 100644 X 0     Z/NN
 EOF
 
-_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"
-
 check_result () {
     git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
     test_cmp expected current
index c2d408b46120ef9d962272b652a68a83ba0852a5..6327d205cb89b00e77502031e3b56b4b895cc32d 100755 (executable)
@@ -26,8 +26,6 @@ read_tree_twoway () {
     git read-tree -m "$1" "$2" && git ls-files --stage
 }
 
-_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"
 compare_change () {
        sed -n >current \
            -e '/^--- /d; /^+++ /d; /^@@ /d;' \
index 5e40cec530df07a8b7c088d31b28ac2d39abdc1b..0241329a08af0be642131ffe4b619231d9ea3844 100755 (executable)
@@ -10,8 +10,6 @@ This is identical to t1001, but uses -u to update the work tree as well.
 '
 . ./test-lib.sh
 
-_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"
 compare_change () {
        sed >current \
            -e '1{/^diff --git /d;}' \
diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
new file mode 100755 (executable)
index 0000000..62246db
--- /dev/null
@@ -0,0 +1,150 @@
+#!/bin/sh
+
+test_description='sparse checkout tests'
+
+. ./test-lib.sh
+
+cat >expected <<EOF
+100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0      init.t
+100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0      sub/added
+EOF
+test_expect_success 'setup' '
+       test_commit init &&
+       echo modified >> init.t &&
+       mkdir sub &&
+       touch sub/added &&
+       git add init.t sub/added &&
+       git commit -m "modified and added" &&
+       git tag top &&
+       git rm sub/added &&
+       git commit -m removed &&
+       git tag removed &&
+       git checkout top &&
+       git ls-files --stage > result &&
+       test_cmp expected result
+'
+
+cat >expected.swt <<EOF
+H init.t
+H sub/added
+EOF
+test_expect_success 'read-tree without .git/info/sparse-checkout' '
+       git read-tree -m -u HEAD &&
+       git ls-files --stage > result &&
+       test_cmp expected result &&
+       git ls-files -t > result &&
+       test_cmp expected.swt result
+'
+
+test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' '
+       echo > .git/info/sparse-checkout
+       git read-tree -m -u HEAD &&
+       git ls-files -t > result &&
+       test_cmp expected.swt result &&
+       test -f init.t &&
+       test -f sub/added
+'
+
+test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' '
+       git config core.sparsecheckout true &&
+       echo > .git/info/sparse-checkout &&
+       git read-tree --no-sparse-checkout -m -u HEAD &&
+       git ls-files -t > result &&
+       test_cmp expected.swt result &&
+       test -f init.t &&
+       test -f sub/added
+'
+
+test_expect_success 'read-tree with empty .git/info/sparse-checkout' '
+       git config core.sparsecheckout true &&
+       echo > .git/info/sparse-checkout &&
+       test_must_fail git read-tree -m -u HEAD &&
+       git ls-files --stage > result &&
+       test_cmp expected result &&
+       git ls-files -t > result &&
+       test_cmp expected.swt result &&
+       test -f init.t &&
+       test -f sub/added
+'
+
+cat >expected.swt <<EOF
+S init.t
+H sub/added
+EOF
+test_expect_success 'match directories with trailing slash' '
+       echo sub/ > .git/info/sparse-checkout &&
+       git read-tree -m -u HEAD &&
+       git ls-files -t > result &&
+       test_cmp expected.swt result &&
+       test ! -f init.t &&
+       test -f sub/added
+'
+
+cat >expected.swt <<EOF
+H init.t
+H sub/added
+EOF
+test_expect_failure 'match directories without trailing slash' '
+       echo init.t > .git/info/sparse-checkout &&
+       echo sub >> .git/info/sparse-checkout &&
+       git read-tree -m -u HEAD &&
+       git ls-files -t > result &&
+       test_cmp expected.swt result &&
+       test ! -f init.t &&
+       test -f sub/added
+'
+
+cat >expected.swt <<EOF
+H init.t
+S sub/added
+EOF
+test_expect_success 'checkout area changes' '
+       echo init.t > .git/info/sparse-checkout &&
+       git read-tree -m -u HEAD &&
+       git ls-files -t > result &&
+       test_cmp expected.swt result &&
+       test -f init.t &&
+       test ! -f sub/added
+'
+
+test_expect_success 'read-tree updates worktree, absent case' '
+       echo sub/added > .git/info/sparse-checkout &&
+       git checkout -f top &&
+       git read-tree -m -u HEAD^ &&
+       test ! -f init.t
+'
+
+test_expect_success 'read-tree updates worktree, dirty case' '
+       echo sub/added > .git/info/sparse-checkout &&
+       git checkout -f top &&
+       echo dirty > init.t &&
+       git read-tree -m -u HEAD^ &&
+       grep -q dirty init.t &&
+       rm init.t
+'
+
+test_expect_success 'read-tree removes worktree, dirty case' '
+       echo init.t > .git/info/sparse-checkout &&
+       git checkout -f top &&
+       echo dirty > added &&
+       git read-tree -m -u HEAD^ &&
+       grep -q dirty added
+'
+
+test_expect_success 'read-tree adds to worktree, absent case' '
+       echo init.t > .git/info/sparse-checkout &&
+       git checkout -f removed &&
+       git read-tree -u -m HEAD^ &&
+       test ! -f sub/added
+'
+
+test_expect_success 'read-tree adds to worktree, dirty case' '
+       echo init.t > .git/info/sparse-checkout &&
+       git checkout -f removed &&
+       mkdir sub &&
+       echo dirty > sub/added &&
+       git read-tree -u -m HEAD^ &&
+       grep -q dirty sub/added
+'
+
+test_done
diff --git a/t/t1012-read-tree-df.sh b/t/t1012-read-tree-df.sh
new file mode 100755 (executable)
index 0000000..9811d46
--- /dev/null
@@ -0,0 +1,102 @@
+#!/bin/sh
+
+test_description='read-tree D/F conflict corner cases'
+
+. ./test-lib.sh
+
+maketree () {
+       (
+               rm -f .git/index .git/index.lock &&
+               git clean -d -f -f -q -x &&
+               name="$1" &&
+               shift &&
+               for it
+               do
+                       path=$(expr "$it" : '\([^:]*\)') &&
+                       mkdir -p $(dirname "$path") &&
+                       echo "$it" >"$path" &&
+                       git update-index --add "$path" || exit
+               done &&
+               git tag "$name" $(git write-tree)
+       )
+}
+
+settree () {
+       rm -f .git/index .git/index.lock &&
+       git clean -d -f -f -q -x &&
+       git read-tree "$1" &&
+       git checkout-index -f -q -u -a &&
+       git update-index --refresh
+}
+
+checkindex () {
+       git ls-files -s |
+       sed "s|^[0-7][0-7]* $_x40 \([0-3]\)     |\1 |" >current &&
+       cat >expect &&
+       test_cmp expect current
+}
+
+test_expect_success setup '
+       maketree O-000 a/b-2/c/d a/b/c/d a/x &&
+       maketree A-000 a/b-2/c/d a/b/c/d a/x &&
+       maketree A-001 a/b-2/c/d a/b/c/d a/b/c/e a/x &&
+       maketree B-000 a/b-2/c/d a/b     a/x &&
+
+       maketree O-010 t-0     t/1  t/2 t=3 &&
+       maketree A-010 t-0 t            t=3 &&
+       maketree B-010         t/1:     t=3: &&
+
+       maketree O-020 ds/dma/ioat.c ds/dma/ioat_dca.c &&
+       maketree A-020 ds/dma/ioat/Makefile ds/dma/ioat/registers.h &&
+       :
+'
+
+test_expect_success '3-way (1)' '
+       settree A-000 &&
+       git read-tree -m -u O-000 A-000 B-000 &&
+       checkindex <<-EOF
+       3 a/b
+       0 a/b-2/c/d
+       1 a/b/c/d
+       2 a/b/c/d
+       0 a/x
+       EOF
+'
+
+test_expect_success '3-way (2)' '
+       settree A-001 &&
+       git read-tree -m -u O-000 A-001 B-000 &&
+       checkindex <<-EOF
+       3 a/b
+       0 a/b-2/c/d
+       1 a/b/c/d
+       2 a/b/c/d
+       2 a/b/c/e
+       0 a/x
+       EOF
+'
+
+test_expect_success '3-way (3)' '
+       settree A-010 &&
+       git read-tree -m -u O-010 A-010 B-010 &&
+       checkindex <<-EOF
+       2 t
+       1 t-0
+       2 t-0
+       1 t/1
+       3 t/1
+       1 t/2
+       0 t=3
+       EOF
+'
+
+test_expect_success '2-way (1)' '
+       settree O-020 &&
+       git read-tree -m -u O-020 A-020 &&
+       checkindex <<-EOF
+       0 ds/dma/ioat/Makefile
+       0 ds/dma/ioat/registers.h
+       EOF
+'
+
+test_done
index 83b7294010cd59d5438b6020868c699c01105595..f11f98c3ce7e35f61d06542ce707d61b98079fda 100755 (executable)
@@ -398,6 +398,17 @@ test_expect_success 'alternative GIT_CONFIG' 'cmp output expect'
 test_expect_success 'alternative GIT_CONFIG (--file)' \
        'git config --file other-config -l > output && cmp output expect'
 
+test_expect_success 'refer config from subdirectory' '
+       mkdir x &&
+       (
+               cd x &&
+               echo strasse >expect
+               git config --get --file ../other-config ein.bahn >actual &&
+               test_cmp expect actual
+       )
+
+'
+
 GIT_CONFIG=other-config git config anwohner.park ausweis
 
 cat > expect << EOF
@@ -683,6 +694,34 @@ test_expect_success 'set --bool-or-int' '
 
 rm .git/config
 
+cat >expect <<\EOF
+[path]
+       home = ~/
+       normal = /dev/null
+       trailingtilde = foo~
+EOF
+
+test_expect_success 'set --path' '
+       git config --path path.home "~/" &&
+       git config --path path.normal "/dev/null" &&
+       git config --path path.trailingtilde "foo~" &&
+       test_cmp expect .git/config'
+
+cat >expect <<EOF
+$HOME/
+/dev/null
+foo~
+EOF
+
+test_expect_success 'get --path' '
+       git config --get --path path.home > result &&
+       git config --get --path path.normal >> result &&
+       git config --get --path path.trailingtilde >> result &&
+       test_cmp expect result
+'
+
+rm .git/config
+
 git config quote.leading " test"
 git config quote.ending "test "
 git config quote.semicolon "test;test"
diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh
new file mode 100755 (executable)
index 0000000..af721f9
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='test git rev-parse diagnosis for invalid argument'
+
+exec </dev/null
+
+. ./test-lib.sh
+
+HASH_file=
+
+test_expect_success 'set up basic repo' '
+       echo one > file.txt &&
+       mkdir subdir &&
+       echo two > subdir/file.txt &&
+       echo three > subdir/file2.txt &&
+       git add . &&
+       git commit -m init &&
+       echo four > index-only.txt &&
+       git add index-only.txt &&
+       echo five > disk-only.txt
+'
+
+test_expect_success 'correct file objects' '
+       HASH_file=$(git rev-parse HEAD:file.txt) &&
+       git rev-parse HEAD:subdir/file.txt &&
+       git rev-parse :index-only.txt &&
+       (cd subdir &&
+        git rev-parse HEAD:subdir/file2.txt &&
+        test $HASH_file = $(git rev-parse HEAD:file.txt) &&
+        test $HASH_file = $(git rev-parse :file.txt) &&
+        test $HASH_file = $(git rev-parse :0:file.txt) )
+'
+
+test_expect_success 'incorrect revision id' '
+       test_must_fail git rev-parse foobar:file.txt 2>error &&
+       grep "Invalid object name '"'"'foobar'"'"'." error &&
+       test_must_fail git rev-parse foobar 2> error &&
+       grep "unknown revision or path not in the working tree." error
+'
+
+test_expect_success 'incorrect file in sha1:path' '
+       test_must_fail git rev-parse HEAD:nothing.txt 2> error &&
+       grep "fatal: Path '"'"'nothing.txt'"'"' does not exist in '"'"'HEAD'"'"'" error &&
+       test_must_fail git rev-parse HEAD:index-only.txt 2> error &&
+       grep "fatal: Path '"'"'index-only.txt'"'"' exists on disk, but not in '"'"'HEAD'"'"'." error &&
+       (cd subdir &&
+        test_must_fail git rev-parse HEAD:file2.txt 2> error &&
+        grep "Did you mean '"'"'HEAD:subdir/file2.txt'"'"'?" error )
+'
+
+test_expect_success 'incorrect file in :path and :N:path' '
+       test_must_fail git rev-parse :nothing.txt 2> error &&
+       grep "fatal: Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
+       test_must_fail git rev-parse :1:nothing.txt 2> error &&
+       grep "Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
+       test_must_fail git rev-parse :1:file.txt 2> error &&
+       grep "Did you mean '"'"':0:file.txt'"'"'?" error &&
+       (cd subdir &&
+        test_must_fail git rev-parse :1:file.txt 2> error &&
+        grep "Did you mean '"'"':0:file.txt'"'"'?" error &&
+        test_must_fail git rev-parse :file2.txt 2> error &&
+        grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error &&
+        test_must_fail git rev-parse :2:file2.txt 2> error &&
+        grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error) &&
+       test_must_fail git rev-parse :disk-only.txt 2> error &&
+       grep "fatal: Path '"'"'disk-only.txt'"'"' exists on disk, but not in the index." error
+'
+
+test_done
diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh
new file mode 100755 (executable)
index 0000000..8c8dfda
--- /dev/null
@@ -0,0 +1,139 @@
+#!/bin/sh
+
+test_description='test <branch>@{upstream} syntax'
+
+. ./test-lib.sh
+
+
+test_expect_success 'setup' '
+
+       test_commit 1 &&
+       git checkout -b side &&
+       test_commit 2 &&
+       git checkout master &&
+       git clone . clone &&
+       test_commit 3 &&
+       (cd clone &&
+        test_commit 4 &&
+        git branch --track my-side origin/side)
+
+'
+
+full_name () {
+       (cd clone &&
+        git rev-parse --symbolic-full-name "$@")
+}
+
+commit_subject () {
+       (cd clone &&
+        git show -s --pretty=format:%s "$@")
+}
+
+test_expect_success '@{upstream} resolves to correct full name' '
+       test refs/remotes/origin/master = "$(full_name @{upstream})"
+'
+
+test_expect_success '@{u} resolves to correct full name' '
+       test refs/remotes/origin/master = "$(full_name @{u})"
+'
+
+test_expect_success 'my-side@{upstream} resolves to correct full name' '
+       test refs/remotes/origin/side = "$(full_name my-side@{u})"
+'
+
+test_expect_success 'my-side@{u} resolves to correct commit' '
+       git checkout side &&
+       test_commit 5 &&
+       (cd clone && git fetch) &&
+       test 2 = "$(commit_subject my-side)" &&
+       test 5 = "$(commit_subject my-side@{u})"
+'
+
+test_expect_success 'not-tracking@{u} fails' '
+       test_must_fail full_name non-tracking@{u} &&
+       (cd clone && git checkout --no-track -b non-tracking) &&
+       test_must_fail full_name non-tracking@{u}
+'
+
+test_expect_success '<branch>@{u}@{1} resolves correctly' '
+       test_commit 6 &&
+       (cd clone && git fetch) &&
+       test 5 = $(commit_subject my-side@{u}@{1})
+'
+
+test_expect_success '@{u} without specifying branch fails on a detached HEAD' '
+       git checkout HEAD^0 &&
+       test_must_fail git rev-parse @{u}
+'
+
+test_expect_success 'checkout -b new my-side@{u} forks from the same' '
+(
+       cd clone &&
+       git checkout -b new my-side@{u} &&
+       git rev-parse --symbolic-full-name my-side@{u} >expect &&
+       git rev-parse --symbolic-full-name new@{u} >actual &&
+       test_cmp expect actual
+)
+'
+
+test_expect_success 'merge my-side@{u} records the correct name' '
+(
+       sq="'\''" &&
+       cd clone || exit
+       git checkout master || exit
+       git branch -D new ;# can fail but is ok
+       git branch -t new my-side@{u} &&
+       git merge -s ours new@{u} &&
+       git show -s --pretty=format:%s >actual &&
+       echo "Merge remote branch ${sq}origin/side${sq}" >expect &&
+       test_cmp expect actual
+)
+'
+
+test_expect_success 'branch -d other@{u}' '
+       git checkout -t -b other master &&
+       git branch -d @{u} &&
+       git for-each-ref refs/heads/master >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'checkout other@{u}' '
+       git branch -f master HEAD &&
+       git checkout -t -b another master &&
+       git checkout @{u} &&
+       git symbolic-ref HEAD >actual &&
+       echo refs/heads/master >expect &&
+       test_cmp expect actual
+'
+
+cat >expect <<EOF
+commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5
+Reflog: master@{0} (C O Mitter <committer@example.com>)
+Reflog message: branch: Created from HEAD
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:15:13 2005 -0700
+
+    3
+EOF
+test_expect_success 'log -g other@{u}' '
+       git log -1 -g other@{u} >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<EOF
+commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5
+Reflog: master@{Thu Apr 7 15:17:13 2005 -0700} (C O Mitter <committer@example.com>)
+Reflog message: branch: Created from HEAD
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:15:13 2005 -0700
+
+    3
+EOF
+
+test_expect_success 'log -g other@{u}@{now}' '
+       git log -1 -g other@{u}@{now} >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t1508-at-combinations.sh b/t/t1508-at-combinations.sh
new file mode 100755 (executable)
index 0000000..d5d6244
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+test_description='test various @{X} syntax combinations together'
+. ./test-lib.sh
+
+check() {
+test_expect_${3:-success} "$1 = $2" "
+       echo '$2' >expect &&
+       git log -1 --format=%s '$1' >actual &&
+       test_cmp expect actual
+"
+}
+nonsense() {
+test_expect_${2:-success} "$1 is nonsensical" "
+       test_must_fail git log -1 '$1'
+"
+}
+fail() {
+       "$@" failure
+}
+
+test_expect_success 'setup' '
+       test_commit master-one &&
+       test_commit master-two &&
+       git checkout -b upstream-branch &&
+       test_commit upstream-one &&
+       test_commit upstream-two &&
+       git checkout -b old-branch &&
+       test_commit old-one &&
+       test_commit old-two &&
+       git checkout -b new-branch &&
+       test_commit new-one &&
+       test_commit new-two &&
+       git config branch.old-branch.remote . &&
+       git config branch.old-branch.merge refs/heads/master &&
+       git config branch.new-branch.remote . &&
+       git config branch.new-branch.merge refs/heads/upstream-branch
+'
+
+check HEAD new-two
+check "@{1}" new-one
+check "@{-1}" old-two
+check "@{-1}@{1}" old-one
+check "@{u}" upstream-two
+check "@{u}@{1}" upstream-one
+check "@{-1}@{u}" master-two
+check "@{-1}@{u}@{1}" master-one
+nonsense "@{u}@{-1}"
+nonsense "@{1}@{u}"
+
+test_done
index 87b30a268c66848726baec5676e73b8db686806d..b44de9dc623fc923941b61719f445170602e21be 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='checkout can switch to last branch'
+test_description='checkout can switch to last branch and merge base'
 
 . ./test-lib.sh
 
@@ -91,4 +91,29 @@ test_expect_success 'switch to twelfth from the last' '
        test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13"
 '
 
+test_expect_success 'merge base test setup' '
+       git checkout -b another other &&
+       echo "hello again" >>world &&
+       git add world &&
+       git commit -m third
+'
+
+test_expect_success 'another...master' '
+       git checkout another &&
+       git checkout another...master &&
+       test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
+'
+
+test_expect_success '...master' '
+       git checkout another &&
+       git checkout ...master &&
+       test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
+'
+
+test_expect_success 'master...' '
+       git checkout another &&
+       git checkout master... &&
+       test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
+'
+
 test_done
diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh
new file mode 100755 (executable)
index 0000000..cb7effe
--- /dev/null
@@ -0,0 +1,170 @@
+#!/bin/sh
+
+test_description='undoing resolution'
+
+. ./test-lib.sh
+
+check_resolve_undo () {
+       msg=$1
+       shift
+       while case $# in
+       0)      break ;;
+       1|2|3)  die "Bug in check-resolve-undo test" ;;
+       esac
+       do
+               path=$1
+               shift
+               for stage in 1 2 3
+               do
+                       sha1=$1
+                       shift
+                       case "$sha1" in
+                       '') continue ;;
+                       esac
+                       sha1=$(git rev-parse --verify "$sha1")
+                       printf "100644 %s %s\t%s\n" $sha1 $stage $path
+               done
+       done >"$msg.expect" &&
+       git ls-files --resolve-undo >"$msg.actual" &&
+       test_cmp "$msg.expect" "$msg.actual"
+}
+
+prime_resolve_undo () {
+       git reset --hard &&
+       git checkout second^0 &&
+       test_tick &&
+       test_must_fail git merge third^0 &&
+       echo merge does not leave anything &&
+       check_resolve_undo empty &&
+       echo different >fi/le &&
+       git add fi/le &&
+       echo resolving records &&
+       check_resolve_undo recorded fi/le initial:fi/le second:fi/le third:fi/le
+}
+
+test_expect_success setup '
+       mkdir fi &&
+       test_commit initial fi/le first &&
+       git branch side &&
+       git branch another &&
+       test_commit second fi/le second &&
+       git checkout side &&
+       test_commit third fi/le third &&
+       git checkout another &&
+       test_commit fourth fi/le fourth &&
+       git checkout master
+'
+
+test_expect_success 'add records switch clears' '
+       prime_resolve_undo &&
+       test_tick &&
+       git commit -m merged &&
+       echo committing keeps &&
+       check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+       git checkout second^0 &&
+       echo switching clears &&
+       check_resolve_undo cleared
+'
+
+test_expect_success 'rm records reset clears' '
+       prime_resolve_undo &&
+       test_tick &&
+       git commit -m merged &&
+       echo committing keeps &&
+       check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+
+       echo merge clears upfront &&
+       test_must_fail git merge fourth^0 &&
+       check_resolve_undo nuked &&
+
+       git rm -f fi/le &&
+       echo resolving records &&
+       check_resolve_undo recorded fi/le initial:fi/le HEAD:fi/le fourth:fi/le &&
+
+       git reset --hard &&
+       echo resetting discards &&
+       check_resolve_undo discarded
+'
+
+test_expect_success 'plumbing clears' '
+       prime_resolve_undo &&
+       test_tick &&
+       git commit -m merged &&
+       echo committing keeps &&
+       check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+
+       echo plumbing clear &&
+       git update-index --clear-resolve-undo &&
+       check_resolve_undo cleared
+'
+
+test_expect_success 'add records checkout -m undoes' '
+       prime_resolve_undo &&
+       git diff HEAD &&
+       git checkout --conflict=merge fi/le &&
+       echo checkout used the record and removed it &&
+       check_resolve_undo removed &&
+       echo the index and the work tree is unmerged again &&
+       git diff >actual &&
+       grep "^++<<<<<<<" actual
+'
+
+test_expect_success 'unmerge with plumbing' '
+       prime_resolve_undo &&
+       git update-index --unresolve fi/le &&
+       git ls-files -u >actual &&
+       test $(wc -l <actual) = 3
+'
+
+test_expect_success 'rerere and rerere forget' '
+       mkdir .git/rr-cache &&
+       prime_resolve_undo &&
+       echo record the resolution &&
+       git rerere &&
+       rerere_id=$(cd .git/rr-cache && echo */postimage) &&
+       rerere_id=${rerere_id%/postimage} &&
+       test -f .git/rr-cache/$rerere_id/postimage &&
+       git checkout -m fi/le &&
+       echo resurrect the conflict &&
+       grep "^=======" fi/le &&
+       echo reresolve the conflict &&
+       git rerere &&
+       test "z$(cat fi/le)" = zdifferent &&
+       echo register the resolution again &&
+       git add fi/le &&
+       check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+       test -z "$(git ls-files -u)" &&
+       git rerere forget fi/le &&
+       ! test -f .git/rr-cache/$rerere_id/postimage &&
+       tr "\0" "\n" <.git/MERGE_RR >actual &&
+       echo "$rerere_id        fi/le" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rerere and rerere forget (subdirectory)' '
+       rm -fr .git/rr-cache &&
+       mkdir .git/rr-cache &&
+       prime_resolve_undo &&
+       echo record the resolution &&
+       (cd fi && git rerere) &&
+       rerere_id=$(cd .git/rr-cache && echo */postimage) &&
+       rerere_id=${rerere_id%/postimage} &&
+       test -f .git/rr-cache/$rerere_id/postimage &&
+       (cd fi && git checkout -m le) &&
+       echo resurrect the conflict &&
+       grep "^=======" fi/le &&
+       echo reresolve the conflict &&
+       (cd fi && git rerere) &&
+       test "z$(cat fi/le)" = zdifferent &&
+       echo register the resolution again &&
+       (cd fi && git add le) &&
+       check_resolve_undo kept fi/le initial:fi/le second:fi/le third:fi/le &&
+       test -z "$(git ls-files -u)" &&
+       (cd fi && git rerere forget le) &&
+       ! test -f .git/rr-cache/$rerere_id/postimage &&
+       tr "\0" "\n" <.git/MERGE_RR >actual &&
+       echo "$rerere_id        fi/le" >expect &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t2104-update-index-skip-worktree.sh b/t/t2104-update-index-skip-worktree.sh
new file mode 100755 (executable)
index 0000000..1d0879b
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
+#
+
+test_description='skip-worktree bit test'
+
+. ./test-lib.sh
+
+cat >expect.full <<EOF
+H 1
+H 2
+H sub/1
+H sub/2
+EOF
+
+cat >expect.skip <<EOF
+S 1
+H 2
+S sub/1
+H sub/2
+EOF
+
+test_expect_success 'setup' '
+       mkdir sub &&
+       touch ./1 ./2 sub/1 sub/2 &&
+       git add 1 2 sub/1 sub/2 &&
+       git ls-files -t | test_cmp expect.full -
+'
+
+test_expect_success 'index is at version 2' '
+       test "$(test-index-version < .git/index)" = 2
+'
+
+test_expect_success 'update-index --skip-worktree' '
+       git update-index --skip-worktree 1 sub/1 &&
+       git ls-files -t | test_cmp expect.skip -
+'
+
+test_expect_success 'index is at version 3 after having some skip-worktree entries' '
+       test "$(test-index-version < .git/index)" = 3
+'
+
+test_expect_success 'ls-files -t' '
+       git ls-files -t | test_cmp expect.skip -
+'
+
+test_expect_success 'update-index --no-skip-worktree' '
+       git update-index --no-skip-worktree 1 sub/1 &&
+       git ls-files -t | test_cmp expect.full -
+'
+
+test_expect_success 'index version is back to 2 when there is no skip-worktree entry' '
+       test "$(test-index-version < .git/index)" = 2
+'
+
+test_done
diff --git a/t/t2105-update-index-gitfile.sh b/t/t2105-update-index-gitfile.sh
new file mode 100755 (executable)
index 0000000..641607d
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Brad King
+#
+
+test_description='git update-index for gitlink to .git file.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'submodule with absolute .git file' '
+       mkdir sub1 &&
+       (cd sub1 &&
+        git init &&
+        REAL="$(pwd)/.real" &&
+        mv .git "$REAL"
+        echo "gitdir: $REAL" >.git &&
+        test_commit first)
+'
+
+test_expect_success 'add gitlink to absolute .git file' '
+       git update-index --add -- sub1
+'
+
+test_expect_success 'submodule with relative .git file' '
+       mkdir sub2 &&
+       (cd sub2 &&
+        git init &&
+        mv .git .real &&
+        echo "gitdir: .real" >.git &&
+        test_commit first)
+'
+
+test_expect_success 'add gitlink to relative .git file' '
+       git update-index --add -- sub2
+'
+
+test_done
index c65bca838881938e2f924cfe62b2c303dbd5b1cd..6d2f2b67ee8d03e1f1dc4874da100cb2e179b6d1 100755 (executable)
@@ -64,6 +64,8 @@ two/*.4
 echo '!*.2
 !*.8' >one/two/.gitignore
 
+allignores='.gitignore one/.gitignore one/two/.gitignore'
+
 test_expect_success \
     'git ls-files --others with various exclude options.' \
     'git ls-files --others \
@@ -85,6 +87,26 @@ test_expect_success \
        >output &&
      test_cmp expect output'
 
+test_expect_success 'setup skip-worktree gitignore' '
+       git add $allignores &&
+       git update-index --skip-worktree $allignores &&
+       rm $allignores
+'
+
+test_expect_success \
+    'git ls-files --others with various exclude options.' \
+    'git ls-files --others \
+       --exclude=\*.6 \
+       --exclude-per-directory=.gitignore \
+       --exclude-from=.git/ignore \
+       >output &&
+     test_cmp expect output'
+
+test_expect_success 'restore gitignore' '
+       git checkout $allignores &&
+       rm .git/index
+'
+
 cat > excludes-file <<\EOF
 *.[1-8]
 e*
@@ -153,4 +175,43 @@ test_expect_success 'negated exclude matches can override previous ones' '
        grep "^a.1" output
 '
 
+test_expect_success 'subdirectory ignore (setup)' '
+       mkdir -p top/l1/l2 &&
+       (
+               cd top &&
+               git init &&
+               echo /.gitignore >.gitignore &&
+               echo l1 >>.gitignore &&
+               echo l2 >l1/.gitignore &&
+               >l1/l2/l1
+       )
+'
+
+test_expect_success 'subdirectory ignore (toplevel)' '
+       (
+               cd top &&
+               git ls-files -o --exclude-standard
+       ) >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'subdirectory ignore (l1/l2)' '
+       (
+               cd top/l1/l2 &&
+               git ls-files -o --exclude-standard
+       ) >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'subdirectory ignore (l1)' '
+       (
+               cd top/l1 &&
+               git ls-files -o --exclude-standard
+       ) >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
 test_done
index 9b3fa2bdcd9bebae46a000ffe9c2059cea5a93fd..9929f82021deeb20358016b487400b80e27b596f 100755 (executable)
@@ -276,11 +276,13 @@ test_expect_success 'fail if the index has unresolved entries' '
 
        test_must_fail git merge "$c5" &&
        test_must_fail git merge "$c5" 2> out &&
+       grep "not possible because you have unmerged files" out &&
+       git add -u &&
+       test_must_fail git merge "$c5" 2> out &&
        grep "You have not concluded your merge" out &&
        rm -f .git/MERGE_HEAD &&
        test_must_fail git merge "$c5" 2> out &&
-       grep "You are in the middle of a conflicted merge" out
-
+       grep "Your local changes to .* would be overwritten by merge." out
 '
 
 test_expect_success 'merge-recursive remove conflict' '
index ee60d03fe8a582100e9df5511f6fea8900bcc543..eee0d344d24623ad9928366729c8efaea699fcd7 100755 (executable)
@@ -43,8 +43,6 @@ 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"
 test_output () {
     sed -e "s/ $_x40   / X     /" <current >check
     test_cmp expected check
index 8be9fb411276e3c416374a813d691ff0455a8746..06654c6f82fb6cfe955b91bcbd4a5fa064c73e70 100755 (executable)
@@ -39,8 +39,6 @@ test_expect_success \
      tree=`git write-tree` &&
      echo $tree'
 
-_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
index d59a9b4aef5fc53e42ec95a3b96a18fb67aa82cb..e0b760513cfc065126cecd6e273180826c8f6bc9 100755 (executable)
@@ -468,4 +468,30 @@ test_expect_success 'detect misconfigured autosetuprebase (no value)' '
        git config --unset branch.autosetuprebase
 '
 
+test_expect_success 'attempt to delete a branch without base and unmerged to HEAD' '
+       git checkout my9 &&
+       git config --unset branch.my8.merge &&
+       test_must_fail git branch -d my8
+'
+
+test_expect_success 'attempt to delete a branch merged to its base' '
+       # we are on my9 which is the initial commit; traditionally
+       # we would not have allowed deleting my8 that is not merged
+       # to my9, but it is set to track master that already has my8
+       git config branch.my8.merge refs/heads/master &&
+       git branch -d my8
+'
+
+test_expect_success 'attempt to delete a branch merged to its base' '
+       git checkout master &&
+       echo Third >>A &&
+       git commit -m "Third commit" A &&
+       git branch -t my10 my9 &&
+       git branch -f my10 HEAD^ &&
+       # we are on master which is at the third commit, and my10
+       # is behind us, so traditionally we would have allowed deleting
+       # it; but my10 is set to track my9 that is further behind.
+       test_must_fail git branch -d my10
+'
+
 test_done
index 4e6a44b623c456dc85f9daa6c4b5b1f0789c93c5..4314ad2d66d06b411e4bc0c9ee7b07553fc35ac2 100755 (executable)
@@ -134,10 +134,6 @@ test_expect_success 'rebase -q is quiet' '
      test ! -s output.out
 '
 
-q_to_cr () {
-       tr Q '\015'
-}
-
 test_expect_success 'Rebase a commit that sprinkles CRs in' '
        (
                echo "One"
index 3a37793c0ddbdab8820dba033521d5e113bc33ac..4e3513709eb121769f87501c1862c996184a6d05 100755 (executable)
@@ -14,58 +14,47 @@ that the result still makes sense.
 
 set_fake_editor
 
-# set up two branches like this:
+# Set up the repository like this:
 #
-# A - B - C - D - E
+#     one - two - three - four (conflict-branch)
+#   /
+# A - B - C - D - E            (master)
+# | \
+# |   F - G - H                (branch1)
+# |     \
+#  \      I                    (branch2)
 #   \
-#     F - G - H
-#       \
-#         I
+#     J - K - L - M            (no-conflict-branch)
 #
-# where B, D and G touch the same file.
+# where A, B, D and G all touch file1, and one, two, three, four all
+# touch file "conflict".
 
 test_expect_success 'setup' '
-       : > file1 &&
-       git add file1 &&
-       test_tick &&
-       git commit -m A &&
-       git tag A &&
-       echo 1 > file1 &&
-       test_tick &&
-       git commit -m B file1 &&
-       : > file2 &&
-       git add file2 &&
-       test_tick &&
-       git commit -m C &&
-       echo 2 > file1 &&
-       test_tick &&
-       git commit -m D file1 &&
-       : > file3 &&
-       git add file3 &&
-       test_tick &&
-       git commit -m E &&
+       test_commit A file1 &&
+       test_commit B file1 &&
+       test_commit C file2 &&
+       test_commit D file1 &&
+       test_commit E file3 &&
        git checkout -b branch1 A &&
-       : > file4 &&
-       git add file4 &&
-       test_tick &&
-       git commit -m F &&
-       git tag F &&
-       echo 3 > file1 &&
-       test_tick &&
-       git commit -m G file1 &&
-       : > file5 &&
-       git add file5 &&
-       test_tick &&
-       git commit -m H &&
+       test_commit F file4 &&
+       test_commit G file1 &&
+       test_commit H file5 &&
        git checkout -b branch2 F &&
-       : > file6 &&
-       git add file6 &&
-       test_tick &&
-       git commit -m I &&
-       git tag I
+       test_commit I file6
+       git checkout -b conflict-branch A &&
+       for n in one two three four
+       do
+               test_commit $n conflict
+       done &&
+       git checkout -b no-conflict-branch A &&
+       for n in J K L M
+       do
+               test_commit $n file$n
+       done
 '
 
 test_expect_success 'no changes are a nop' '
+       git checkout branch2 &&
        git rebase -i F &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
        test $(git rev-parse I) = $(git rev-parse HEAD)
@@ -111,19 +100,20 @@ test_expect_success 'exchange two commits' '
 
 cat > expect << EOF
 diff --git a/file1 b/file1
-index e69de29..00750ed 100644
+index f70f10e..fd79235 100644
 --- a/file1
 +++ b/file1
-@@ -0,0 +1 @@
-+3
+@@ -1 +1 @@
+-A
++G
 EOF
 
 cat > expect2 << EOF
 <<<<<<< HEAD
-2
+D
 =======
-3
->>>>>>> b7ca976... G
+G
+>>>>>>> 51047de... G
 EOF
 
 test_expect_success 'stop on conflicting pick' '
@@ -161,7 +151,8 @@ test_expect_success 'squash' '
        test_tick &&
        GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 &&
        echo "******************************" &&
-       FAKE_LINES="1 squash 2" git rebase -i --onto master HEAD~2 &&
+       FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \
+               git rebase -i --onto master HEAD~2 &&
        test B = $(cat file7) &&
        test $(git rev-parse HEAD^) = $(git rev-parse master)
 '
@@ -256,30 +247,113 @@ test_expect_success 'verbose flag is heeded, even after --continue' '
 test_expect_success 'multi-squash only fires up editor once' '
        base=$(git rev-parse HEAD~4) &&
        FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \
+               EXPECT_HEADER_COUNT=4 \
                git rebase -i $base &&
        test $base = $(git rev-parse HEAD^) &&
        test 1 = $(git show | grep ONCE | wc -l)
 '
 
+test_expect_success 'multi-fixup does not fire up editor' '
+       git checkout -b multi-fixup E &&
+       base=$(git rev-parse HEAD~4) &&
+       FAKE_COMMIT_AMEND="NEVER" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \
+               git rebase -i $base &&
+       test $base = $(git rev-parse HEAD^) &&
+       test 0 = $(git show | grep NEVER | wc -l) &&
+       git checkout to-be-rebased &&
+       git branch -D multi-fixup
+'
+
+test_expect_success 'commit message used after conflict' '
+       git checkout -b conflict-fixup conflict-branch &&
+       base=$(git rev-parse HEAD~4) &&
+       (
+               FAKE_LINES="1 fixup 3 fixup 4" &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i $base
+       ) &&
+       echo three > conflict &&
+       git add conflict &&
+       FAKE_COMMIT_AMEND="ONCE" EXPECT_HEADER_COUNT=2 \
+               git rebase --continue &&
+       test $base = $(git rev-parse HEAD^) &&
+       test 1 = $(git show | grep ONCE | wc -l) &&
+       git checkout to-be-rebased &&
+       git branch -D conflict-fixup
+'
+
+test_expect_success 'commit message retained after conflict' '
+       git checkout -b conflict-squash conflict-branch &&
+       base=$(git rev-parse HEAD~4) &&
+       (
+               FAKE_LINES="1 fixup 3 squash 4" &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i $base
+       ) &&
+       echo three > conflict &&
+       git add conflict &&
+       FAKE_COMMIT_AMEND="TWICE" EXPECT_HEADER_COUNT=2 \
+               git rebase --continue &&
+       test $base = $(git rev-parse HEAD^) &&
+       test 2 = $(git show | grep TWICE | wc -l) &&
+       git checkout to-be-rebased &&
+       git branch -D conflict-squash
+'
+
+cat > expect-squash-fixup << EOF
+B
+
+D
+
+ONCE
+EOF
+
+test_expect_success 'squash and fixup generate correct log messages' '
+       git checkout -b squash-fixup E &&
+       base=$(git rev-parse HEAD~4) &&
+       FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 squash 3 fixup 4" \
+               EXPECT_HEADER_COUNT=4 \
+               git rebase -i $base &&
+       git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup &&
+       test_cmp expect-squash-fixup actual-squash-fixup &&
+       git checkout to-be-rebased &&
+       git branch -D squash-fixup
+'
+
+test_expect_success 'squash ignores comments' '
+       git checkout -b skip-comments E &&
+       base=$(git rev-parse HEAD~4) &&
+       FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="# 1 # squash 2 # squash 3 # squash 4 #" \
+               EXPECT_HEADER_COUNT=4 \
+               git rebase -i $base &&
+       test $base = $(git rev-parse HEAD^) &&
+       test 1 = $(git show | grep ONCE | wc -l) &&
+       git checkout to-be-rebased &&
+       git branch -D skip-comments
+'
+
+test_expect_success 'squash ignores blank lines' '
+       git checkout -b skip-blank-lines E &&
+       base=$(git rev-parse HEAD~4) &&
+       FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="> 1 > squash 2 > squash 3 > squash 4 >" \
+               EXPECT_HEADER_COUNT=4 \
+               git rebase -i $base &&
+       test $base = $(git rev-parse HEAD^) &&
+       test 1 = $(git show | grep ONCE | wc -l) &&
+       git checkout to-be-rebased &&
+       git branch -D skip-blank-lines
+'
+
 test_expect_success 'squash works as expected' '
-       for n in one two three four
-       do
-               echo $n >> file$n &&
-               git add file$n &&
-               git commit -m $n
-       done &&
+       git checkout -b squash-works no-conflict-branch &&
        one=$(git rev-parse HEAD~3) &&
-       FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 &&
+       FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=2 \
+               git rebase -i HEAD~3 &&
        test $one = $(git rev-parse HEAD~2)
 '
 
 test_expect_success 'interrupted squash works as expected' '
-       for n in one two three four
-       do
-               echo $n >> conflict &&
-               git add conflict &&
-               git commit -m $n
-       done &&
+       git checkout -b interrupted-squash conflict-branch &&
        one=$(git rev-parse HEAD~3) &&
        (
                FAKE_LINES="1 squash 3 2" &&
@@ -296,12 +370,7 @@ test_expect_success 'interrupted squash works as expected' '
 '
 
 test_expect_success 'interrupted squash works as expected (case 2)' '
-       for n in one two three four
-       do
-               echo $n >> conflict &&
-               git add conflict &&
-               git commit -m $n
-       done &&
+       git checkout -b interrupted-squash2 conflict-branch &&
        one=$(git rev-parse HEAD~3) &&
        (
                FAKE_LINES="3 squash 1 2" &&
index e12cd578e8663ad8717aecde310bb0b6a500c2a2..2062b858bbcb63a715d87f3b12adfd0e9ceb3a67 100755 (executable)
@@ -32,8 +32,8 @@ test_expect_success rebase '
 
        git checkout side &&
        git rebase master &&
-       git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
-       git cat-file commit side@{1} | sed -e "1,/^$/d" >expect &&
+       git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+       git cat-file commit side@{1} | sed -e "1,/^\$/d" >expect &&
        test_cmp expect actual
 
 '
diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh
new file mode 100755 (executable)
index 0000000..b63f4e2
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+test_description='auto squash'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo 0 >file0 &&
+       git add . &&
+       test_tick &&
+       git commit -m "initial commit" &&
+       echo 0 >file1 &&
+       echo 2 >file2 &&
+       git add . &&
+       test_tick &&
+       git commit -m "first commit" &&
+       echo 3 >file3 &&
+       git add . &&
+       test_tick &&
+       git commit -m "second commit" &&
+       git tag base
+'
+
+test_expect_success 'auto fixup' '
+       git reset --hard base &&
+       echo 1 >file1 &&
+       git add -u &&
+       test_tick &&
+       git commit -m "fixup! first"
+
+       git tag final-fixup &&
+       test_tick &&
+       git rebase --autosquash -i HEAD^^^ &&
+       git log --oneline >actual &&
+       test 3 = $(wc -l <actual) &&
+       git diff --exit-code final-fixup &&
+       test 1 = "$(git cat-file blob HEAD^:file1)" &&
+       test 1 = $(git cat-file commit HEAD^ | grep first | wc -l)
+'
+
+test_expect_success 'auto squash' '
+       git reset --hard base &&
+       echo 1 >file1 &&
+       git add -u &&
+       test_tick &&
+       git commit -m "squash! first"
+
+       git tag final-squash &&
+       test_tick &&
+       git rebase --autosquash -i HEAD^^^ &&
+       git log --oneline >actual &&
+       test 3 = $(wc -l <actual) &&
+       git diff --exit-code final-squash &&
+       test 1 = "$(git cat-file blob HEAD^:file1)" &&
+       test 2 = $(git cat-file commit HEAD^ | grep first | wc -l)
+'
+
+test_expect_success 'misspelled auto squash' '
+       git reset --hard base &&
+       echo 1 >file1 &&
+       git add -u &&
+       test_tick &&
+       git commit -m "squash! forst"
+       git tag final-missquash &&
+       test_tick &&
+       git rebase --autosquash -i HEAD^^^ &&
+       git log --oneline >actual &&
+       test 4 = $(wc -l <actual) &&
+       git diff --exit-code final-missquash &&
+       test 0 = $(git rev-list final-missquash...HEAD | wc -l)
+'
+
+test_done
diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh
new file mode 100755 (executable)
index 0000000..ddf2f64
--- /dev/null
@@ -0,0 +1,105 @@
+#!/bin/sh
+
+test_description='git rebase --onto A...B'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-rebase.sh"
+
+# Rebase only the tip commit of "topic" on merge base between "master"
+# and "topic".  Cannot do this for "side" with "master" because there
+# is no single merge base.
+#
+#
+#          F---G topic                             G'
+#         /                                       /
+# A---B---C---D---E master      -->       A---B---C---D---E
+#      \   \ /
+#      \   x
+#       \ / \
+#        H---I---J---K side
+
+test_expect_success setup '
+       test_commit A &&
+       test_commit B &&
+       git branch side &&
+       test_commit C &&
+       git branch topic &&
+       git checkout side &&
+       test_commit H &&
+       git checkout master &&
+       test_tick &&
+       git merge H &&
+       git tag D &&
+       test_commit E &&
+       git checkout topic &&
+       test_commit F &&
+       test_commit G &&
+       git checkout side &&
+       test_tick &&
+       git merge C &&
+       git tag I &&
+       test_commit J &&
+       test_commit K
+'
+
+test_expect_success 'rebase --onto master...topic' '
+       git reset --hard &&
+       git checkout topic &&
+       git reset --hard G &&
+
+       git rebase --onto master...topic F &&
+       git rev-parse HEAD^1 >actual &&
+       git rev-parse C^0 >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rebase --onto master...' '
+       git reset --hard &&
+       git checkout topic &&
+       git reset --hard G &&
+
+       git rebase --onto master... F &&
+       git rev-parse HEAD^1 >actual &&
+       git rev-parse C^0 >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rebase --onto master...side' '
+       git reset --hard &&
+       git checkout side &&
+       git reset --hard K &&
+
+       test_must_fail git rebase --onto master...side J
+'
+
+test_expect_success 'rebase -i --onto master...topic' '
+       git reset --hard &&
+       git checkout topic &&
+       git reset --hard G &&
+       set_fake_editor &&
+       EXPECT_COUNT=1 git rebase -i --onto master...topic F &&
+       git rev-parse HEAD^1 >actual &&
+       git rev-parse C^0 >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rebase -i --onto master...' '
+       git reset --hard &&
+       git checkout topic &&
+       git reset --hard G &&
+       set_fake_editor &&
+       EXPECT_COUNT=1 git rebase -i --onto master... F &&
+       git rev-parse HEAD^1 >actual &&
+       git rev-parse C^0 >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'rebase -i --onto master...side' '
+       git reset --hard &&
+       git checkout side &&
+       git reset --hard K &&
+
+       test_must_fail git rebase -i --onto master...side J
+'
+
+test_done
index bb4cf00d78637b3bdf0c4e3da99291e0ab3c718f..7f858151d4d7c1548803944de0dd8c54cdd8c78b 100755 (executable)
@@ -66,7 +66,7 @@ test_expect_success 'revert forbidden on dirty working tree' '
        echo content >extra_file &&
        git add extra_file &&
        test_must_fail git revert HEAD 2>errors &&
-       grep "Dirty index" errors
+       grep "Your local changes would be overwritten by " errors
 
 '
 
index 58680524258037eb0c17f7e2929d58ed1c16b574..14da45fe5a19f7436d9748f934c59b8a431d30dc 100755 (executable)
@@ -25,7 +25,7 @@ for_each_name () {
        for name in \
            Name "Name and a${LF}LF" "Name and an${HT}HT" "Name${DQ}" \
            "$FN$HT$GN" "$FN$LF$GN" "$FN $GN" "$FN$GN" "$FN$DQ$GN" \
-           "With SP in it"
+           "With SP in it" "caractère spécial/file"
        do
                eval "$1"
        done
@@ -33,6 +33,7 @@ for_each_name () {
 
 test_expect_success setup '
 
+       mkdir "caractère spécial" &&
        for_each_name "echo initial >\"\$name\""
        git add . &&
        git commit -q -m Initial &&
@@ -50,6 +51,7 @@ Name
 "Name and an\tHT"
 "Name\""
 With SP in it
+"caract\303\250re sp\303\251cial/file"
 "\346\277\261\351\207\216\t\347\264\224"
 "\346\277\261\351\207\216\n\347\264\224"
 "\346\277\261\351\207\216 \347\264\224"
@@ -63,6 +65,7 @@ Name
 "Name and an\tHT"
 "Name\""
 With SP in it
+caractère spécial/file
 "濱野\t純"
 "濱野\n純"
 濱野 純
@@ -97,6 +100,13 @@ test_expect_success 'check fully quoted output from diff-tree' '
 
 '
 
+test_expect_success 'check fully quoted output from ls-tree' '
+
+       git ls-tree --name-only -r HEAD >current &&
+       test_cmp expect.quoted current
+
+'
+
 test_expect_success 'setting core.quotepath' '
 
        git config --bool core.quotepath false
@@ -130,4 +140,11 @@ test_expect_success 'check fully quoted output from diff-tree' '
 
 '
 
+test_expect_success 'check fully quoted output from ls-tree' '
+
+       git ls-tree --name-only -r HEAD >current &&
+       test_cmp expect.raw current
+
+'
+
 test_done
index 8c1b81e248bced2ccb5e4ff0067996462e89deb8..ff8c2f7532aa6fd748dbbdc0322384576f3a35ed 100755 (executable)
@@ -20,8 +20,6 @@ test_expect_success \
     'test_chmod +x rezrov &&
      git diff-index $tree >current'
 
-_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"
 sed -e 's/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' <current >check
 echo ":100644 100755 X X M     rezrov" >expected
 
index f64aa48d24f2f5704d07b9285c94ba983f7d92ac..bc46563afc607e6fba9acbd2773d88207c878981 100755 (executable)
@@ -77,10 +77,6 @@ test_expect_success 'apply binary patch' \
         tree1=`git write-tree` &&
         test "$tree1" = "$tree0"'
 
-q_to_nul() {
-       perl -pe 'y/Q/\000/'
-}
-
 nul_to_q() {
        perl -pe 'y/\000/Q/'
 }
index 3bc1cccf8869aef26e175e207dc2923d3ddb1e65..f2a2aaa2b9c7fd84634bb74febb3f0f5ac1793e1 100755 (executable)
@@ -93,9 +93,9 @@ test_expect_success 'extra headers' '
        git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>
 " &&
        git format-patch --stdout master..side > patch2 &&
-       sed -e "/^$/q" patch2 > hdrs2 &&
-       grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs2 &&
-       grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs2
+       sed -e "/^\$/q" patch2 > hdrs2 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>\$" hdrs2 &&
+       grep "^Cc: S. E. Cipient <scipient@example.com>\$" hdrs2
 
 '
 
@@ -104,9 +104,9 @@ test_expect_success 'extra headers without newlines' '
        git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
        git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>" &&
        git format-patch --stdout master..side >patch3 &&
-       sed -e "/^$/q" patch3 > hdrs3 &&
-       grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs3 &&
-       grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs3
+       sed -e "/^\$/q" patch3 > hdrs3 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>\$" hdrs3 &&
+       grep "^Cc: S. E. Cipient <scipient@example.com>\$" hdrs3
 
 '
 
@@ -115,32 +115,32 @@ test_expect_success 'extra headers with multiple To:s' '
        git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
        git config --add format.headers "To: S. E. Cipient <scipient@example.com>" &&
        git format-patch --stdout master..side > patch4 &&
-       sed -e "/^$/q" patch4 > hdrs4 &&
-       grep "^To: R. E. Cipient <rcipient@example.com>,$" hdrs4 &&
-       grep "^ *S. E. Cipient <scipient@example.com>$" hdrs4
+       sed -e "/^\$/q" patch4 > hdrs4 &&
+       grep "^To: R. E. Cipient <rcipient@example.com>,\$" hdrs4 &&
+       grep "^ *S. E. Cipient <scipient@example.com>\$" hdrs4
 '
 
 test_expect_success 'additional command line cc' '
 
        git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" &&
-       git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch5 &&
-       grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch5 &&
-       grep "^ *S. E. Cipient <scipient@example.com>$" patch5
+       git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
+       grep "^Cc: R. E. Cipient <rcipient@example.com>,\$" patch5 &&
+       grep "^ *S. E. Cipient <scipient@example.com>\$" patch5
 '
 
 test_expect_success 'command line headers' '
 
        git config --unset-all format.headers &&
-       git format-patch --add-header="Cc: R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch6 &&
-       grep "^Cc: R. E. Cipient <rcipient@example.com>$" patch6
+       git format-patch --add-header="Cc: R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch6 &&
+       grep "^Cc: R. E. Cipient <rcipient@example.com>\$" patch6
 '
 
 test_expect_success 'configuration headers and command line headers' '
 
        git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" &&
-       git format-patch --add-header="Cc: S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch7 &&
-       grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch7 &&
-       grep "^ *S. E. Cipient <scipient@example.com>$" patch7
+       git format-patch --add-header="Cc: S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch7 &&
+       grep "^Cc: R. E. Cipient <rcipient@example.com>,\$" patch7 &&
+       grep "^ *S. E. Cipient <scipient@example.com>\$" patch7
 '
 
 test_expect_success 'multiple files' '
@@ -406,9 +406,9 @@ test_expect_success 'cover-letter inherits diff options' '
        git mv file foo &&
        git commit -m foo &&
        git format-patch --cover-letter -1 &&
-       ! grep "file => foo .* 0 *$" 0000-cover-letter.patch &&
+       ! grep "file => foo .* 0 *\$" 0000-cover-letter.patch &&
        git format-patch --cover-letter -1 -M &&
-       grep "file => foo .* 0 *$" 0000-cover-letter.patch
+       grep "file => foo .* 0 *\$" 0000-cover-letter.patch
 
 '
 
@@ -425,7 +425,7 @@ EOF
 test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
 
        git format-patch --cover-letter -2 &&
-       sed -e "1,/A U Thor/d" -e "/^$/q" < 0000-cover-letter.patch > output &&
+       sed -e "1,/A U Thor/d" -e "/^\$/q" < 0000-cover-letter.patch > output &&
        test_cmp expect output
 
 '
@@ -450,7 +450,7 @@ EOF
 test_expect_success 'format-patch respects -U' '
 
        git format-patch -U4 -2 &&
-       sed -e "1,/^$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+       sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
        test_cmp expect output
 
 '
@@ -471,7 +471,7 @@ 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 &&
+       sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
        test_cmp expect output
 
 '
index 8dd147d78f6ee6a4be4b1af235ed35af19b273df..90f33423731a84310caf92780482a44f8d2f32d8 100755 (executable)
@@ -93,8 +93,6 @@ git diff > out
 test_expect_success 'another test, without options' 'test_cmp expect out'
 
 cat << EOF > expect
-diff --git a/x b/x
-index d99af23..8b32fb5 100644
 EOF
 git diff -w > out
 test_expect_success 'another test, with -w' 'test_cmp expect out'
@@ -386,6 +384,18 @@ test_expect_success 'checkdiff allows new blank lines' '
        git diff --check
 '
 
+cat <<EOF >expect
+EOF
+test_expect_success 'whitespace-only changes not reported' '
+       git reset --hard &&
+       echo >x "hello world" &&
+       git add x &&
+       git commit -m "hello 1" &&
+       echo >x "hello  world" &&
+       git diff -b >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'combined diff with autocrlf conversion' '
 
        git reset --hard &&
index 5cf8924b2163ec55328ce270911a23cdcf52634a..83c19147717f2a5e6c634918c74b93e54376d725 100755 (executable)
@@ -32,7 +32,8 @@ test_expect_success setup '
                cd sub &&
                git rev-list HEAD
        ) &&
-       echo ":160000 160000 $3 $_z40 M sub" >expect
+       echo ":160000 160000 $3 $_z40 M sub" >expect &&
+       subtip=$3 subprev=$2
 '
 
 test_expect_success 'git diff --raw HEAD' '
@@ -50,6 +51,87 @@ test_expect_success 'git diff-files --raw' '
        test_cmp expect actual.files
 '
 
+expect_from_to () {
+       printf "%sSubproject commit %s\n+Subproject commit %s\n" \
+               "-" "$1" "$2"
+}
+
+test_expect_success 'git diff HEAD' '
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev &&
+       test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (work tree)' '
+       echo >>sub/world &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev-dirty &&
+       test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (index)' '
+       (
+               cd sub &&
+               git reset --hard &&
+               echo >>world &&
+               git add world
+       ) &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev-dirty &&
+       test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (untracked)' '
+       (
+               cd sub &&
+               git reset --hard &&
+               git clean -qfdx &&
+               >cruft
+       ) &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subtip $subprev-dirty &&
+       test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' '
+       git commit -m "x" sub &&
+       echo >>sub/world &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (index, refs match)' '
+       (
+               cd sub &&
+               git reset --hard &&
+               echo >>world &&
+               git add world
+       ) &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual.body
+'
+
+test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' '
+       (
+               cd sub &&
+               git reset --hard &&
+               git clean -qfdx &&
+               >cruft
+       ) &&
+       git diff HEAD >actual &&
+       sed -e "1,/^@@/d" actual >actual.body &&
+       expect_from_to >expect.body $subprev $subprev-dirty &&
+       test_cmp expect.body actual.body
+'
+
 test_expect_success 'git diff (empty submodule dir)' '
        : >empty &&
        rm -rf sub/* sub/.git &&
index a3f0897a52ce2147388baeac6fc64d3b8501b516..88c5619ae7471ab0d3286259d88c437ae3953b4a 100755 (executable)
@@ -48,7 +48,7 @@ test_expect_success 'file is considered binary by plumbing' '
 
 test_expect_success 'setup textconv filters' '
        echo file diff=foo >.gitattributes &&
-       git config diff.foo.textconv "$PWD"/hexdump &&
+       git config diff.foo.textconv "\"$(pwd)\""/hexdump &&
        git config diff.fail.textconv false
 '
 
index a894c6062271c53b688830a3230d1c78fb561d70..7e7b307a24606131b4880817a0056af11973f3d2 100755 (executable)
@@ -54,7 +54,7 @@ chmod +x dump
 
 test_expect_success 'setup textconv' '
        echo file diff=foo >.gitattributes &&
-       git config diff.foo.textconv "$PWD"/dump
+       git config diff.foo.textconv "\"$(pwd)\""/dump
 '
 
 test_expect_success 'rewrite diff respects textconv' '
index 1c21276c55400c594f71bc01eba17b0d3820b420..2e2e103b31332ea2f74de5d5e6e49c00b13dfa8a 100755 (executable)
@@ -12,19 +12,9 @@ test_expect_success setup '
 
 '
 
-decrypt_color () {
-       sed \
-               -e 's/.\[1m/<WHITE>/g' \
-               -e 's/.\[31m/<RED>/g' \
-               -e 's/.\[32m/<GREEN>/g' \
-               -e 's/.\[35m/<MAGENTA>/g' \
-               -e 's/.\[36m/<BROWN>/g' \
-               -e 's/.\[m/<RESET>/g'
-}
-
 word_diff () {
        test_must_fail git diff --no-index "$@" pre post > output &&
-       decrypt_color < output > output.decrypted &&
+       test_decode_color <output >output.decrypted &&
        test_cmp expect output.decrypted
 }
 
@@ -49,7 +39,7 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1,3 +1,7 @@<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
 <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
 
 a = b + c<RESET>
@@ -70,9 +60,9 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1 +1 @@<RESET>
+<CYAN>@@ -1 +1 @@<RESET>
 <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
-<BROWN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET>
+<CYAN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
 
@@ -90,7 +80,7 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1,3 +1,7 @@<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
 h(4),<GREEN>hh<RESET>[44]
 
 a = b + c<RESET>
@@ -126,7 +116,7 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1,3 +1,7 @@<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
 h(4)<GREEN>,hh[44]<RESET>
 
 a = b + c<RESET>
@@ -168,7 +158,7 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1,3 +1,7 @@<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
 h(4),<GREEN>hh[44<RESET>]
 
 a = b + c<RESET>
@@ -190,7 +180,7 @@ cat > expect <<\EOF
 <WHITE>index c29453b..be22f37 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1 +1 @@<RESET>
+<CYAN>@@ -1 +1 @@<RESET>
 aaa (aaa) <GREEN>aaa<RESET>
 EOF
 
@@ -209,7 +199,7 @@ cat > expect <<\EOF
 <WHITE>index 289cb9d..2d06f37 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1 +1 @@<RESET>
+<CYAN>@@ -1 +1 @@<RESET>
 (<RED>:<RESET>
 EOF
 
index 2cf7e01ac2c42216dc935d2d2d9dd23b7ae62c4f..7584efa36b06effd9005b8ebcc6afecec07e424b 100755 (executable)
@@ -76,7 +76,7 @@ test_expect_success 'check combined output (1)' '
        verify_helper sidewithone
 '
 
-test_expect_failure 'check combined output (2)' '
+test_expect_success 'check combined output (2)' '
        git show sidesansone -- >sidesansone &&
        verify_helper sidesansone
 '
diff --git a/t/t4040-whitespace-status.sh b/t/t4040-whitespace-status.sh
new file mode 100755 (executable)
index 0000000..a30b03b
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+test_description='diff --exit-code with whitespace'
+. ./test-lib.sh
+
+test_expect_success setup '
+       mkdir a b &&
+       echo >c &&
+       echo >a/d &&
+       echo >b/e &&
+       git add . &&
+       test_tick &&
+       git commit -m initial &&
+       echo " " >a/d &&
+       test_tick &&
+       git commit -a -m second &&
+       echo "  " >a/d &&
+       echo " " >b/e &&
+       git add a/d
+'
+
+test_expect_success 'diff-tree --exit-code' '
+       test_must_fail git diff --exit-code HEAD^ HEAD &&
+       test_must_fail git diff-tree --exit-code HEAD^ HEAD
+'
+
+test_expect_success 'diff-tree -b --exit-code' '
+       git diff -b --exit-code HEAD^ HEAD &&
+       git diff-tree -b -p --exit-code HEAD^ HEAD &&
+       git diff-tree -b --exit-code HEAD^ HEAD
+'
+
+test_expect_success 'diff-index --cached --exit-code' '
+       test_must_fail git diff --cached --exit-code HEAD &&
+       test_must_fail git diff-index --cached --exit-code HEAD
+'
+
+test_expect_success 'diff-index -b -p --cached --exit-code' '
+       git diff -b --cached --exit-code HEAD &&
+       git diff-index -b -p --cached --exit-code HEAD
+'
+
+test_expect_success 'diff-index --exit-code' '
+       test_must_fail git diff --exit-code HEAD &&
+       test_must_fail git diff-index --exit-code HEAD
+'
+
+test_expect_success 'diff-index -b -p --exit-code' '
+       git diff -b --exit-code HEAD &&
+       git diff-index -b -p --exit-code HEAD
+'
+
+test_expect_success 'diff-files --exit-code' '
+       test_must_fail git diff --exit-code &&
+       test_must_fail git diff-files --exit-code
+'
+
+test_expect_success 'diff-files -b -p --exit-code' '
+       git diff -b --exit-code &&
+       git diff-files -b -p --exit-code
+'
+
+test_done
index 5bb4fed3f5056ddb6121b68bb43234870cb0f28f..464305405ac715411b9cc5faabf55d116f0c6ec7 100755 (executable)
@@ -191,6 +191,73 @@ EOF
 "
 
 commit_file sm1 &&
+test_expect_success 'submodule is up to date' "
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+EOF
+"
+
+test_expect_success 'submodule contains untracked content' "
+       echo new > sm1/new-file &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6..$head6-dirty:
+EOF
+"
+
+test_expect_success 'submodule contains untracked and modifed content' "
+       echo new > sm1/foo6 &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6..$head6-dirty:
+EOF
+"
+
+test_expect_success 'submodule contains modifed content' "
+       rm -f sm1/new-file &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6..$head6-dirty:
+EOF
+"
+
+(cd sm1; git commit -mchange foo6 >/dev/null) &&
+head8=$(cd sm1; git rev-parse --verify HEAD | cut -c1-7) &&
+test_expect_success 'submodule is modified' "
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6..$head8:
+  > change
+EOF
+"
+
+test_expect_success 'modified submodule contains untracked content' "
+       echo new > sm1/new-file &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6..$head8-dirty:
+  > change
+EOF
+"
+
+test_expect_success 'modified submodule contains untracked and modifed content' "
+       echo modification >> sm1/foo6 &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6..$head8-dirty:
+  > change
+EOF
+"
+
+test_expect_success 'modified submodule contains modifed content' "
+       rm -f sm1/new-file &&
+       git diff-index -p --submodule=log HEAD >actual &&
+       diff actual - <<-EOF
+Submodule sm1 $head6..$head8-dirty:
+  > change
+EOF
+"
+
 rm -rf sm1
 test_expect_success 'deleted submodule' "
        git diff-index -p --submodule=log HEAD >actual &&
index 83d4ba679850c2ae2548bcfcce3f22227fcde8c7..b463b4f05ce43ee345a0594528d684befe7c2ef3 100755 (executable)
@@ -22,4 +22,9 @@ test_expect_success 'apply git diff with -p2' '
        git apply -p2 patch.file
 '
 
+test_expect_success 'apply with too large -p' '
+       test_must_fail git apply --stat -p3 patch.file 2>err &&
+       grep "removing 3 leading" err
+'
+
 test_done
index 3b471b641ba2d3274dd1ab89948485ff8ce4dfdb..9671de799949f8f67f906cd3db907e3a53cf4eef 100755 (executable)
@@ -37,11 +37,11 @@ test_expect_success setup '
        # patch-2 is the same as patch-1 but is based
        # on a version that already has whitespace fixed,
        # and does not introduce whitespace breakages.
-       sed -e "s/ $//" patch-1 >patch-2 &&
+       sed -e "s/ \$//" patch-1 >patch-2 &&
 
        # If all whitespace breakages are fixed the contents
        # should look like file-fixed
-       sed -e "s/ $//" file-1 >file-fixed
+       sed -e "s/ \$//" file-1 >file-fixed
 
 '
 
diff --git a/t/t4133-apply-filenames.sh b/t/t4133-apply-filenames.sh
new file mode 100755 (executable)
index 0000000..3421807
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Andreas Gruenbacher
+#
+
+test_description='git apply filename consistency check'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       cat > bad1.patch <<EOF
+diff --git a/f b/f
+new file mode 100644
+index 0000000..d00491f
+--- /dev/null
++++ b/f-blah
+@@ -0,0 +1 @@
++1
+EOF
+       cat > bad2.patch <<EOF
+diff --git a/f b/f
+deleted file mode 100644
+index d00491f..0000000
+--- b/f-blah
++++ /dev/null
+@@ -1 +0,0 @@
+-1
+EOF
+'
+
+test_expect_success 'apply diff with inconsistent filenames in headers' '
+       test_must_fail git apply bad1.patch 2>err
+       grep "inconsistent new filename" err
+       test_must_fail git apply bad2.patch 2>err
+       grep "inconsistent old filename" err
+'
+
+test_done
index 8296605234ddc82697a51de0a09b703635b4c9f4..810b04b817c79d2b4c478f767843b4e7a42e0bed 100755 (executable)
@@ -83,6 +83,12 @@ test_expect_success setup '
                echo "X-Fake-Field: Line Three" &&
                git format-patch --stdout first | sed -e "1d"
        } > patch1.eml &&
+       {
+               echo "X-Fake-Field: Line One" &&
+               echo "X-Fake-Field: Line Two" &&
+               echo "X-Fake-Field: Line Three" &&
+               git format-patch --stdout first | sed -e "1d"
+       } | append_cr >patch1-crlf.eml &&
        sed -n -e "3,\$p" msg >file &&
        git add file &&
        test_tick &&
@@ -123,6 +129,15 @@ test_expect_success 'am applies patch e-mail not in a mbox' '
        test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
 '
 
+test_expect_success 'am applies patch e-mail not in a mbox with CRLF' '
+       git checkout first &&
+       git am patch1-crlf.eml &&
+       ! test -d .git/rebase-apply &&
+       test -z "$(git diff second)" &&
+       test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
+       test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
+'
+
 GIT_AUTHOR_NAME="Another Thor"
 GIT_AUTHOR_EMAIL="a.thor@example.com"
 GIT_COMMITTER_NAME="Co M Miter"
@@ -287,7 +302,7 @@ test_expect_success 'am --committer-date-is-author-date' '
        git checkout first &&
        test_tick &&
        git am --committer-date-is-author-date patch1 &&
-       git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+       git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
        at=$(sed -ne "/^author /s/.*> //p" head1) &&
        ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
        test "$at" = "$ct"
@@ -297,7 +312,7 @@ test_expect_success 'am without --committer-date-is-author-date' '
        git checkout first &&
        test_tick &&
        git am patch1 &&
-       git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+       git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
        at=$(sed -ne "/^author /s/.*> //p" head1) &&
        ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
        test "$at" != "$ct"
@@ -311,7 +326,7 @@ test_expect_success 'am --ignore-date' '
        git checkout first &&
        test_tick &&
        git am --ignore-date patch1 &&
-       git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+       git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
        at=$(sed -ne "/^author /s/.*> //p" head1) &&
        echo "$at" | grep "+0000"
 '
index a6bc028a57115729d38e4b228cd259880d0bf6f8..bb402c3780356d1feab4e8b7c9b9624495d3e176 100755 (executable)
@@ -217,7 +217,22 @@ test_expect_success 'rerere.autoupdate' '
        git checkout version2 &&
        test_must_fail git merge fifth &&
        test 0 = $(git ls-files -u | wc -l)
+'
 
+test_expect_success 'merge --rerere-autoupdate' '
+       git config --unset rerere.autoupdate
+       git reset --hard &&
+       git checkout version2 &&
+       test_must_fail git merge --rerere-autoupdate fifth &&
+       test 0 = $(git ls-files -u | wc -l)
+'
+
+test_expect_success 'merge --no-rerere-autoupdate' '
+       git config rerere.autoupdate true
+       git reset --hard &&
+       git checkout version2 &&
+       test_must_fail git merge --no-rerere-autoupdate fifth &&
+       test 2 = $(git ls-files -u | wc -l)
 '
 
 test_done
index 779a5adf554d77464f2a326a2eb919e224993746..1dc224f6fbf074434728d326b8a36160e9032192 100755 (executable)
@@ -255,7 +255,7 @@ EOF
 
 test_expect_success 'log --graph with merge' '
        git log --graph --date-order --pretty=tformat:%s |
-               sed "s/ *$//" >actual &&
+               sed "s/ *\$//" >actual &&
        test_cmp expect actual
 '
 
@@ -315,7 +315,7 @@ EOF
 test_expect_success 'log --graph with full output' '
        git log --graph --date-order --pretty=short |
                git name-rev --name-only --stdin |
-               sed "s/Merge:.*/Merge: A B/;s/ *$//" >actual &&
+               sed "s/Merge:.*/Merge: A B/;s/ *\$//" >actual &&
        test_cmp expect actual
 '
 
@@ -383,7 +383,7 @@ EOF
 
 test_expect_success 'log --graph with merge' '
        git log --graph --date-order --pretty=tformat:%s |
-               sed "s/ *$//" >actual &&
+               sed "s/ *\$//" >actual &&
        test_cmp expect actual
 '
 
index e2aa254eae2ea930f29c4735f3f3d11bc790c455..1058d981dc85b2a7b63aca6e9cbb48f07e0a09a3 100755 (executable)
@@ -16,7 +16,9 @@ test_expect_success \
      perl -e "print \"a\" x 4096;" > a &&
      perl -e "print \"b\" x 4096;" > b &&
      perl -e "print \"c\" x 4096;" > c &&
-     git update-index --add a b c &&
+     test-genrandom "seed a" 2097152 > a_big &&
+     test-genrandom "seed b" 2097152 > b_big &&
+     git update-index --add a a_big b b_big c &&
      cat c >d && echo foo >>d && git update-index --add d &&
      tree=`git write-tree` &&
      commit=`git commit-tree $tree </dev/null` && {
@@ -280,26 +282,8 @@ test_expect_success \
 
      :'
 
-test_expect_success \
-    'fake a SHA1 hash collision' \
-    'test -f   .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67 &&
-     cp -f     .git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \
-               .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67'
-
-test_expect_success \
-    'make sure index-pack detects the SHA1 collision' \
-    'test_must_fail git index-pack -o bad.idx test-3.pack 2>msg &&
-     grep "SHA1 COLLISION FOUND" msg'
-
-test_expect_success \
-    'honor pack.packSizeLimit' \
-    'git config pack.packSizeLimit 200 &&
-     packname_4=$(git pack-objects test-4 <obj-list) &&
-     test 3 = $(ls test-4-*.pack | wc -l)'
-
 test_expect_success 'unpacking with --strict' '
 
-       git config --unset pack.packsizelimit &&
        for j in a b c d e f g
        do
                for i in 0 1 2 3 4 5 6 7 8 9
@@ -392,10 +376,42 @@ test_expect_success 'index-pack with --strict' '
        )
 '
 
-test_expect_success 'tolerate absurdly small packsizelimit' '
-       git config pack.packSizeLimit 2 &&
-       packname_9=$(git pack-objects test-9 <obj-list) &&
-       test $(wc -l <obj-list) = $(ls test-9-*.pack | wc -l)
+test_expect_success 'honor pack.packSizeLimit' '
+       git config pack.packSizeLimit 3m &&
+       packname_10=$(git pack-objects test-10 <obj-list) &&
+       test 2 = $(ls test-10-*.pack | wc -l)
+'
+
+test_expect_success 'verify resulting packs' '
+       git verify-pack test-10-*.pack
+'
+
+test_expect_success 'tolerate packsizelimit smaller than biggest object' '
+       git config pack.packSizeLimit 1 &&
+       packname_11=$(git pack-objects test-11 <obj-list) &&
+       test 3 = $(ls test-11-*.pack | wc -l)
 '
 
+test_expect_success 'verify resulting packs' '
+       git verify-pack test-11-*.pack
+'
+
+#
+# WARNING!
+#
+# The following test is destructive.  Please keep the next
+# two tests at the end of this file.
+#
+
+test_expect_success \
+    'fake a SHA1 hash collision' \
+    'test -f   .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67 &&
+     cp -f     .git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \
+               .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67'
+
+test_expect_success \
+    'make sure index-pack detects the SHA1 collision' \
+    'test_must_fail git index-pack -o bad.idx test-3.pack 2>msg &&
+     grep "SHA1 COLLISION FOUND" msg'
+
 test_done
index f2d5581b12f7d70c9f346da75dced81e12bd4c7f..c718253673ec8b3baf635c4dc0b05fe127c6f4cd 100755 (executable)
@@ -32,7 +32,7 @@ test_expect_success setup '
        done &&
        git update-ref HEAD "$commit" &&
        git clone ./. victim &&
-       ( cd victim && git log ) &&
+       ( cd victim && git config receive.denyCurrentBranch warn && git log ) &&
        git update-ref HEAD "$zero" &&
        parent=$zero &&
        i=0 &&
@@ -129,6 +129,7 @@ rewound_push_setup() {
            cd parent &&
            git init &&
            echo one >file && git add file && git commit -m one &&
+           git config receive.denyCurrentBranch warn &&
            echo two >file && git commit -a -m two
        ) &&
        git clone parent child &&
@@ -190,16 +191,11 @@ test_expect_success 'pushing wildcard refspecs respects forcing' '
        test "$parent_head" = "$child_head"
 '
 
-test_expect_success 'warn pushing to delete current branch' '
+test_expect_success 'deny pushing to delete current branch' '
        rewound_push_setup &&
        (
            cd child &&
-           git send-pack ../parent :refs/heads/master 2>errs
-       ) &&
-       grep "warning: to refuse deleting" child/errs &&
-       (
-               cd parent &&
-               test_must_fail git rev-parse --verify master
+           test_must_fail git send-pack ../parent :refs/heads/master 2>errs
        )
 '
 
index 64f66c94f36538b1c7d20045fc4233aa0b9d9a0d..325714e5299a5b59157c3741e7fa0d98d70b9990 100755 (executable)
@@ -18,6 +18,7 @@ test_expect_success setup '
        git update-ref refs/heads/master $commit0 &&
        git update-ref refs/heads/tofail $commit1 &&
        git clone ./. victim &&
+       GIT_DIR=victim/.git git config receive.denyCurrentBranch warn &&
        GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 &&
        git update-ref refs/heads/master $commit1 &&
        git update-ref refs/heads/tofail $commit0
index cb9aacc7bc62e2ecfdca0dc7f6071e5330fe09d0..4bda18a662da5a0c36ee007c37d9ded6be3b1832 100755 (executable)
@@ -8,6 +8,7 @@ test_expect_success setup '
 
        >file1 && git add file1 && test_tick &&
        git commit -m Initial &&
+       git config receive.denyCurrentBranch warn &&
 
        mkdir another && (
                cd another &&
index 936fe0a1a635bc8a553962f13bff32e873c358cd..a82c5ffa1c608f45786fe37531ffe93008a3570b 100755 (executable)
@@ -533,5 +533,219 @@ test_expect_success 'show empty remote' '
        )
 '
 
-test_done
+test_expect_success 'new remote' '
+(
+       git remote add someremote foo &&
+       echo foo >expect &&
+       git config --get-all remote.someremote.url >actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url bar' '
+(
+       git remote set-url someremote bar &&
+       echo bar >expect &&
+       git config --get-all remote.someremote.url >actual &&
+       cmp expect actual
+)
+'
 
+test_expect_success 'remote set-url baz bar' '
+(
+       git remote set-url someremote baz bar &&
+       echo baz >expect &&
+       git config --get-all remote.someremote.url >actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url zot bar' '
+(
+       test_must_fail git remote set-url someremote zot bar &&
+       echo baz >expect &&
+       git config --get-all remote.someremote.url >actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push zot baz' '
+(
+       test_must_fail git remote set-url --push someremote zot baz &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push zot' '
+(
+       git remote set-url --push someremote zot &&
+       echo zot >expect &&
+       echo "YYY" >>expect &&
+       echo baz >>expect &&
+       git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push qux zot' '
+(
+       git remote set-url --push someremote qux zot &&
+       echo qux >expect &&
+       echo "YYY" >>expect &&
+       echo baz >>expect &&
+       git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push foo qu+x' '
+(
+       git remote set-url --push someremote foo qu+x &&
+       echo foo >expect &&
+       echo "YYY" >>expect &&
+       echo baz >>expect &&
+       git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push --add aaa' '
+(
+       git remote set-url --push --add someremote aaa &&
+       echo foo >expect &&
+       echo aaa >>expect &&
+       echo "YYY" >>expect &&
+       echo baz >>expect &&
+       git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push bar aaa' '
+(
+       git remote set-url --push someremote bar aaa &&
+       echo foo >expect &&
+       echo bar >>expect &&
+       echo "YYY" >>expect &&
+       echo baz >>expect &&
+       git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push --delete bar' '
+(
+       git remote set-url --push --delete someremote bar &&
+       echo foo >expect &&
+       echo "YYY" >>expect &&
+       echo baz >>expect &&
+       git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --push --delete foo' '
+(
+       git remote set-url --push --delete someremote foo &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --add bbb' '
+(
+       git remote set-url --add someremote bbb &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       echo bbb >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --delete .*' '
+(
+       test_must_fail git remote set-url --delete someremote .* &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       echo bbb >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --delete bbb' '
+(
+       git remote set-url --delete someremote bbb &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --delete baz' '
+(
+       test_must_fail git remote set-url --delete someremote baz &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --add ccc' '
+(
+       git remote set-url --add someremote ccc &&
+       echo "YYY" >expect &&
+       echo baz >>expect &&
+       echo ccc >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_expect_success 'remote set-url --delete baz' '
+(
+       git remote set-url --delete someremote baz &&
+       echo "YYY" >expect &&
+       echo ccc >>expect &&
+       test_must_fail git config --get-all remote.someremote.pushurl >actual &&
+       echo "YYY" >>actual &&
+       git config --get-all remote.someremote.url >>actual &&
+       cmp expect actual
+)
+'
+
+test_done
index 6889a53cf9bdea0aff88789f954ddf31d1eec010..0f04b2e8949dfec46fbc42af8347c9f3e6d302a7 100755 (executable)
@@ -12,6 +12,7 @@ mk_empty () {
        (
                cd testrepo &&
                git init &&
+               git config receive.denyCurrentBranch warn &&
                mv .git/hooks .git/hooks-disabled
        )
 }
@@ -546,6 +547,32 @@ test_expect_success 'allow deleting an invalid remote ref' '
 
 '
 
+test_expect_success 'allow deleting a ref using --delete' '
+       mk_test heads/master &&
+       (cd testrepo && git config receive.denyDeleteCurrent warn) &&
+       git push testrepo --delete master &&
+       (cd testrepo && test_must_fail git rev-parse --verify refs/heads/master)
+'
+
+test_expect_success 'allow deleting a tag using --delete' '
+       mk_test heads/master &&
+       git tag -a -m dummy_message deltag heads/master &&
+       git push testrepo --tags &&
+       (cd testrepo && git rev-parse --verify -q refs/tags/deltag) &&
+       git push testrepo --delete tag deltag &&
+       (cd testrepo && test_must_fail git rev-parse --verify refs/tags/deltag)
+'
+
+test_expect_success 'push --delete without args aborts' '
+       mk_test heads/master &&
+       test_must_fail git push testrepo --delete
+'
+
+test_expect_success 'push --delete refuses src:dest refspecs' '
+       mk_test heads/master &&
+       test_must_fail git push testrepo --delete master:foo
+'
+
 test_expect_success 'warn on push to HEAD of non-bare repository' '
        mk_test heads/master
        (cd testrepo &&
index ea49dedbf8867694d83cd550c8212ff107361920..e2ad2605084194868fc23e23fec1636af622b47f 100755 (executable)
@@ -19,7 +19,8 @@ mk_repo_pair () {
        mkdir mirror &&
        (
                cd mirror &&
-               git init
+               git init &&
+               git config receive.denyCurrentBranch warn
        ) &&
        mkdir master &&
        (
index 86bbd7d024ff6b1ee775ec737c550d54f9371ad7..7206817ca1c7a450b47f9a1d7d8a3af53452dac6 100755 (executable)
@@ -20,13 +20,19 @@ fi
 #
 # The working directory is subdir-link.
 
-mkdir subdir
-echo file >subdir/file
-git add subdir/file
-git commit -q -m file
-git clone -q . clone-repo
-ln -s clone-repo/subdir/ subdir-link
-
+test_expect_success setup '
+       mkdir subdir &&
+       echo file >subdir/file &&
+       git add subdir/file &&
+       git commit -q -m file &&
+       git clone -q . clone-repo &&
+       ln -s clone-repo/subdir/ subdir-link &&
+       (
+               cd clone-repo &&
+               git config receive.denyCurrentBranch warn
+       ) &&
+       git config receive.denyCurrentBranch warn
+'
 
 # Demonstrate that things work if we just avoid the symlink
 #
diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh
new file mode 100755 (executable)
index 0000000..00da707
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='push with --set-upstream'
+. ./test-lib.sh
+
+test_expect_success 'setup bare parent' '
+       git init --bare parent &&
+       git remote add upstream parent
+'
+
+test_expect_success 'setup local commit' '
+       echo content >file &&
+       git add file &&
+       git commit -m one
+'
+
+check_config() {
+       (echo $2; echo $3) >expect.$1
+       (git config branch.$1.remote
+        git config branch.$1.merge) >actual.$1
+       test_cmp expect.$1 actual.$1
+}
+
+test_expect_success 'push -u master:master' '
+       git push -u upstream master:master &&
+       check_config master upstream refs/heads/master
+'
+
+test_expect_success 'push -u master:other' '
+       git push -u upstream master:other &&
+       check_config master upstream refs/heads/other
+'
+
+test_expect_success 'push -u --dry-run master:otherX' '
+       git push -u --dry-run upstream master:otherX &&
+       check_config master upstream refs/heads/other
+'
+
+test_expect_success 'push -u master2:master2' '
+       git branch master2 &&
+       git push -u upstream master2:master2 &&
+       check_config master2 upstream refs/heads/master2
+'
+
+test_expect_success 'push -u master2:other2' '
+       git push -u upstream master2:other2 &&
+       check_config master2 upstream refs/heads/other2
+'
+
+test_expect_success 'push -u :master2' '
+       git push -u upstream :master2 &&
+       check_config master2 upstream refs/heads/other2
+'
+
+test_expect_success 'push -u --all' '
+       git branch all1 &&
+       git branch all2 &&
+       git push -u --all &&
+       check_config all1 upstream refs/heads/all1 &&
+       check_config all2 upstream refs/heads/all2
+'
+
+test_expect_success 'push -u HEAD' '
+       git checkout -b headbranch &&
+       git push -u upstream HEAD &&
+       check_config headbranch upstream refs/heads/headbranch
+'
+
+test_done
diff --git a/t/t5524-pull-msg.sh b/t/t5524-pull-msg.sh
new file mode 100755 (executable)
index 0000000..8cccecc
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_description='git pull message generation'
+
+. ./test-lib.sh
+
+dollar='$Dollar'
+
+test_expect_success setup '
+       test_commit initial afile original &&
+       git clone . cloned &&
+       (
+               cd cloned &&
+               echo added >bfile &&
+               git add bfile &&
+               test_tick &&
+               git commit -m "add bfile"
+       ) &&
+       test_tick && test_tick &&
+       echo "original $dollar" >afile &&
+       git add afile &&
+       git commit -m "do not clobber $dollar signs"
+'
+
+test_expect_success pull '
+(
+       cd cloned &&
+       git pull --log &&
+       git log -2 &&
+       git cat-file commit HEAD >result &&
+       grep Dollar result
+)
+'
+
+test_done
index 2a58d0cc9cbfeec1085c97bf288fcd1c50c03aef..53f54a2789557bcfd4e9843b50fe28189fa6f356 100755 (executable)
@@ -88,5 +88,45 @@ test_expect_success 'used receive-pack service' '
        test_cmp exp act
 '
 
+test_expect_success 'non-fast-forward push fails' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       git checkout master &&
+       echo "changed" > path2 &&
+       git commit -a -m path2 --amend &&
+
+       HEAD=$(git rev-parse --verify HEAD) &&
+       !(git push -v origin >output 2>&1) &&
+       (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
+        test $HEAD != $(git rev-parse --verify HEAD))
+'
+
+test_expect_success 'non-fast-forward push show ref status' '
+       grep "^ ! \[rejected\][ ]*master -> master (non-fast-forward)$" output
+'
+
+test_expect_success 'non-fast-forward push shows help message' '
+       grep "To prevent you from losing history, non-fast-forward updates were rejected" \
+               output
+'
+
+test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper' '
+       # create a dissimilarly-named remote ref so that git is unable to match the
+       # two refs (viz. local, remote) unless an explicit refspec is provided.
+       git push origin master:retsam
+
+       echo "change changed" > path2 &&
+       git commit -a -m path2 --amend &&
+
+       # push master too; this ensures there is at least one '"'push'"' command to
+       # the remote helper and triggers interaction with the helper.
+       !(git push -v origin +master master:retsam >output 2>&1) &&
+
+       grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *master -> master (forced update)$" output &&
+       grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output &&
+
+       grep "To prevent you from losing history, non-fast-forward updates were rejected" \
+               output
+'
+
 stop_httpd
 test_done
diff --git a/t/t5560-http-backend-noserver.sh b/t/t5560-http-backend-noserver.sh
new file mode 100755 (executable)
index 0000000..44885b8
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+test_description='test git-http-backend-noserver'
+. ./test-lib.sh
+
+HTTPD_DOCUMENT_ROOT_PATH="$TRASH_DIRECTORY"
+
+run_backend() {
+       echo "$2" |
+       QUERY_STRING="${1#*\?}" \
+       GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \
+       PATH_INFO="${1%%\?*}" \
+       git http-backend >act.out 2>act.err
+}
+
+GET() {
+       export REQUEST_METHOD="GET" &&
+       run_backend "/repo.git/$1" &&
+       unset REQUEST_METHOD &&
+       if ! grep "Status" act.out >act
+       then
+               printf "Status: 200 OK\r\n" >act
+       fi
+       printf "Status: $2\r\n" >exp &&
+       test_cmp exp act
+}
+
+POST() {
+       export REQUEST_METHOD="POST" &&
+       export CONTENT_TYPE="application/x-$1-request" &&
+       run_backend "/repo.git/$1" "$2" &&
+       unset REQUEST_METHOD &&
+       unset CONTENT_TYPE &&
+       if ! grep "Status" act.out >act
+       then
+               printf "Status: 200 OK\r\n" >act
+       fi
+       printf "Status: $3\r\n" >exp &&
+       test_cmp exp act
+}
+
+log_div() {
+       return 0
+}
+
+. "$TEST_DIRECTORY"/t556x_common
+
+expect_aliased() {
+       export REQUEST_METHOD="GET" &&
+       if test $1 = 0; then
+               run_backend "$2"
+       else
+               run_backend "$2" &&
+               echo "fatal: '$2': aliased" >exp.err &&
+               test_cmp exp.err act.err
+       fi
+       unset REQUEST_METHOD
+}
+
+test_expect_success 'http-backend blocks bad PATH_INFO' '
+       config http.getanyfile true &&
+
+       expect_aliased 0 /repo.git/HEAD &&
+
+       expect_aliased 1 /repo.git/../HEAD &&
+       expect_aliased 1 /../etc/passwd &&
+       expect_aliased 1 ../etc/passwd &&
+       expect_aliased 1 /etc//passwd &&
+       expect_aliased 1 /etc/./passwd &&
+       expect_aliased 1 //domain/data.txt
+'
+
+test_done
diff --git a/t/t5560-http-backend.sh b/t/t5560-http-backend.sh
deleted file mode 100755 (executable)
index ed034bc..0000000
+++ /dev/null
@@ -1,260 +0,0 @@
-#!/bin/sh
-
-test_description='test git-http-backend'
-. ./test-lib.sh
-
-if test -n "$NO_CURL"; then
-       say 'skipping test, git built without http support'
-       test_done
-fi
-
-LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5560'}
-. "$TEST_DIRECTORY"/lib-httpd.sh
-start_httpd
-
-find_file() {
-       cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
-       find $1 -type f |
-       sed -e 1q
-}
-
-config() {
-       git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" config $1 $2
-}
-
-GET() {
-       curl --include "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null &&
-       tr '\015' Q <out |
-       sed '
-               s/Q$//
-               1q
-       ' >act &&
-       echo "HTTP/1.1 $2" >exp &&
-       test_cmp exp act
-}
-
-POST() {
-       curl --include --data "$2" \
-       --header "Content-Type: application/x-$1-request" \
-       "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null &&
-       tr '\015' Q <out |
-       sed '
-               s/Q$//
-               1q
-       ' >act &&
-       echo "HTTP/1.1 $3" >exp &&
-       test_cmp exp act
-}
-
-log_div() {
-       echo >>"$HTTPD_ROOT_PATH"/access.log
-       echo "###  $1" >>"$HTTPD_ROOT_PATH"/access.log
-       echo "###" >>"$HTTPD_ROOT_PATH"/access.log
-}
-
-test_expect_success 'setup repository' '
-       echo content >file &&
-       git add file &&
-       git commit -m one &&
-
-       mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
-       (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
-        git --bare init &&
-        : >objects/info/alternates &&
-        : >objects/info/http-alternates
-       ) &&
-       git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
-       git push public master:master &&
-
-       (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
-        git repack -a -d
-       ) &&
-
-       echo other >file &&
-       git add file &&
-       git commit -m two &&
-       git push public master:master &&
-
-       LOOSE_URL=$(find_file objects/??) &&
-       PACK_URL=$(find_file objects/pack/*.pack) &&
-       IDX_URL=$(find_file objects/pack/*.idx)
-'
-
-get_static_files() {
-       GET HEAD "$1" &&
-       GET info/refs "$1" &&
-       GET objects/info/packs "$1" &&
-       GET objects/info/alternates "$1" &&
-       GET objects/info/http-alternates "$1" &&
-       GET $LOOSE_URL "$1" &&
-       GET $PACK_URL "$1" &&
-       GET $IDX_URL "$1"
-}
-
-test_expect_success 'direct refs/heads/master not found' '
-       log_div "refs/heads/master"
-       GET refs/heads/master "404 Not Found"
-'
-test_expect_success 'static file is ok' '
-       log_div "getanyfile default"
-       get_static_files "200 OK"
-'
-test_expect_success 'static file if http.getanyfile true is ok' '
-       log_div "getanyfile true"
-       config http.getanyfile true &&
-       get_static_files "200 OK"
-'
-test_expect_success 'static file if http.getanyfile false fails' '
-       log_div "getanyfile false"
-       config http.getanyfile false &&
-       get_static_files "403 Forbidden"
-'
-
-test_expect_success 'http.uploadpack default enabled' '
-       log_div "uploadpack default"
-       GET info/refs?service=git-upload-pack "200 OK"  &&
-       POST git-upload-pack 0000 "200 OK"
-'
-test_expect_success 'http.uploadpack true' '
-       log_div "uploadpack true"
-       config http.uploadpack true &&
-       GET info/refs?service=git-upload-pack "200 OK" &&
-       POST git-upload-pack 0000 "200 OK"
-'
-test_expect_success 'http.uploadpack false' '
-       log_div "uploadpack false"
-       config http.uploadpack false &&
-       GET info/refs?service=git-upload-pack "403 Forbidden" &&
-       POST git-upload-pack 0000 "403 Forbidden"
-'
-
-test_expect_success 'http.receivepack default disabled' '
-       log_div "receivepack default"
-       GET info/refs?service=git-receive-pack "403 Forbidden"  &&
-       POST git-receive-pack 0000 "403 Forbidden"
-'
-test_expect_success 'http.receivepack true' '
-       log_div "receivepack true"
-       config http.receivepack true &&
-       GET info/refs?service=git-receive-pack "200 OK" &&
-       POST git-receive-pack 0000 "200 OK"
-'
-test_expect_success 'http.receivepack false' '
-       log_div "receivepack false"
-       config http.receivepack false &&
-       GET info/refs?service=git-receive-pack "403 Forbidden" &&
-       POST git-receive-pack 0000 "403 Forbidden"
-'
-
-run_backend() {
-       REQUEST_METHOD=GET \
-       GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \
-       PATH_INFO="$2" \
-       git http-backend >act.out 2>act.err
-}
-
-path_info() {
-       if test $1 = 0; then
-               run_backend "$2"
-       else
-               test_must_fail run_backend "$2" &&
-               echo "fatal: '$2': aliased" >exp.err &&
-               test_cmp exp.err act.err
-       fi
-}
-
-test_expect_success 'http-backend blocks bad PATH_INFO' '
-       config http.getanyfile true &&
-
-       run_backend 0 /repo.git/HEAD &&
-
-       run_backend 1 /repo.git/../HEAD &&
-       run_backend 1 /../etc/passwd &&
-       run_backend 1 ../etc/passwd &&
-       run_backend 1 /etc//passwd &&
-       run_backend 1 /etc/./passwd &&
-       run_backend 1 /etc/.../passwd &&
-       run_backend 1 //domain/data.txt
-'
-
-cat >exp <<EOF
-
-###  refs/heads/master
-###
-GET  /smart/repo.git/refs/heads/master HTTP/1.1 404 -
-
-###  getanyfile default
-###
-GET  /smart/repo.git/HEAD HTTP/1.1 200
-GET  /smart/repo.git/info/refs HTTP/1.1 200
-GET  /smart/repo.git/objects/info/packs HTTP/1.1 200
-GET  /smart/repo.git/objects/info/alternates HTTP/1.1 200 -
-GET  /smart/repo.git/objects/info/http-alternates HTTP/1.1 200 -
-GET  /smart/repo.git/$LOOSE_URL HTTP/1.1 200
-GET  /smart/repo.git/$PACK_URL HTTP/1.1 200
-GET  /smart/repo.git/$IDX_URL HTTP/1.1 200
-
-###  getanyfile true
-###
-GET  /smart/repo.git/HEAD HTTP/1.1 200
-GET  /smart/repo.git/info/refs HTTP/1.1 200
-GET  /smart/repo.git/objects/info/packs HTTP/1.1 200
-GET  /smart/repo.git/objects/info/alternates HTTP/1.1 200 -
-GET  /smart/repo.git/objects/info/http-alternates HTTP/1.1 200 -
-GET  /smart/repo.git/$LOOSE_URL HTTP/1.1 200
-GET  /smart/repo.git/$PACK_URL HTTP/1.1 200
-GET  /smart/repo.git/$IDX_URL HTTP/1.1 200
-
-###  getanyfile false
-###
-GET  /smart/repo.git/HEAD HTTP/1.1 403 -
-GET  /smart/repo.git/info/refs HTTP/1.1 403 -
-GET  /smart/repo.git/objects/info/packs HTTP/1.1 403 -
-GET  /smart/repo.git/objects/info/alternates HTTP/1.1 403 -
-GET  /smart/repo.git/objects/info/http-alternates HTTP/1.1 403 -
-GET  /smart/repo.git/$LOOSE_URL HTTP/1.1 403 -
-GET  /smart/repo.git/$PACK_URL HTTP/1.1 403 -
-GET  /smart/repo.git/$IDX_URL HTTP/1.1 403 -
-
-###  uploadpack default
-###
-GET  /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
-POST /smart/repo.git/git-upload-pack HTTP/1.1 200 -
-
-###  uploadpack true
-###
-GET  /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
-POST /smart/repo.git/git-upload-pack HTTP/1.1 200 -
-
-###  uploadpack false
-###
-GET  /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 403 -
-POST /smart/repo.git/git-upload-pack HTTP/1.1 403 -
-
-###  receivepack default
-###
-GET  /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 -
-POST /smart/repo.git/git-receive-pack HTTP/1.1 403 -
-
-###  receivepack true
-###
-GET  /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
-POST /smart/repo.git/git-receive-pack HTTP/1.1 200 -
-
-###  receivepack false
-###
-GET  /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 -
-POST /smart/repo.git/git-receive-pack HTTP/1.1 403 -
-EOF
-test_expect_success 'server request log matches test results' '
-       sed -e "
-               s/^.* \"//
-               s/\"//
-               s/ [1-9][0-9]*\$//
-               s/^GET /GET  /
-       " >act <"$HTTPD_ROOT_PATH"/access.log &&
-       test_cmp exp act
-'
-
-stop_httpd
-test_done
diff --git a/t/t5561-http-backend.sh b/t/t5561-http-backend.sh
new file mode 100755 (executable)
index 0000000..8c6d0b2
--- /dev/null
@@ -0,0 +1,149 @@
+#!/bin/sh
+
+test_description='test git-http-backend'
+. ./test-lib.sh
+
+if test -n "$NO_CURL"; then
+       say 'skipping test, git built without http support'
+       test_done
+fi
+
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5561'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+GET() {
+       curl --include "$HTTPD_URL/$SMART/repo.git/$1" >out 2>/dev/null &&
+       tr '\015' Q <out |
+       sed '
+               s/Q$//
+               1q
+       ' >act &&
+       echo "HTTP/1.1 $2" >exp &&
+       test_cmp exp act
+}
+
+POST() {
+       curl --include --data "$2" \
+       --header "Content-Type: application/x-$1-request" \
+       "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null &&
+       tr '\015' Q <out |
+       sed '
+               s/Q$//
+               1q
+       ' >act &&
+       echo "HTTP/1.1 $3" >exp &&
+       test_cmp exp act
+}
+
+log_div() {
+       echo >>"$HTTPD_ROOT_PATH"/access.log
+       echo "###  $1" >>"$HTTPD_ROOT_PATH"/access.log
+       echo "###" >>"$HTTPD_ROOT_PATH"/access.log
+}
+
+. "$TEST_DIRECTORY"/t556x_common
+
+cat >exp <<EOF
+
+###  refs/heads/master
+###
+GET  /smart/repo.git/refs/heads/master HTTP/1.1 404 -
+
+###  getanyfile default
+###
+GET  /smart/repo.git/HEAD HTTP/1.1 200
+GET  /smart/repo.git/info/refs HTTP/1.1 200
+GET  /smart/repo.git/objects/info/packs HTTP/1.1 200
+GET  /smart/repo.git/objects/info/alternates HTTP/1.1 200 -
+GET  /smart/repo.git/objects/info/http-alternates HTTP/1.1 200 -
+GET  /smart/repo.git/$LOOSE_URL HTTP/1.1 200
+GET  /smart/repo.git/$PACK_URL HTTP/1.1 200
+GET  /smart/repo.git/$IDX_URL HTTP/1.1 200
+
+###  no git-daemon-export-ok
+###
+GET  /smart_noexport/repo.git/HEAD HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/info/refs HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/objects/info/packs HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/objects/info/alternates HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/objects/info/http-alternates HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/$LOOSE_URL HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/$PACK_URL HTTP/1.1 404 -
+GET  /smart_noexport/repo.git/$IDX_URL HTTP/1.1 404 -
+
+###  git-daemon-export-ok
+###
+GET  /smart_noexport/repo.git/HEAD HTTP/1.1 200
+GET  /smart_noexport/repo.git/info/refs HTTP/1.1 200
+GET  /smart_noexport/repo.git/objects/info/packs HTTP/1.1 200
+GET  /smart_noexport/repo.git/objects/info/alternates HTTP/1.1 200 -
+GET  /smart_noexport/repo.git/objects/info/http-alternates HTTP/1.1 200 -
+GET  /smart_noexport/repo.git/$LOOSE_URL HTTP/1.1 200
+GET  /smart_noexport/repo.git/$PACK_URL HTTP/1.1 200
+GET  /smart_noexport/repo.git/$IDX_URL HTTP/1.1 200
+
+###  getanyfile true
+###
+GET  /smart/repo.git/HEAD HTTP/1.1 200
+GET  /smart/repo.git/info/refs HTTP/1.1 200
+GET  /smart/repo.git/objects/info/packs HTTP/1.1 200
+GET  /smart/repo.git/objects/info/alternates HTTP/1.1 200 -
+GET  /smart/repo.git/objects/info/http-alternates HTTP/1.1 200 -
+GET  /smart/repo.git/$LOOSE_URL HTTP/1.1 200
+GET  /smart/repo.git/$PACK_URL HTTP/1.1 200
+GET  /smart/repo.git/$IDX_URL HTTP/1.1 200
+
+###  getanyfile false
+###
+GET  /smart/repo.git/HEAD HTTP/1.1 403 -
+GET  /smart/repo.git/info/refs HTTP/1.1 403 -
+GET  /smart/repo.git/objects/info/packs HTTP/1.1 403 -
+GET  /smart/repo.git/objects/info/alternates HTTP/1.1 403 -
+GET  /smart/repo.git/objects/info/http-alternates HTTP/1.1 403 -
+GET  /smart/repo.git/$LOOSE_URL HTTP/1.1 403 -
+GET  /smart/repo.git/$PACK_URL HTTP/1.1 403 -
+GET  /smart/repo.git/$IDX_URL HTTP/1.1 403 -
+
+###  uploadpack default
+###
+GET  /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/repo.git/git-upload-pack HTTP/1.1 200 -
+
+###  uploadpack true
+###
+GET  /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200
+POST /smart/repo.git/git-upload-pack HTTP/1.1 200 -
+
+###  uploadpack false
+###
+GET  /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 403 -
+POST /smart/repo.git/git-upload-pack HTTP/1.1 403 -
+
+###  receivepack default
+###
+GET  /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 -
+POST /smart/repo.git/git-receive-pack HTTP/1.1 403 -
+
+###  receivepack true
+###
+GET  /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 200
+POST /smart/repo.git/git-receive-pack HTTP/1.1 200 -
+
+###  receivepack false
+###
+GET  /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 -
+POST /smart/repo.git/git-receive-pack HTTP/1.1 403 -
+EOF
+test_expect_success 'server request log matches test results' '
+       sed -e "
+               s/^.* \"//
+               s/\"//
+               s/ [1-9][0-9]*\$//
+               s/^GET /GET  /
+       " >act <"$HTTPD_ROOT_PATH"/access.log &&
+       test_cmp exp act
+'
+
+stop_httpd
+test_done
diff --git a/t/t556x_common b/t/t556x_common
new file mode 100755 (executable)
index 0000000..be024e5
--- /dev/null
@@ -0,0 +1,122 @@
+#!/bin/sh
+
+find_file() {
+       cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+       find $1 -type f |
+       sed -e 1q
+}
+
+config() {
+       git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" config $1 $2
+}
+
+test_expect_success 'setup repository' '
+       echo content >file &&
+       git add file &&
+       git commit -m one &&
+
+       mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+       (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+        git --bare init &&
+        : >objects/info/alternates &&
+        : >objects/info/http-alternates
+       ) &&
+       git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+       git push public master:master &&
+
+       (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+        git repack -a -d
+       ) &&
+
+       echo other >file &&
+       git add file &&
+       git commit -m two &&
+       git push public master:master &&
+
+       LOOSE_URL=$(find_file objects/??) &&
+       PACK_URL=$(find_file objects/pack/*.pack) &&
+       IDX_URL=$(find_file objects/pack/*.idx)
+'
+
+get_static_files() {
+       GET HEAD "$1" &&
+       GET info/refs "$1" &&
+       GET objects/info/packs "$1" &&
+       GET objects/info/alternates "$1" &&
+       GET objects/info/http-alternates "$1" &&
+       GET $LOOSE_URL "$1" &&
+       GET $PACK_URL "$1" &&
+       GET $IDX_URL "$1"
+}
+
+SMART=smart
+export GIT_HTTP_EXPORT_ALL=1
+test_expect_success 'direct refs/heads/master not found' '
+       log_div "refs/heads/master"
+       GET refs/heads/master "404 Not Found"
+'
+test_expect_success 'static file is ok' '
+       log_div "getanyfile default"
+       get_static_files "200 OK"
+'
+SMART=smart_noexport
+unset GIT_HTTP_EXPORT_ALL
+test_expect_success 'no export by default' '
+       log_div "no git-daemon-export-ok"
+       get_static_files "404 Not Found"
+'
+test_expect_success 'export if git-daemon-export-ok' '
+       log_div "git-daemon-export-ok"
+        (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+        touch git-daemon-export-ok
+       ) &&
+        get_static_files "200 OK"
+'
+SMART=smart
+export GIT_HTTP_EXPORT_ALL=1
+test_expect_success 'static file if http.getanyfile true is ok' '
+       log_div "getanyfile true"
+       config http.getanyfile true &&
+       get_static_files "200 OK"
+'
+test_expect_success 'static file if http.getanyfile false fails' '
+       log_div "getanyfile false"
+       config http.getanyfile false &&
+       get_static_files "403 Forbidden"
+'
+
+test_expect_success 'http.uploadpack default enabled' '
+       log_div "uploadpack default"
+       GET info/refs?service=git-upload-pack "200 OK"  &&
+       POST git-upload-pack 0000 "200 OK"
+'
+test_expect_success 'http.uploadpack true' '
+       log_div "uploadpack true"
+       config http.uploadpack true &&
+       GET info/refs?service=git-upload-pack "200 OK" &&
+       POST git-upload-pack 0000 "200 OK"
+'
+test_expect_success 'http.uploadpack false' '
+       log_div "uploadpack false"
+       config http.uploadpack false &&
+       GET info/refs?service=git-upload-pack "403 Forbidden" &&
+       POST git-upload-pack 0000 "403 Forbidden"
+'
+
+test_expect_success 'http.receivepack default disabled' '
+       log_div "receivepack default"
+       GET info/refs?service=git-receive-pack "403 Forbidden"  &&
+       POST git-receive-pack 0000 "403 Forbidden"
+'
+test_expect_success 'http.receivepack true' '
+       log_div "receivepack true"
+       config http.receivepack true &&
+       GET info/refs?service=git-receive-pack "200 OK" &&
+       POST git-receive-pack 0000 "200 OK"
+'
+test_expect_success 'http.receivepack false' '
+       log_div "receivepack false"
+       config http.receivepack false &&
+       GET info/refs?service=git-receive-pack "403 Forbidden" &&
+       POST git-receive-pack 0000 "403 Forbidden"
+'
index 19b5c0d552fa8b4665b5e396833264e258365b28..8b4c356cd21846025d84a434077bfdc8ee2bab57 100755 (executable)
@@ -119,7 +119,9 @@ test_expect_success 'bundle clone with nonexistent HEAD' '
 test_expect_success 'clone empty repository' '
        cd "$D" &&
        mkdir empty &&
-       (cd empty && git init) &&
+       (cd empty &&
+        git init &&
+        git config receive.denyCurrentBranch warn) &&
        git clone empty empty-clone &&
        test_tick &&
        (cd empty-clone
index 27825f5f31ca2310e08fd2555589aa878728b292..02cb02472322e8fd6c31548d30950c937e301e5f 100755 (executable)
@@ -27,7 +27,8 @@ test_expect_success 'redirected clone' '
 '
 test_expect_success 'redirected clone -v' '
 
-       git clone -v "file://$(pwd)/parent" clone-redirected-v >out 2>err &&
+       git clone --progress "file://$(pwd)/parent" clone-redirected-progress \
+               >out 2>err &&
        test -s err
 
 '
index 9f52154cac1bc8d35a279180767ab998ec612170..adfaae8c5b453835eeeac3e3794950971e6dd6d8 100755 (executable)
@@ -31,7 +31,7 @@ test_expect_success 'setup' '
         echo "data 5" &&
         echo ">2gb" &&
         cat commit) |
-       git fast-import &&
+       git fast-import --big-file-threshold=2 &&
        test ! -f exit-status
 
 '
index 571931588eda5799efbf2b0789abd10b8770a0a7..b0047d3c6b593795561ce908ab8e10ff574d3dbc 100755 (executable)
@@ -19,6 +19,13 @@ test_cmp expect.$1 output.$1
 "
 }
 
+test_format percent %%h <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+%h
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+%h
+EOF
+
 test_format hash %H%n%h <<'EOF'
 commit 131a310eb913d107dd3c09a65d1651175898735d
 131a310eb913d107dd3c09a65d1651175898735d
index 510bb9679f29c10645c902caea020651a797aa2f..af34a1e81711ca183680c9882353bd786b55e79f 100755 (executable)
@@ -8,9 +8,6 @@ note () {
        git tag "$1"
 }
 
-_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"
-
 unnote () {
        git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\)) |\1 |g"
 }
diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh
new file mode 100755 (executable)
index 0000000..8d3fa7d
--- /dev/null
@@ -0,0 +1,195 @@
+#!/bin/sh
+
+test_description='rev-list/rev-parse --glob'
+
+. ./test-lib.sh
+
+commit () {
+       test_tick &&
+       echo $1 > foo &&
+       git add foo &&
+       git commit -m "$1"
+}
+
+compare () {
+       # Split arguments on whitespace.
+       git $1 $2 >expected &&
+       git $1 $3 >actual &&
+       test_cmp expected actual
+}
+
+test_expect_success 'setup' '
+
+       commit master &&
+       git checkout -b subspace/one master &&
+       commit one &&
+       git checkout -b subspace/two master &&
+       commit two &&
+       git checkout -b subspace-x master &&
+       commit subspace-x &&
+       git checkout -b other/three master &&
+       commit three &&
+       git checkout -b someref master &&
+       commit some &&
+       git checkout master &&
+       commit master2 &&
+       git tag foo/bar master &&
+       git update-ref refs/remotes/foo/baz master
+'
+
+test_expect_success 'rev-parse --glob=refs/heads/subspace/*' '
+
+       compare rev-parse "subspace/one subspace/two" "--glob=refs/heads/subspace/*"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/subspace/*' '
+
+       compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace/*"
+
+'
+
+test_expect_success 'rev-parse --glob=refs/heads/subspace/' '
+
+       compare rev-parse "subspace/one subspace/two" "--glob=refs/heads/subspace/"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/subspace/' '
+
+       compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace/"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/subspace' '
+
+       compare rev-parse "subspace/one subspace/two" "--glob=heads/subspace"
+
+'
+
+test_expect_success 'rev-parse --branches=subspace/*' '
+
+       compare rev-parse "subspace/one subspace/two" "--branches=subspace/*"
+
+'
+
+test_expect_success 'rev-parse --branches=subspace/' '
+
+       compare rev-parse "subspace/one subspace/two" "--branches=subspace/"
+
+'
+
+test_expect_success 'rev-parse --branches=subspace' '
+
+       compare rev-parse "subspace/one subspace/two" "--branches=subspace"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/subspace/* --glob=heads/other/*' '
+
+       compare rev-parse "subspace/one subspace/two other/three" "--glob=heads/subspace/* --glob=heads/other/*"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/someref/* master' '
+
+       compare rev-parse "master" "--glob=heads/someref/* master"
+
+'
+
+test_expect_success 'rev-parse --glob=heads/*' '
+
+       compare rev-parse "master other/three someref subspace-x subspace/one subspace/two" "--glob=heads/*"
+
+'
+
+test_expect_success 'rev-parse --tags=foo' '
+
+       compare rev-parse "foo/bar" "--tags=foo"
+
+'
+
+test_expect_success 'rev-parse --remotes=foo' '
+
+       compare rev-parse "foo/baz" "--remotes=foo"
+
+'
+
+test_expect_success 'rev-list --glob=refs/heads/subspace/*' '
+
+       compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/*"
+
+'
+
+test_expect_success 'rev-list --glob=heads/subspace/*' '
+
+       compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/*"
+
+'
+
+test_expect_success 'rev-list --glob=refs/heads/subspace/' '
+
+       compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/"
+
+'
+
+test_expect_success 'rev-list --glob=heads/subspace/' '
+
+       compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/"
+
+'
+
+test_expect_success 'rev-list --glob=heads/subspace' '
+
+       compare rev-list "subspace/one subspace/two" "--glob=heads/subspace"
+
+'
+
+test_expect_success 'rev-list --branches=subspace/*' '
+
+       compare rev-list "subspace/one subspace/two" "--branches=subspace/*"
+
+'
+
+test_expect_success 'rev-list --branches=subspace/' '
+
+       compare rev-list "subspace/one subspace/two" "--branches=subspace/"
+
+'
+
+test_expect_success 'rev-list --branches=subspace' '
+
+       compare rev-list "subspace/one subspace/two" "--branches=subspace"
+
+'
+test_expect_success 'rev-list --glob=heads/someref/* master' '
+
+       compare rev-list "master" "--glob=heads/someref/* master"
+
+'
+
+test_expect_success 'rev-list --glob=heads/subspace/* --glob=heads/other/*' '
+
+       compare rev-list "subspace/one subspace/two other/three" "--glob=heads/subspace/* --glob=heads/other/*"
+
+'
+
+test_expect_success 'rev-list --glob=heads/*' '
+
+       compare rev-list "master other/three someref subspace-x subspace/one subspace/two" "--glob=heads/*"
+
+'
+
+test_expect_success 'rev-list --tags=foo' '
+
+       compare rev-list "foo/bar" "--tags=foo"
+
+'
+
+test_expect_success 'rev-list --remotes=foo' '
+
+       compare rev-list "foo/baz" "--remotes=foo"
+
+'
+
+test_done
index 7dcf39191476f272431e19e10ebb299d6aa55bb1..6291307cd03e4e374e640dc82ddc8f26dbb8ff1d 100755 (executable)
@@ -146,8 +146,8 @@ test_expect_success 'binary files cannot be merged' '
        grep "Cannot merge binary files" merge.err
 '
 
-sed -e "s/deerit.$/deerit;/" -e "s/me;$/me./" < new5.txt > new6.txt
-sed -e "s/deerit.$/deerit,/" -e "s/me;$/me,/" < new5.txt > new7.txt
+sed -e "s/deerit.\$/deerit;/" -e "s/me;\$/me./" < new5.txt > new6.txt
+sed -e "s/deerit.\$/deerit,/" -e "s/me;\$/me,/" < new5.txt > new7.txt
 
 test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' '
 
index 1ba0a252230a283a6bb2463d98537aab1eaf4fb8..5e439972bea358f700ded4266df31f7dc4514800 100755 (executable)
@@ -70,6 +70,18 @@ test_expect_success 'check merge result in working tree' '
 
 '
 
+test_expect_success 'retry the merge with longer context' '
+       echo text conflict-marker-size=32 >>.gitattributes &&
+       git checkout -m text &&
+       sed -ne "/^\([<=>]\)\1\1\1*/{
+               s/ .*$//
+               p
+       }" >actual text &&
+       grep ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" actual &&
+       grep "================================" actual &&
+       grep "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" actual
+'
+
 cat >./custom-merge <<\EOF
 #!/bin/sh
 
index 5bbfa44e8d9ee3eebb18eb07e93380c802741a18..3900d9f61f84a9eab907e87fca7bb9429ea36f04 100755 (executable)
@@ -52,6 +52,7 @@ test_expect_success 'initial merge' '
        git merge -s ours --no-commit gui/master &&
        git read-tree --prefix=git-gui/ -u gui/master &&
        git commit -m "Merge git-gui as our subdirectory" &&
+       git checkout -b work &&
        git ls-files -s >actual &&
        (
                echo "100644 $o1 0      git-gui/git-gui.sh"
@@ -65,9 +66,10 @@ test_expect_success 'merge update' '
        echo git-gui2 > git-gui.sh &&
        o3=$(git hash-object git-gui.sh) &&
        git add git-gui.sh &&
+       git checkout -b master2 &&
        git commit -m "update git-gui" &&
        cd ../git &&
-       git pull -s subtree gui master &&
+       git pull -s subtree gui master2 &&
        git ls-files -s >actual &&
        (
                echo "100644 $o3 0      git-gui/git-gui.sh"
@@ -76,4 +78,47 @@ test_expect_success 'merge update' '
        test_cmp expected actual
 '
 
+test_expect_success 'initial ambiguous subtree' '
+       cd ../git &&
+       git reset --hard master &&
+       git checkout -b master2 &&
+       git merge -s ours --no-commit gui/master &&
+       git read-tree --prefix=git-gui2/ -u gui/master &&
+       git commit -m "Merge git-gui2 as our subdirectory" &&
+       git checkout -b work2 &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      git-gui/git-gui.sh"
+               echo "100644 $o1 0      git-gui2/git-gui.sh"
+               echo "100644 $o2 0      git.c"
+       ) >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'merge using explicit' '
+       cd ../git &&
+       git reset --hard master2 &&
+       git pull -Xsubtree=git-gui gui master2 &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o3 0      git-gui/git-gui.sh"
+               echo "100644 $o1 0      git-gui2/git-gui.sh"
+               echo "100644 $o2 0      git.c"
+       ) >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'merge2 using explicit' '
+       cd ../git &&
+       git reset --hard master2 &&
+       git pull -Xsubtree=git-gui2 gui master2 &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      git-gui/git-gui.sh"
+               echo "100644 $o3 0      git-gui2/git-gui.sh"
+               echo "100644 $o2 0      git.c"
+       ) >expected &&
+       test_cmp expected actual
+'
+
 test_done
index def397c53a76dead449710eaca6333c2e1fb36aa..c51865fdbc0a6fd98cca4a4accd35b302e5fd739 100755 (executable)
@@ -423,7 +423,7 @@ test_expect_success 'skipped merge base when good and bad are siblings' '
        grep "merge base must be tested" my_bisect_log.txt &&
        grep $HASH4 my_bisect_log.txt &&
        git bisect skip > my_bisect_log.txt 2>&1 &&
-       grep "Warning" my_bisect_log.txt &&
+       grep "warning" my_bisect_log.txt &&
        grep $SIDE_HASH6 my_bisect_log.txt &&
        git bisect reset
 '
index 75d9602de4d4238b4182956127540525f711d33f..e8d65eefb521cc9f2e8211918734d069381772d4 100755 (executable)
@@ -1,13 +1,5 @@
 #!/bin/sh
 
-append_cr () {
-       sed -e 's/$/Q/' | tr Q '\015'
-}
-
-remove_cr () {
-       tr '\015' Q | sed -e 's/Q$//'
-}
-
 test_description='merge conflict in crlf repo
 
                b---M
index 5b96fb0b37b8397342796fd3b9fec94c91063ba4..3202e1de6d01bf3c36e82351c7f8426cc2bb44ab 100755 (executable)
@@ -74,7 +74,7 @@ test_expect_success 'setup a merge where dir a/b-2 changed to symlink' '
        git tag test2
 '
 
-test_expect_failure 'merge should not have conflicts (resolve)' '
+test_expect_success 'merge should not have conflicts (resolve)' '
        git reset --hard &&
        git checkout baseline^0 &&
        git merge -s resolve test2 &&
diff --git a/t/t6037-merge-ours-theirs.sh b/t/t6037-merge-ours-theirs.sh
new file mode 100755 (executable)
index 0000000..8ab3d61
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+test_description='Merge-recursive ours and theirs variants'
+. ./test-lib.sh
+
+test_expect_success setup '
+       for i in 1 2 3 4 5 6 7 8 9
+       do
+               echo "$i"
+       done >file &&
+       git add file &&
+       cp file elif &&
+       git commit -m initial &&
+
+       sed -e "s/1/one/" -e "s/9/nine/" >file <elif &&
+       git commit -a -m ours &&
+
+       git checkout -b side HEAD^ &&
+
+       sed -e "s/9/nueve/" >file <elif &&
+       git commit -a -m theirs &&
+
+       git checkout master^0
+'
+
+test_expect_success 'plain recursive - should conflict' '
+       git reset --hard master &&
+       test_must_fail git merge -s recursive side &&
+       grep nine file &&
+       grep nueve file &&
+       ! grep 9 file &&
+       grep one file &&
+       ! grep 1 file
+'
+
+test_expect_success 'recursive favouring theirs' '
+       git reset --hard master &&
+       git merge -s recursive -Xtheirs side &&
+       ! grep nine file &&
+       grep nueve file &&
+       ! grep 9 file &&
+       grep one file &&
+       ! grep 1 file
+'
+
+test_expect_success 'recursive favouring ours' '
+       git reset --hard master &&
+       git merge -s recursive -X ours side &&
+       grep nine file &&
+       ! grep nueve file &&
+       ! grep 9 file &&
+       grep one file &&
+       ! grep 1 file
+'
+
+test_expect_success 'pull with -X' '
+       git reset --hard master && git pull -s recursive -Xours . side &&
+       git reset --hard master && git pull -s recursive -X ours . side &&
+       git reset --hard master && git pull -s recursive -Xtheirs . side &&
+       git reset --hard master && git pull -s recursive -X theirs . side &&
+       git reset --hard master && ! git pull -s recursive -X bork . side
+'
+
+test_done
index 00e1de9627e8e20f4d28e3122502a494d21899e5..1785e178a4cb8fddd58d1b1db8062cf12825155e 100755 (executable)
@@ -69,7 +69,7 @@ test_expect_success 'status' '
                cd test &&
                git checkout b1 >/dev/null &&
                # reports nothing to commit
-               test_must_fail git status
+               test_must_fail git commit --dry-run
        ) >actual &&
        grep "have 1 and 1 different" actual
 '
@@ -89,4 +89,25 @@ test_expect_success 'status when tracking annotated tags' '
        grep "set up to track" actual &&
        git checkout heavytrack
 '
+
+test_expect_success 'setup tracking with branch --set-upstream on existing branch' '
+       git branch from-master master &&
+       test_must_fail git config branch.from-master.merge > actual &&
+       git branch --set-upstream from-master master &&
+       git config branch.from-master.merge > actual &&
+       grep -q "^refs/heads/master$" actual
+'
+
+test_expect_success '--set-upstream does not change branch' '
+       git branch from-master2 master &&
+       test_must_fail git config branch.from-master2.merge > actual &&
+       git rev-list from-master2 &&
+       git update-ref refs/heads/from-master2 from-master2^ &&
+       git rev-parse from-master2 >expect2 &&
+       git branch --set-upstream from-master2 master &&
+       git config branch.from-master.merge > actual &&
+       git rev-parse from-master2 >actual2 &&
+       grep -q "^refs/heads/master$" actual &&
+       cmp expect2 actual2
+'
 test_done
index 10b8f8c44befdb4eb00b3959f8b29cbebb7a22e1..65a35d94a001b555ce9e4d6c528d588339a5300b 100755 (executable)
@@ -189,6 +189,18 @@ test_expect_success 'absolute pathname outside should fail' '(
 
 )'
 
+test_expect_success 'git mv to move multiple sources into a directory' '
+       rm -fr .git && git init &&
+       mkdir dir other &&
+       >dir/a.txt &&
+       >dir/b.txt &&
+       git add dir/?.txt &&
+       git mv dir/a.txt dir/b.txt other &&
+       git ls-files >actual &&
+       { echo other/a.txt; echo other/b.txt; } >expect &&
+       test_cmp expect actual
+'
+
 test_expect_success 'git mv should not change sha1 of moved cache entry' '
 
        rm -fr .git &&
index abd14bf819f5c60fc1b9dc758c04974bd24b10a4..7144f815c07a46e4f39a0f739fb964467fd27a16 100755 (executable)
@@ -291,6 +291,14 @@ y:y yy
 z:zzz
 EOF
 
+test_expect_success 'grep -q, silently report matches' '
+       >empty &&
+       git grep -q mmap >actual &&
+       test_cmp empty actual &&
+       test_must_fail git grep -q qfwfq >actual &&
+       test_cmp empty actual
+'
+
 # Create 1024 file names that sort between "y" and "z" to make sure
 # the two files are handled by different calls to an external grep.
 # This depends on MAXARGS in builtin-grep.c being 1024 or less.
@@ -302,8 +310,8 @@ test_expect_success 'grep -C1, hunk mark between files' '
        test_cmp expected actual
 '
 
-test_expect_success 'grep -C1 --no-ext-grep, hunk mark between files' '
-       git grep -C1 --no-ext-grep "^[yz]" >actual &&
+test_expect_success 'grep -C1 hunk mark between files' '
+       git grep -C1 "^[yz]" >actual &&
        test_cmp expected actual
 '
 
@@ -359,7 +367,7 @@ test_expect_success 'log grep (6)' '
 test_expect_success 'grep with CE_VALID file' '
        git update-index --assume-unchanged t/t &&
        rm t/t &&
-       test "$(git grep --no-ext-grep test)" = "t/t:test" &&
+       test "$(git grep test)" = "t/t:test" &&
        git update-index --no-assume-unchanged t/t &&
        git checkout t/t
 '
index 9503875e97e09ad94283e63ae298551453e5787a..0da13a8d6b8f75ba041a353dfc6a356c1e8bbed0 100755 (executable)
@@ -306,4 +306,43 @@ test_expect_success '--remap-to-ancestor with filename filters' '
        test $orig_invariant = $(git rev-parse invariant)
 '
 
+test_expect_success 'setup submodule' '
+       rm -fr ?* .git &&
+       git init &&
+       test_commit file &&
+       mkdir submod &&
+       submodurl="$PWD/submod" &&
+       ( cd submod &&
+         git init &&
+         test_commit file-in-submod ) &&
+       git submodule add "$submodurl" &&
+       git commit -m "added submodule" &&
+       test_commit add-file &&
+       ( cd submod && test_commit add-in-submodule ) &&
+       git add submod &&
+       git commit -m "changed submodule" &&
+       git branch original HEAD
+'
+
+orig_head=`git show-ref --hash --head HEAD`
+
+test_expect_success 'rewrite submodule with another content' '
+       git filter-branch --tree-filter "test -d submod && {
+                                        rm -rf submod &&
+                                        git rm -rf --quiet submod &&
+                                        mkdir submod &&
+                                        : > submod/file
+                                        } || :" HEAD &&
+       test $orig_head != `git show-ref --hash --head HEAD`
+'
+
+test_expect_success 'replace submodule revision' '
+       git reset --hard original &&
+       git filter-branch -f --tree-filter \
+           "if git ls-files --error-unmatch -- submod > /dev/null 2>&1
+            then git update-index --cacheinfo 160000 0123456789012345678901234567890123456789 submod
+            fi" HEAD &&
+       test $orig_head != `git show-ref --hash --head HEAD`
+'
+
 test_done
diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh
new file mode 100755 (executable)
index 0000000..bb4066f
--- /dev/null
@@ -0,0 +1,163 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
+#
+
+test_description='skip-worktree bit test'
+
+. ./test-lib.sh
+
+cat >expect.full <<EOF
+H 1
+H 2
+H init.t
+H sub/1
+H sub/2
+EOF
+
+cat >expect.skip <<EOF
+S 1
+H 2
+H init.t
+S sub/1
+H sub/2
+EOF
+
+NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+ZERO_SHA0=0000000000000000000000000000000000000000
+setup_absent() {
+       test -f 1 && rm 1
+       git update-index --remove 1 &&
+       git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+       git update-index --skip-worktree 1
+}
+
+test_absent() {
+       echo "100644 $NULL_SHA1 0       1" > expected &&
+       git ls-files --stage 1 > result &&
+       test_cmp expected result &&
+       test ! -f 1
+}
+
+setup_dirty() {
+       git update-index --force-remove 1 &&
+       echo dirty > 1 &&
+       git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+       git update-index --skip-worktree 1
+}
+
+test_dirty() {
+       echo "100644 $NULL_SHA1 0       1" > expected &&
+       git ls-files --stage 1 > result &&
+       test_cmp expected result &&
+       echo dirty > expected
+       test_cmp expected 1
+}
+
+test_expect_success 'setup' '
+       test_commit init &&
+       mkdir sub &&
+       touch ./1 ./2 sub/1 sub/2 &&
+       git add 1 2 sub/1 sub/2 &&
+       git update-index --skip-worktree 1 sub/1 &&
+       git ls-files -t > result &&
+       test_cmp expect.skip result
+'
+
+test_expect_success 'update-index' '
+       setup_absent &&
+       git update-index 1 &&
+       test_absent
+'
+
+test_expect_success 'update-index' '
+       setup_dirty &&
+       git update-index 1 &&
+       test_dirty
+'
+
+test_expect_success 'update-index --remove' '
+       setup_absent &&
+       git update-index --remove 1 &&
+       test -z "$(git ls-files 1)" &&
+       test ! -f 1
+'
+
+test_expect_success 'update-index --remove' '
+       setup_dirty &&
+       git update-index --remove 1 &&
+       test -z "$(git ls-files 1)" &&
+       echo dirty > expected &&
+       test_cmp expected 1
+'
+
+test_expect_success 'ls-files --delete' '
+       setup_absent &&
+       test -z "$(git ls-files -d)"
+'
+
+test_expect_success 'ls-files --delete' '
+       setup_dirty &&
+       test -z "$(git ls-files -d)"
+'
+
+test_expect_success 'ls-files --modified' '
+       setup_absent &&
+       test -z "$(git ls-files -m)"
+'
+
+test_expect_success 'ls-files --modified' '
+       setup_dirty &&
+       test -z "$(git ls-files -m)"
+'
+
+test_expect_success 'grep with skip-worktree file' '
+       git update-index --no-skip-worktree 1 &&
+       echo test > 1 &&
+       git update-index 1 &&
+       git update-index --skip-worktree 1 &&
+       rm 1 &&
+       test "$(git grep --no-ext-grep test)" = "1:test"
+'
+
+echo ":000000 100644 $ZERO_SHA0 $NULL_SHA1 A   1" > expected
+test_expect_success 'diff-index does not examine skip-worktree absent entries' '
+       setup_absent &&
+       git diff-index HEAD -- 1 > result &&
+       test_cmp expected result
+'
+
+test_expect_success 'diff-index does not examine skip-worktree dirty entries' '
+       setup_dirty &&
+       git diff-index HEAD -- 1 > result &&
+       test_cmp expected result
+'
+
+test_expect_success 'diff-files does not examine skip-worktree absent entries' '
+       setup_absent &&
+       test -z "$(git diff-files -- one)"
+'
+
+test_expect_success 'diff-files does not examine skip-worktree dirty entries' '
+       setup_dirty &&
+       test -z "$(git diff-files -- one)"
+'
+
+test_expect_success 'git-rm succeeds on skip-worktree absent entries' '
+       setup_absent &&
+       git rm 1
+'
+
+test_expect_success 'commit on skip-worktree absent entries' '
+       git reset &&
+       setup_absent &&
+       test_must_fail git commit -m null 1
+'
+
+test_expect_success 'commit on skip-worktree dirty entries' '
+       git reset &&
+       setup_dirty &&
+       test_must_fail git commit -m null 1
+'
+
+test_done
diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh
new file mode 100755 (executable)
index 0000000..8d8b1c0
--- /dev/null
@@ -0,0 +1,146 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Nguyễn Thái Ngọc Duy
+#
+
+test_description='test worktree writing operations when skip-worktree is used'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit init &&
+       echo modified >> init.t &&
+       touch added &&
+       git add init.t added &&
+       git commit -m "modified and added" &&
+       git tag top
+'
+
+test_expect_success 'read-tree updates worktree, absent case' '
+       git checkout -f top &&
+       git update-index --skip-worktree init.t &&
+       rm init.t &&
+       git read-tree -m -u HEAD^ &&
+       echo init > expected &&
+       test_cmp expected init.t
+'
+
+test_expect_success 'read-tree updates worktree, dirty case' '
+       git checkout -f top &&
+       git update-index --skip-worktree init.t &&
+       echo dirty >> init.t &&
+       test_must_fail git read-tree -m -u HEAD^ &&
+       grep -q dirty init.t &&
+       test "$(git ls-files -t init.t)" = "S init.t" &&
+       git update-index --no-skip-worktree init.t
+'
+
+test_expect_success 'read-tree removes worktree, absent case' '
+       git checkout -f top &&
+       git update-index --skip-worktree added &&
+       rm added &&
+       git read-tree -m -u HEAD^ &&
+       test ! -f added
+'
+
+test_expect_success 'read-tree removes worktree, dirty case' '
+       git checkout -f top &&
+       git update-index --skip-worktree added &&
+       echo dirty >> added &&
+       test_must_fail git read-tree -m -u HEAD^ &&
+       grep -q dirty added &&
+       test "$(git ls-files -t added)" = "S added" &&
+       git update-index --no-skip-worktree added
+'
+
+NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+ZERO_SHA0=0000000000000000000000000000000000000000
+setup_absent() {
+       test -f 1 && rm 1
+       git update-index --remove 1 &&
+       git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+       git update-index --skip-worktree 1
+}
+
+test_absent() {
+       echo "100644 $NULL_SHA1 0       1" > expected &&
+       git ls-files --stage 1 > result &&
+       test_cmp expected result &&
+       test ! -f 1
+}
+
+setup_dirty() {
+       git update-index --force-remove 1 &&
+       echo dirty > 1 &&
+       git update-index --add --cacheinfo 100644 $NULL_SHA1 1 &&
+       git update-index --skip-worktree 1
+}
+
+test_dirty() {
+       echo "100644 $NULL_SHA1 0       1" > expected &&
+       git ls-files --stage 1 > result &&
+       test_cmp expected result &&
+       echo dirty > expected
+       test_cmp expected 1
+}
+
+cat >expected <<EOF
+S 1
+H 2
+H init.t
+S sub/1
+H sub/2
+EOF
+
+test_expect_success 'index setup' '
+       git checkout -f init &&
+       mkdir sub &&
+       touch ./1 ./2 sub/1 sub/2 &&
+       git add 1 2 sub/1 sub/2 &&
+       git update-index --skip-worktree 1 sub/1 &&
+       git ls-files -t > result &&
+       test_cmp expected result
+'
+
+test_expect_success 'git-add ignores worktree content' '
+       setup_absent &&
+       git add 1 &&
+       test_absent
+'
+
+test_expect_success 'git-add ignores worktree content' '
+       setup_dirty &&
+       git add 1 &&
+       test_dirty
+'
+
+test_expect_success 'git-rm fails if worktree is dirty' '
+       setup_dirty &&
+       test_must_fail git rm 1 &&
+       test_dirty
+'
+
+cat >expected <<EOF
+Would remove expected
+Would remove result
+EOF
+test_expect_success 'git-clean, absent case' '
+       setup_absent &&
+       git clean -n > result &&
+       test_cmp expected result
+'
+
+test_expect_success 'git-clean, dirty case' '
+       setup_dirty &&
+       git clean -n > result &&
+       test_cmp expected result
+'
+
+test_expect_failure 'git-apply adds file' false
+test_expect_failure 'git-apply updates file' false
+test_expect_failure 'git-apply removes file' false
+test_expect_failure 'git-mv to skip-worktree' false
+test_expect_failure 'git-mv from skip-worktree' false
+test_expect_failure 'git-checkout' false
+
+test_done
index 1044aa65494f6c908aa4d70c1590a5577b288f50..fcac4725982096af98414c5d40df2d4c14be9454 100755 (executable)
@@ -31,8 +31,7 @@ test_expect_success 'Report new path with conflict' '
 cat >expect <<EOF
 # On branch side
 # Unmerged paths:
-#   (use "git reset HEAD <file>..." to unstage)
-#   (use "git add <file>..." to mark resolution)
+#   (use "git add/rm <file>..." as appropriate to mark resolution)
 #
 #      deleted by us:      foo
 #
@@ -50,9 +49,11 @@ test_expect_success 'M/D conflict does not segfault' '
                git rm foo &&
                git commit -m delete &&
                test_must_fail git merge master &&
-               test_must_fail git status > ../actual
-       ) &&
-       test_cmp expect actual
+               test_must_fail git commit --dry-run >../actual &&
+               test_cmp ../expect ../actual &&
+               git status >../actual &&
+               test_cmp ../expect ../actual
+       )
 '
 
 test_done
diff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh
new file mode 100755 (executable)
index 0000000..8704d00
--- /dev/null
@@ -0,0 +1,183 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Christian Couder
+#
+
+test_description='Tests for "git reset --merge"'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+    for i in 1 2 3; do echo line $i; done >file1 &&
+    cat file1 >file2 &&
+    git add file1 file2 &&
+    test_tick &&
+    git commit -m "Initial commit" &&
+    git tag initial &&
+    echo line 4 >>file1 &&
+    cat file1 >file2 &&
+    test_tick &&
+    git commit -m "add line 4 to file1" file1 &&
+    git tag second
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     C       C     C    D     --merge  D       D     D
+# file2:     C       D     D    D     --merge  C       D     D
+test_expect_success 'reset --merge is ok with changes in file it does not touch' '
+    git reset --merge HEAD^ &&
+    ! grep 4 file1 &&
+    grep 4 file2 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test -z "$(git diff --cached)"
+'
+
+test_expect_success 'reset --merge is ok when switching back' '
+    git reset --merge second &&
+    grep 4 file1 &&
+    grep 4 file2 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test -z "$(git diff --cached)"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     B       B     C    D     --merge  D       D     D
+# file2:     C       D     D    D     --merge  C       D     D
+test_expect_success 'reset --merge discards changes added to index (1)' '
+    git reset --hard second &&
+    cat file1 >file2 &&
+    echo "line 5" >> file1 &&
+    git add file1 &&
+    git reset --merge HEAD^ &&
+    ! grep 4 file1 &&
+    ! grep 5 file1 &&
+    grep 4 file2 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test -z "$(git diff --cached)"
+'
+
+test_expect_success 'reset --merge is ok again when switching back (1)' '
+    git reset --hard initial &&
+    echo "line 5" >> file2 &&
+    git add file2 &&
+    git reset --merge second &&
+    ! grep 4 file2 &&
+    ! grep 5 file1 &&
+    grep 4 file1 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test -z "$(git diff --cached)"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     C       C     C    D     --merge  D       D     D
+# file2:     C       C     D    D     --merge  D       D     D
+test_expect_success 'reset --merge discards changes added to index (2)' '
+    git reset --hard second &&
+    echo "line 4" >> file2 &&
+    git add file2 &&
+    git reset --merge HEAD^ &&
+    ! grep 4 file2 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test -z "$(git diff)" &&
+    test -z "$(git diff --cached)"
+'
+
+test_expect_success 'reset --merge is ok again when switching back (2)' '
+    git reset --hard initial &&
+    git reset --merge second &&
+    ! grep 4 file2 &&
+    grep 4 file1 &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test -z "$(git diff --cached)"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     A       B     B    C     --merge  (disallowed)
+test_expect_success 'reset --merge fails with changes in file it touches' '
+    git reset --hard second &&
+    echo "line 5" >> file1 &&
+    test_tick &&
+    git commit -m "add line 5" file1 &&
+    sed -e "s/line 1/changed line 1/" <file1 >file3 &&
+    mv file3 file1 &&
+    test_must_fail git reset --merge HEAD^ 2>err.log &&
+    grep file1 err.log | grep "not uptodate"
+'
+
+test_expect_success 'setup 3 different branches' '
+    git reset --hard second &&
+    git branch branch1 &&
+    git branch branch2 &&
+    git branch branch3 &&
+    git checkout branch1 &&
+    echo "line 5 in branch1" >> file1 &&
+    test_tick &&
+    git commit -a -m "change in branch1" &&
+    git checkout branch2 &&
+    echo "line 5 in branch2" >> file1 &&
+    test_tick &&
+    git commit -a -m "change in branch2" &&
+    git tag third &&
+    git checkout branch3 &&
+    echo a new file >file3 &&
+    rm -f file1 &&
+    git add file3 &&
+    test_tick &&
+    git commit -a -m "change in branch3"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     X       U     B    C     --merge  C       C     C
+test_expect_success '"reset --merge HEAD^" is ok with pending merge' '
+    git checkout third &&
+    test_must_fail git merge branch1 &&
+    git reset --merge HEAD^ &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test -z "$(git diff --cached)" &&
+    test -z "$(git diff)"
+'
+
+# The next test will test the following:
+#
+#           working index HEAD target         working index HEAD
+#           ----------------------------------------------------
+# file1:     X       U     B    B     --merge  B       B     B
+test_expect_success '"reset --merge HEAD" is ok with pending merge' '
+    git reset --hard third &&
+    test_must_fail git merge branch1 &&
+    git reset --merge HEAD &&
+    test "$(git rev-parse HEAD)" = "$(git rev-parse third)" &&
+    test -z "$(git diff --cached)" &&
+    test -z "$(git diff)"
+'
+
+test_expect_success '--merge with added/deleted' '
+    git reset --hard third &&
+    rm -f file2 &&
+    test_must_fail git merge branch3 &&
+    ! test -f file2 &&
+    test -f file3 &&
+    git diff --exit-code file3 &&
+    git diff --exit-code branch3 file3 &&
+    git reset --merge HEAD &&
+    ! test -f file3 &&
+    ! test -f file2 &&
+    git diff --exit-code --cached
+'
+
+test_done
diff --git a/t/t7111-reset-table.sh b/t/t7111-reset-table.sh
new file mode 100755 (executable)
index 0000000..de896c9
--- /dev/null
@@ -0,0 +1,113 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Christian Couder
+#
+
+test_description='Tests to check that "reset" options follow a known table'
+
+. ./test-lib.sh
+
+
+test_expect_success 'creating initial commits' '
+    test_commit E file1 &&
+    test_commit D file1 &&
+    test_commit C file1
+'
+
+while read W1 I1 H1 T opt W2 I2 H2
+do
+    test_expect_success "check: $W1 $I1 $H1 $T --$opt $W2 $I2 $H2" '
+       git reset --hard C &&
+       if test "$I1" != "$H1"
+       then
+           echo "$I1" >file1 &&
+           git add file1
+       fi &&
+       if test "$W1" != "$I1"
+       then
+           echo "$W1" >file1
+       fi &&
+       if test "$W2" != "XXXXX"
+       then
+           git reset --$opt $T &&
+           test "$(cat file1)" = "$W2" &&
+           git checkout-index -f -- file1 &&
+           test "$(cat file1)" = "$I2" &&
+           git checkout -f HEAD -- file1 &&
+           test "$(cat file1)" = "$H2"
+       else
+           test_must_fail git reset --$opt $T
+       fi
+    '
+done <<\EOF
+A B C D soft   A B D
+A B C D mixed  A D D
+A B C D hard   D D D
+A B C D merge  XXXXX
+A B C C soft   A B C
+A B C C mixed  A C C
+A B C C hard   C C C
+A B C C merge  XXXXX
+B B C D soft   B B D
+B B C D mixed  B D D
+B B C D hard   D D D
+B B C D merge  D D D
+B B C C soft   B B C
+B B C C mixed  B C C
+B B C C hard   C C C
+B B C C merge  C C C
+B C C D soft   B C D
+B C C D mixed  B D D
+B C C D hard   D D D
+B C C D merge  XXXXX
+B C C C soft   B C C
+B C C C mixed  B C C
+B C C C hard   C C C
+B C C C merge  B C C
+EOF
+
+test_expect_success 'setting up branches to test with unmerged entries' '
+    git reset --hard C &&
+    git branch branch1 &&
+    git branch branch2 &&
+    git checkout branch1 &&
+    test_commit B1 file1 &&
+    git checkout branch2 &&
+    test_commit B file1
+'
+
+while read W1 I1 H1 T opt W2 I2 H2
+do
+    test_expect_success "check: $W1 $I1 $H1 $T --$opt $W2 $I2 $H2" '
+       git reset --hard B &&
+       test_must_fail git merge branch1 &&
+       cat file1 >X_file1 &&
+       if test "$W2" != "XXXXX"
+       then
+           git reset --$opt $T &&
+           if test "$W2" = "X"
+           then
+               test_cmp file1 X_file1
+           else
+               test "$(cat file1)" = "$W2"
+           fi &&
+           git checkout-index -f -- file1 &&
+           test "$(cat file1)" = "$I2" &&
+           git checkout -f HEAD -- file1 &&
+           test "$(cat file1)" = "$H2"
+       else
+           test_must_fail git reset --$opt $T
+       fi
+    '
+done <<\EOF
+X U B C soft   XXXXX
+X U B C mixed  X C C
+X U B C hard   C C C
+X U B C merge  C C C
+X U B B soft   XXXXX
+X U B B mixed  X B B
+X U B B hard   B B B
+X U B B merge  B B B
+EOF
+
+test_done
index ebfd34df36068f8808406a98d371731fb85012c4..6442f710be8bcaea11931044d52deb5b75d8f7e0 100755 (executable)
@@ -542,4 +542,61 @@ test_expect_success 'switch out of non-branch' '
        ! grep "^Previous HEAD" error.log
 '
 
+(
+ echo "#!$SHELL_PATH"
+ cat <<\EOF
+O=$1 A=$2 B=$3
+cat "$A" >.tmp
+exec >"$A"
+echo '<<<<<<< filfre-theirs'
+cat "$B"
+echo '||||||| filfre-common'
+cat "$O"
+echo '======='
+cat ".tmp"
+echo '>>>>>>> filfre-ours'
+rm -f .tmp
+exit 1
+EOF
+) >filfre.sh
+chmod +x filfre.sh
+
+test_expect_success 'custom merge driver with checkout -m' '
+       git reset --hard &&
+
+       git config merge.filfre.driver "./filfre.sh %O %A %B" &&
+       git config merge.filfre.name "Feel-free merge driver" &&
+       git config merge.filfre.recursive binary &&
+       echo "arm merge=filfre" >.gitattributes &&
+
+       git checkout -b left &&
+       echo neutral >arm &&
+       git add arm .gitattributes &&
+       test_tick &&
+       git commit -m neutral &&
+       git branch right &&
+
+       echo left >arm &&
+       test_tick &&
+       git commit -a -m left &&
+       git checkout right &&
+
+       echo right >arm &&
+       test_tick &&
+       git commit -a -m right &&
+
+       test_must_fail git merge left &&
+       (
+               for t in filfre-common left right
+               do
+                       grep $t arm || exit 1
+               done
+               exit 0
+       ) &&
+
+       mv arm expect &&
+       git checkout -m arm &&
+       test_cmp expect arm
+'
+
 test_done
index 118c6ebb182b5cd4700e533d6a951b31529149af..7d8ed68befed0e85ad85f9f933a2b887125e38e8 100755 (executable)
@@ -22,6 +22,25 @@ test_expect_success 'setup' '
 
 '
 
+test_expect_success 'git clean with skip-worktree .gitignore' '
+       git update-index --skip-worktree .gitignore &&
+       rm .gitignore &&
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       git clean &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test ! -f a.out &&
+       test ! -f src/part3.c &&
+       test -f docs/manual.txt &&
+       test -f obj.o &&
+       test -f build/lib.so &&
+       git update-index --no-skip-worktree .gitignore &&
+       git checkout .gitignore
+'
+
 test_expect_success 'git clean' '
 
        mkdir -p build docs &&
index a0cc99ab9f5b262851a1075193f5529b5582fd0a..1a4dc5f89353df7d7bda4bea539ee5bd7a3b9bae 100755 (executable)
@@ -299,6 +299,15 @@ test_expect_success 'ls-files gracefully handles trailing slash' '
 
 '
 
+test_expect_success 'moving to a commit without submodule does not leave empty dir' '
+       rm -rf init &&
+       mkdir init &&
+       git reset --hard &&
+       git checkout initial &&
+       test ! -d init &&
+       git checkout second
+'
+
 test_expect_success 'submodule <invalid-path> warns' '
 
        git submodule no-such-submodule 2> output.err &&
index 6cc16c39fe75ee9dab942fc5ae376827d3b7ea44..d3c039f724c3a1247417b5fd29d6bd50a88f54be 100755 (executable)
@@ -213,7 +213,7 @@ EOF
 test_expect_success '--for-status' "
     git submodule summary --for-status HEAD^ >actual &&
     test_cmp actual - <<EOF
-# Modified submodules:
+# Submodule changes to be committed:
 #
 # * sm1 $head6...0000000:
 #
index 8eec0fa9bc7278981b9a16571c588ede0a94d341..9f5c3edb0392c321092e56e2bf46fd096f74cf75 100755 (executable)
@@ -150,7 +150,7 @@ EOF
 test_expect_success '--signoff' '
        echo "yet another content *narf*" >> foo &&
        echo "zort" | git commit -s -F - foo &&
-       git cat-file commit HEAD | sed "1,/^$/d" > output &&
+       git cat-file commit HEAD | sed "1,/^\$/d" > output &&
        test_cmp expect output
 '
 
index a603f6d21a61df197256ea91bfb38c1f4e45c5e8..7940901d47fd457cda77ee333aa40145433be4d4 100755 (executable)
@@ -117,7 +117,11 @@ test_expect_success \
 test_expect_success \
        "overriding author from command line" \
        "echo 'gak' >file && \
-        git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a"
+        git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a >output 2>&1"
+
+test_expect_success \
+       "commit --author output mentions author" \
+       "grep Rubber.Duck output"
 
 test_expect_success PERL \
        "interactive add" \
@@ -211,6 +215,21 @@ test_expect_success 'amend commit to fix author' '
 
 '
 
+test_expect_success 'amend commit to fix date' '
+
+       test_tick &&
+       newtick=$GIT_AUTHOR_DATE &&
+       git reset --hard &&
+       git cat-file -p HEAD |
+       sed -e "s/author.*/author $author $newtick/" \
+               -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
+               expected &&
+       git commit --amend --date="$newtick" &&
+       git cat-file -p HEAD > current &&
+       test_cmp expected current
+
+'
+
 test_expect_success 'sign off (1)' '
 
        echo 1 >positive &&
index fe94552296bb98ef78dbc51e8b1f7d4a665cf0a4..844fb43c6db1ae4e9b8a3cda6156af359e9f639e 100755 (executable)
@@ -267,4 +267,113 @@ test_expect_success 'A single-liner subject with a token plus colon is not a foo
 
 '
 
+cat >.git/FAKE_EDITOR <<EOF
+#!$SHELL_PATH
+mv "\$1" "\$1.orig"
+(
+       echo message
+       cat "\$1.orig"
+) >"\$1"
+EOF
+
+echo '## Custom template' >template
+
+clear_config () {
+       (
+               git config --unset-all "$1"
+               case $? in
+               0|5)    exit 0 ;;
+               *)      exit 1 ;;
+               esac
+       )
+}
+
+try_commit () {
+       git reset --hard &&
+       echo >>negative &&
+       GIT_EDITOR=.git/FAKE_EDITOR git commit -a $* $use_template &&
+       case "$use_template" in
+       '')
+               ! grep "^## Custom template" .git/COMMIT_EDITMSG ;;
+       *)
+               grep "^## Custom template" .git/COMMIT_EDITMSG ;;
+       esac
+}
+
+try_commit_status_combo () {
+
+       test_expect_success 'commit' '
+               clear_config commit.status &&
+               try_commit "" &&
+               grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit' '
+               clear_config commit.status &&
+               try_commit "" &&
+               grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit --status' '
+               clear_config commit.status &&
+               try_commit --status &&
+               grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit --no-status' '
+               clear_config commit.status &&
+               try_commit --no-status
+               ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit with commit.status = yes' '
+               clear_config commit.status &&
+               git config commit.status yes &&
+               try_commit "" &&
+               grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit with commit.status = no' '
+               clear_config commit.status &&
+               git config commit.status no &&
+               try_commit "" &&
+               ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit --status with commit.status = yes' '
+               clear_config commit.status &&
+               git config commit.status yes &&
+               try_commit --status &&
+               grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit --no-status with commit.status = yes' '
+               clear_config commit.status &&
+               git config commit.status yes &&
+               try_commit --no-status &&
+               ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit --status with commit.status = no' '
+               clear_config commit.status &&
+               git config commit.status no &&
+               try_commit --status &&
+               grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+       test_expect_success 'commit --no-status with commit.status = no' '
+               clear_config commit.status &&
+               git config commit.status no &&
+               try_commit --no-status &&
+               ! grep "^# Changes to be committed:" .git/COMMIT_EDITMSG
+       '
+
+}
+
+try_commit_status_combo
+
+use_template="-t template"
+
+try_commit_status_combo
+
 test_done
index d9a08aac56f8edf002a126cba83172abf5015034..253c3343190e88349f6aca1109e7439e3cf2a06e 100755 (executable)
@@ -5,34 +5,87 @@ test_description='git status for submodule'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
-       test_create_repo sub
-       cd sub &&
-       : >bar &&
-       git add bar &&
-       git commit -m " Add bar" &&
-       cd .. &&
-       git add sub &&
+       test_create_repo sub &&
+       (
+               cd sub &&
+               : >bar &&
+               git add bar &&
+               git commit -m " Add bar" &&
+               : >foo &&
+               git add foo &&
+               git commit -m " Add foo"
+       ) &&
+       echo output > .gitignore &&
+       git add sub .gitignore &&
        git commit -m "Add submodule sub"
 '
 
 test_expect_success 'status clean' '
-       git status |
-       grep "nothing to commit"
+       git status >output &&
+       grep "nothing to commit" output
 '
-test_expect_success 'status -a clean' '
-       git status -a |
-       grep "nothing to commit"
+
+test_expect_success 'commit --dry-run -a clean' '
+       test_must_fail git commit --dry-run -a >output &&
+       grep "nothing to commit" output
+'
+
+test_expect_success 'status with modified file in submodule' '
+       (cd sub && git reset --hard) &&
+       echo "changed" >sub/foo &&
+       git status >output &&
+       grep "modified:   sub" output
+'
+
+test_expect_success 'status with modified file in submodule (porcelain)' '
+       (cd sub && git reset --hard) &&
+       echo "changed" >sub/foo &&
+       git status --porcelain >output &&
+       diff output - <<-\EOF
+        M sub
+       EOF
+'
+
+test_expect_success 'status with added file in submodule' '
+       (cd sub && git reset --hard && echo >foo && git add foo) &&
+       git status >output &&
+       grep "modified:   sub" output
+'
+
+test_expect_success 'status with added file in submodule (porcelain)' '
+       (cd sub && git reset --hard && echo >foo && git add foo) &&
+       git status --porcelain >output &&
+       diff output - <<-\EOF
+        M sub
+       EOF
+'
+
+test_expect_success 'status with untracked file in submodule' '
+       (cd sub && git reset --hard) &&
+       echo "content" >sub/new-file &&
+       git status >output &&
+       grep "modified:   sub" output
+'
+
+test_expect_success 'status with untracked file in submodule (porcelain)' '
+       git status --porcelain >output &&
+       diff output - <<-\EOF
+        M sub
+       EOF
 '
+
 test_expect_success 'rm submodule contents' '
        rm -rf sub/* sub/.git
 '
+
 test_expect_success 'status clean (empty submodule dir)' '
-       git status |
-       grep "nothing to commit"
+       git status >output &&
+       grep "nothing to commit" output
 '
+
 test_expect_success 'status -a clean (empty submodule dir)' '
-       git status -a |
-       grep "nothing to commit"
+       test_must_fail git commit --dry-run -a >output &&
+       grep "nothing to commit" output
 '
 
 test_done
index 93f875f50054225e0879843b6e29ac057ba6c020..556d0faa77e027c8a18e213088fa6bbc5d7e7af5 100755 (executable)
@@ -8,26 +8,26 @@ test_description='git status'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
-       : > tracked &&
-       : > modified &&
+       : >tracked &&
+       : >modified &&
        mkdir dir1 &&
-       : > dir1/tracked &&
-       : > dir1/modified &&
+       : >dir1/tracked &&
+       : >dir1/modified &&
        mkdir dir2 &&
-       : > dir1/tracked &&
-       : > dir1/modified &&
+       : >dir1/tracked &&
+       : >dir1/modified &&
        git add . &&
 
        git status >output &&
 
        test_tick &&
        git commit -m initial &&
-       : > untracked &&
-       : > dir1/untracked &&
-       : > dir2/untracked &&
-       echo 1 > dir1/modified &&
-       echo 2 > dir2/modified &&
-       echo 3 > dir2/added &&
+       : >untracked &&
+       : >dir1/untracked &&
+       : >dir2/untracked &&
+       echo 1 >dir1/modified &&
+       echo 2 >dir2/modified &&
+       echo 3 >dir2/added &&
        git add dir2/added
 '
 
@@ -37,7 +37,7 @@ test_expect_success 'status (1)' '
 
 '
 
-cat > expect << \EOF
+cat >expect <<\EOF
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
@@ -63,7 +63,25 @@ EOF
 
 test_expect_success 'status (2)' '
 
-       git status > output &&
+       git status >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status -s (2)' '
+
+       git status -s >output &&
        test_cmp expect output
 
 '
@@ -85,8 +103,8 @@ cat >expect <<EOF
 EOF
 test_expect_success 'status -uno' '
        mkdir dir3 &&
-       : > dir3/untracked1 &&
-       : > dir3/untracked2 &&
+       : >dir3/untracked1 &&
+       : >dir3/untracked2 &&
        git status -uno >output &&
        test_cmp expect output
 '
@@ -97,6 +115,22 @@ test_expect_success 'status (status.showUntrackedFiles no)' '
        test_cmp expect output
 '
 
+cat >expect << EOF
+ M dir1/modified
+A  dir2/added
+EOF
+test_expect_success 'status -s -uno' '
+       git config --unset status.showuntrackedfiles
+       git status -s -uno >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'status -s (status.showUntrackedFiles no)' '
+       git config status.showuntrackedfiles no
+       git status -s >output &&
+       test_cmp expect output
+'
+
 cat >expect <<EOF
 # On branch master
 # Changes to be committed:
@@ -132,6 +166,29 @@ test_expect_success 'status (status.showUntrackedFiles normal)' '
        test_cmp expect output
 '
 
+cat >expect <<EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? dir3/
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s -unormal' '
+       git config --unset status.showuntrackedfiles
+       git status -s -unormal >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'status -s (status.showUntrackedFiles normal)' '
+       git config status.showuntrackedfiles normal
+       git status -s >output &&
+       test_cmp expect output
+'
+
 cat >expect <<EOF
 # On branch master
 # Changes to be committed:
@@ -169,7 +226,30 @@ test_expect_success 'status (status.showUntrackedFiles all)' '
        test_cmp expect output
 '
 
-cat > expect << \EOF
+cat >expect <<EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s -uall' '
+       git config --unset status.showuntrackedfiles
+       git status -s -uall >output &&
+       test_cmp expect output
+'
+test_expect_success 'status -s (status.showUntrackedFiles all)' '
+       git config status.showuntrackedfiles all
+       git status -s >output &&
+       rm -rf dir3 &&
+       git config --unset status.showuntrackedfiles &&
+       test_cmp expect output
+'
+
+cat >expect <<\EOF
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
@@ -195,12 +275,156 @@ EOF
 
 test_expect_success 'status with relative paths' '
 
-       (cd dir1 && git status) > output &&
+       (cd dir1 && git status) >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ M modified
+A  ../dir2/added
+?? untracked
+?? ../dir2/modified
+?? ../dir2/untracked
+?? ../expect
+?? ../output
+?? ../untracked
+EOF
+test_expect_success 'status -s with relative paths' '
+
+       (cd dir1 && git status -s) >output &&
        test_cmp expect output
 
 '
 
-cat > expect << \EOF
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status --porcelain ignores relative paths setting' '
+
+       (cd dir1 && git status --porcelain) >output &&
+       test_cmp expect output
+
+'
+
+test_expect_success 'setup unique colors' '
+
+       git config status.color.untracked blue
+
+'
+
+cat >expect <<\EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      <GREEN>new file:   dir2/added<RESET>
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      <RED>modified:   dir1/modified<RESET>
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      <BLUE>dir1/untracked<RESET>
+#      <BLUE>dir2/modified<RESET>
+#      <BLUE>dir2/untracked<RESET>
+#      <BLUE>expect<RESET>
+#      <BLUE>output<RESET>
+#      <BLUE>untracked<RESET>
+EOF
+
+test_expect_success 'status with color.ui' '
+
+       git config color.ui always &&
+       git status | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+test_expect_success 'status with color.status' '
+
+       git config --unset color.ui &&
+       git config color.status always &&
+       git status | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ <RED>M<RESET> dir1/modified
+<GREEN>A<RESET>  dir2/added
+<BLUE>??<RESET> dir1/untracked
+<BLUE>??<RESET> dir2/modified
+<BLUE>??<RESET> dir2/untracked
+<BLUE>??<RESET> expect
+<BLUE>??<RESET> output
+<BLUE>??<RESET> untracked
+EOF
+
+test_expect_success 'status -s with color.ui' '
+
+       git config --unset color.status &&
+       git config color.ui always &&
+       git status -s | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+test_expect_success 'status -s with color.status' '
+
+       git config --unset color.ui &&
+       git config color.status always &&
+       git status -s | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status --porcelain ignores color.ui' '
+
+       git config --unset color.status &&
+       git config color.ui always &&
+       git status --porcelain | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+test_expect_success 'status --porcelain ignores color.status' '
+
+       git config --unset color.ui &&
+       git config color.status always &&
+       git status --porcelain | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+# recover unconditionally from color tests
+git config --unset color.status
+git config --unset color.ui
+
+cat >expect <<\EOF
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
@@ -224,10 +448,29 @@ cat > expect << \EOF
 #      untracked
 EOF
 
+
 test_expect_success 'status without relative paths' '
 
        git config status.relativePaths false
-       (cd dir1 && git status) > output &&
+       (cd dir1 && git status) >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status -s without relative paths' '
+
+       (cd dir1 && git status -s) >output &&
        test_cmp expect output
 
 '
@@ -248,8 +491,8 @@ cat <<EOF >expect
 #      output
 #      untracked
 EOF
-test_expect_success 'status of partial commit excluding new file in index' '
-       git status dir1/modified >output &&
+test_expect_success 'dry-run of partial commit excluding new file in index' '
+       git commit --dry-run dir1/modified >output &&
        test_cmp expect output
 '
 
@@ -298,6 +541,28 @@ test_expect_success 'status --untracked-files=all does not show submodule' '
        test_cmp expect output
 '
 
+cat >expect <<EOF
+ M dir1/modified
+A  dir2/added
+A  sm
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s submodule summary is disabled by default' '
+       git status -s >output &&
+       test_cmp expect output
+'
+
+# we expect the same as the previous test
+test_expect_success 'status -s --untracked-files=all does not show submodule' '
+       git status -s --untracked-files=all >output &&
+       test_cmp expect output
+'
+
 head=$(cd sm && git rev-parse --short=7 --verify HEAD)
 
 cat >expect <<EOF
@@ -314,7 +579,7 @@ cat >expect <<EOF
 #
 #      modified:   dir1/modified
 #
-# Modified submodules:
+# Submodule changes to be committed:
 #
 # * sm 0000000...$head (1):
 #   > Add foo
@@ -335,6 +600,21 @@ test_expect_success 'status submodule summary' '
        test_cmp expect output
 '
 
+cat >expect <<EOF
+ M dir1/modified
+A  dir2/added
+A  sm
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s submodule summary' '
+       git status -s >output &&
+       test_cmp expect output
+'
 
 cat >expect <<EOF
 # On branch master
@@ -358,7 +638,23 @@ EOF
 test_expect_success 'status submodule summary (clean submodule)' '
        git commit -m "commit submodule" &&
        git config status.submodulesummary 10 &&
-       test_must_fail git status >output &&
+       test_must_fail git commit --dry-run >output &&
+       test_cmp expect output &&
+       git status >output &&
+       test_cmp expect output
+'
+
+cat >expect <<EOF
+ M dir1/modified
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s submodule summary (clean submodule)' '
+       git status -s >output &&
        test_cmp expect output
 '
 
@@ -376,7 +672,7 @@ cat >expect <<EOF
 #
 #      modified:   dir1/modified
 #
-# Modified submodules:
+# Submodule changes to be committed:
 #
 # * sm 0000000...$head (1):
 #   > Add foo
@@ -391,9 +687,9 @@ cat >expect <<EOF
 #      output
 #      untracked
 EOF
-test_expect_success 'status submodule summary (--amend)' '
+test_expect_success 'commit --dry-run submodule summary (--amend)' '
        git config status.submodulesummary 10 &&
-       git status --amend >output &&
+       git commit --dry-run --amend >output &&
        test_cmp expect output
 '
 
index fff6a6d0eab4b27d5bc7ff4636913cb10296ab7b..19c72f55bf60c746f72e640e24043b2d83e00fe3 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 #
-# Copyright (c) 2009 David Aguilar
+# Copyright (c) 2009, 2010 David Aguilar
 #
 
 test_description='git-difftool
@@ -15,14 +15,19 @@ if ! test_have_prereq PERL; then
        test_done
 fi
 
+LF='
+'
+
 remove_config_vars()
 {
        # Unset all config variables used by git-difftool
        git config --unset diff.tool
+       git config --unset diff.guitool
        git config --unset difftool.test-tool.cmd
        git config --unset difftool.prompt
        git config --unset merge.tool
        git config --unset mergetool.test-tool.cmd
+       git config --unset mergetool.prompt
        return 0
 }
 
@@ -31,11 +36,11 @@ restore_test_defaults()
        # Restores the test defaults used by several tests
        remove_config_vars
        unset GIT_DIFF_TOOL
-       unset GIT_MERGE_TOOL
        unset GIT_DIFFTOOL_PROMPT
        unset GIT_DIFFTOOL_NO_PROMPT
        git config diff.tool test-tool &&
        git config difftool.test-tool.cmd 'cat $LOCAL'
+       git config difftool.bogus-tool.cmd false
 }
 
 prompt_given()
@@ -71,11 +76,22 @@ test_expect_success 'custom commands' '
 
 # Ensures that git-difftool ignores bogus --tool values
 test_expect_success 'difftool ignores bad --tool values' '
-       diff=$(git difftool --no-prompt --tool=bogus-tool branch)
+       diff=$(git difftool --no-prompt --tool=bad-tool branch)
        test "$?" = 1 &&
        test "$diff" = ""
 '
 
+test_expect_success 'difftool honors --gui' '
+       git config merge.tool bogus-tool &&
+       git config diff.tool bogus-tool &&
+       git config diff.guitool test-tool &&
+
+       diff=$(git difftool --no-prompt --gui branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults
+'
+
 # Specify the diff tool using $GIT_DIFF_TOOL
 test_expect_success 'GIT_DIFF_TOOL variable' '
        git config --unset diff.tool
@@ -94,15 +110,7 @@ test_expect_success 'GIT_DIFF_TOOL overrides' '
        git config diff.tool bogus-tool &&
        git config merge.tool bogus-tool &&
 
-       GIT_MERGE_TOOL=test-tool &&
-       export GIT_MERGE_TOOL &&
-       diff=$(git difftool --no-prompt branch) &&
-       test "$diff" = "branch" &&
-       unset GIT_MERGE_TOOL &&
-
-       GIT_MERGE_TOOL=bogus-tool &&
        GIT_DIFF_TOOL=test-tool &&
-       export GIT_MERGE_TOOL &&
        export GIT_DIFF_TOOL &&
 
        diff=$(git difftool --no-prompt branch) &&
@@ -152,6 +160,17 @@ test_expect_success 'difftool.prompt config variable is false' '
        restore_test_defaults
 '
 
+# Test that we don't have to pass --no-prompt when mergetool.prompt is false
+test_expect_success 'difftool merge.prompt = false' '
+       git config --unset difftool.prompt
+       git config mergetool.prompt false &&
+
+       diff=$(git difftool branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults
+'
+
 # Test that the -y flag can override difftool.prompt = true
 test_expect_success 'difftool.prompt can overridden with -y' '
        git config difftool.prompt true &&
@@ -210,7 +229,39 @@ test_expect_success 'difftool.<tool>.path' '
        diff=$(git difftool --tool=tkdiff --no-prompt branch) &&
        git config --unset difftool.tkdiff.path &&
        lines=$(echo "$diff" | grep file | wc -l) &&
-       test "$lines" -eq 1
+       test "$lines" -eq 1 &&
+
+       restore_test_defaults
+'
+
+test_expect_success 'difftool --extcmd=cat' '
+       diff=$(git difftool --no-prompt --extcmd=cat branch) &&
+       test "$diff" = branch"$LF"master
+'
+
+test_expect_success 'difftool --extcmd cat' '
+       diff=$(git difftool --no-prompt --extcmd cat branch) &&
+       test "$diff" = branch"$LF"master
+'
+
+test_expect_success 'difftool -x cat' '
+       diff=$(git difftool --no-prompt -x cat branch) &&
+       test "$diff" = branch"$LF"master
+'
+
+test_expect_success 'difftool --extcmd echo arg1' '
+       diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"echo\ \$1\" branch)
+       test "$diff" = file
+'
+
+test_expect_success 'difftool --extcmd cat arg1' '
+       diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$1\" branch)
+       test "$diff" = master
+'
+
+test_expect_success 'difftool --extcmd cat arg2' '
+       diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$2\" branch)
+       test "$diff" = branch
 '
 
 test_done
index 752adaac850862caf50e3573338b9cb742781147..c09f37528811f9395d63e3f3acd1f598307983a2 100755 (executable)
@@ -186,8 +186,8 @@ test_expect_success 'Prompting works' '
                --smtp-server="$(pwd)/fake.sendmail" \
                $patches \
                2>errors &&
-               grep "^From: Example <from@example.com>$" msgtxt1 &&
-               grep "^To: to@example.com$" msgtxt1
+               grep "^From: Example <from@example.com>\$" msgtxt1 &&
+               grep "^To: to@example.com\$" msgtxt1
 '
 
 test_expect_success 'cccmd works' '
@@ -236,7 +236,7 @@ test_expect_success 'Author From: in message body' '
                --to=nobody@example.com \
                --smtp-server="$(pwd)/fake.sendmail" \
                $patches &&
-       sed "1,/^$/d" < msgtxt1 > msgbody1
+       sed "1,/^\$/d" < msgtxt1 > msgbody1
        grep "From: A <author@example.com>" msgbody1
 '
 
@@ -247,7 +247,7 @@ test_expect_success 'Author From: not in message body' '
                --to=nobody@example.com \
                --smtp-server="$(pwd)/fake.sendmail" \
                $patches &&
-       sed "1,/^$/d" < msgtxt1 > msgbody1
+       sed "1,/^\$/d" < msgtxt1 > msgbody1
        ! grep "From: A <author@example.com>" msgbody1
 '
 
index 359eeaa738e28c6a3eb2ced6c0f24bbd8992ec4f..3569c620964d40e1f2461e8e1a5ad22be7be0939 100755 (executable)
@@ -33,7 +33,7 @@ test_expect_success 'svn non-merge merge commits did not become git merge commit
        [ -z "$bad_non_merges" ]
        '
 
-test_expect_success 'everything got merged in the end' '
+test_expect_failure 'everything got merged in the end' '
        unmerged=$(git rev-list --all --not master)
        [ -z "$unmerged" ]
        '
index d917717cf3f177be7ed68306d078ac3362744bc8..3d73f140f86ceb59dc556f81c4af09db00e62cdc 100644 (file)
@@ -26,8 +26,9 @@ i=0
 cd foo
 mkdir trunk
 mkdir branches
-svn add trunk branches
-i=$(commit $i "Setup trunk and branches")
+mkdir tags
+svn add trunk branches tags
+i=$(commit $i "Setup trunk, branches, and tags")
 
 git cat-file blob 6683463e:Makefile > trunk/Makefile
 svn add trunk/Makefile 
@@ -155,6 +156,66 @@ svn merge ../branches/right --accept postpone
 i=$(commit $i "non-merge right to trunk 2")
 cd ..
 
+say "Adding subdirectory to LEFT"
+svn update
+cd branches/left
+mkdir subdir
+echo "Yeehaw" > subdir/cowboy
+svn add subdir
+i=$(commit $i "add subdirectory to left branch")
+cd ../../
+
+say "Merging LEFT to TRUNK"
+svn update
+cd trunk
+svn merge ../branches/left --accept postpone
+i=$(commit $i "merge left to trunk")
+cd ..
+
+say "Make PARTIAL branch"
+svn update
+i=$(commit $i "make partial branch")
+svn cp trunk/subdir branches/partial
+
+say "Make a commit to PARTIAL"
+svn update
+cd branches/partial
+echo "racecar" > palindromes
+svn add palindromes
+i=$(commit $i "partial update")
+cd ../../
+
+say "Merge PARTIAL to TRUNK"
+svn update
+cd trunk/subdir
+svn merge ../../branches/partial --accept postpone
+i=$(commit $i "merge partial to trunk")
+cd ../../
+
+say "Tagging trunk"
+svn update
+i=$(commit $i "tagging v1.0")
+svn cp trunk tags/v1.0
+
+say "Branching BUGFIX from v1.0"
+svn update
+i=$(commit $i "make bugfix branch from tag")
+svn cp tags/v1.0 branches/bugfix
+
+say "Make a commit to BUGFIX"
+svn update
+cd branches/bugfix/
+echo "kayak" >> subdir/palindromes
+i=$(commit $i "commit to bugfix")
+cd ../../
+
+say "Merge BUGFIX to TRUNK"
+svn update
+cd trunk
+svn merge ../branches/bugfix/ --accept postpone
+i=$(commit $i "Merge BUGFIX to TRUNK")
+cd ..
+
 cd ..
 svnadmin dump foo.svn > svn-mergeinfo.dump
 
index 9543e31c96ddd9a8d4a5f2a23d0fc9947d89bbbc..ebf386ebd59372004d0bcf8440e8f2feb02ad5e6 100644 (file)
@@ -1,6 +1,6 @@
 SVN-fs-dump-format-version: 2
 
-UUID: 64142547-0943-4db2-836a-d1e1eb2f9924
+UUID: d6191530-2693-4a8e-98e7-b194d4c3edd8
 
 Revision-number: 0
 Prop-content-length: 56
@@ -9,25 +9,25 @@ Content-length: 56
 K 8
 svn:date
 V 27
-2009-12-19T16:17:51.232640Z
+2010-01-19T04:14:02.832406Z
 PROPS-END
 
 Revision-number: 1
-Prop-content-length: 128
-Content-length: 128
+Prop-content-length: 134
+Content-length: 134
 
 K 7
 svn:log
-V 29
-(r1) Setup trunk and branches
+V 36
+(r1) Setup trunk, branches, and tags
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:51.831965Z
+2010-01-19T04:14:03.055172Z
 PROPS-END
 
 Node-path: branches
@@ -39,6 +39,15 @@ Content-length: 10
 PROPS-END
 
 
+Node-path: tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
 Node-path: trunk
 Node-kind: dir
 Node-action: add
@@ -49,8 +58,8 @@ PROPS-END
 
 
 Revision-number: 2
-Prop-content-length: 112
-Content-length: 112
+Prop-content-length: 111
+Content-length: 111
 
 K 7
 svn:log
@@ -58,12 +67,12 @@ V 13
 (r2) ancestor
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:52.300075Z
+2010-01-19T04:14:04.064506Z
 PROPS-END
 
 Node-path: trunk/Makefile
@@ -156,8 +165,8 @@ backup: clean
 
 
 Revision-number: 3
-Prop-content-length: 120
-Content-length: 120
+Prop-content-length: 119
+Content-length: 119
 
 K 7
 svn:log
@@ -165,12 +174,12 @@ V 21
 (r3) make left branch
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:52.768800Z
+2010-01-19T04:14:06.040389Z
 PROPS-END
 
 Node-path: branches/left
@@ -190,8 +199,8 @@ Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590
 
 
 Revision-number: 4
-Prop-content-length: 121
-Content-length: 121
+Prop-content-length: 120
+Content-length: 120
 
 K 7
 svn:log
@@ -199,12 +208,12 @@ V 22
 (r4) make right branch
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:53.177879Z
+2010-01-19T04:14:08.040905Z
 PROPS-END
 
 Node-path: branches/right
@@ -224,8 +233,8 @@ Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590
 
 
 Revision-number: 5
-Prop-content-length: 117
-Content-length: 117
+Prop-content-length: 116
+Content-length: 116
 
 K 7
 svn:log
@@ -233,12 +242,12 @@ V 18
 (r5) left update 1
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:53.604691Z
+2010-01-19T04:14:09.049169Z
 PROPS-END
 
 Node-path: branches/left/Makefile
@@ -329,8 +338,8 @@ backup: clean
 
 
 Revision-number: 6
-Prop-content-length: 118
-Content-length: 118
+Prop-content-length: 117
+Content-length: 117
 
 K 7
 svn:log
@@ -338,12 +347,12 @@ V 19
 (r6) right update 1
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:54.063555Z
+2010-01-19T04:14:10.049350Z
 PROPS-END
 
 Node-path: branches/right/Makefile
@@ -437,8 +446,8 @@ backup: clean
 
 
 Revision-number: 7
-Prop-content-length: 117
-Content-length: 117
+Prop-content-length: 116
+Content-length: 116
 
 K 7
 svn:log
@@ -446,12 +455,12 @@ V 18
 (r7) left update 2
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:54.523904Z
+2010-01-19T04:14:11.049209Z
 PROPS-END
 
 Node-path: branches/left/Makefile
@@ -542,8 +551,8 @@ backup: clean
 
 
 Revision-number: 8
-Prop-content-length: 117
-Content-length: 117
+Prop-content-length: 116
+Content-length: 116
 
 K 7
 svn:log
@@ -551,12 +560,12 @@ V 18
 (r8) left update 3
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:54.975970Z
+2010-01-19T04:14:12.049234Z
 PROPS-END
 
 Node-path: branches/left/Makefile
@@ -647,8 +656,8 @@ backup: clean
 
 
 Revision-number: 9
-Prop-content-length: 124
-Content-length: 124
+Prop-content-length: 123
+Content-length: 123
 
 K 7
 svn:log
@@ -656,12 +665,12 @@ V 25
 (r9) make left sub-branch
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:55.459904Z
+2010-01-19T04:14:14.040894Z
 PROPS-END
 
 Node-path: branches/left-sub
@@ -687,8 +696,8 @@ Text-copy-source-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10
 
 
 Revision-number: 10
-Prop-content-length: 129
-Content-length: 129
+Prop-content-length: 128
+Content-length: 128
 
 K 7
 svn:log
@@ -696,12 +705,12 @@ V 30
 (r10) left sub-branch update 1
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:55.862113Z
+2010-01-19T04:14:15.049935Z
 PROPS-END
 
 Node-path: branches/left-sub/README
@@ -718,8 +727,8 @@ crunch
 
 
 Revision-number: 11
-Prop-content-length: 126
-Content-length: 126
+Prop-content-length: 125
+Content-length: 125
 
 K 7
 svn:log
@@ -727,12 +736,12 @@ V 27
 (r11) Merge left to trunk 1
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:56.413416Z
+2010-01-19T04:14:18.056594Z
 PROPS-END
 
 Node-path: trunk
@@ -836,8 +845,8 @@ backup: clean
 
 
 Revision-number: 12
-Prop-content-length: 118
-Content-length: 118
+Prop-content-length: 117
+Content-length: 117
 
 K 7
 svn:log
@@ -845,12 +854,12 @@ V 19
 (r12) left update 4
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:56.831014Z
+2010-01-19T04:14:19.049620Z
 PROPS-END
 
 Node-path: branches/left/zlonk
@@ -867,8 +876,8 @@ touche
 
 
 Revision-number: 13
-Prop-content-length: 119
-Content-length: 119
+Prop-content-length: 118
+Content-length: 118
 
 K 7
 svn:log
@@ -876,12 +885,12 @@ V 20
 (r13) right update 2
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:57.341143Z
+2010-01-19T04:14:20.049659Z
 PROPS-END
 
 Node-path: branches/right/bang
@@ -898,8 +907,8 @@ thwacke
 
 
 Revision-number: 14
-Prop-content-length: 141
-Content-length: 141
+Prop-content-length: 140
+Content-length: 140
 
 K 7
 svn:log
@@ -907,12 +916,12 @@ V 42
 (r14) Cherry-pick right 2 commits to trunk
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:57.841851Z
+2010-01-19T04:14:23.041991Z
 PROPS-END
 
 Node-path: trunk
@@ -1029,8 +1038,8 @@ Text-copy-source-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062
 
 
 Revision-number: 15
-Prop-content-length: 127
-Content-length: 127
+Prop-content-length: 126
+Content-length: 126
 
 K 7
 svn:log
@@ -1038,12 +1047,12 @@ V 28
 (r15) Merge right to trunk 1
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:58.368520Z
+2010-01-19T04:14:26.054456Z
 PROPS-END
 
 Node-path: trunk
@@ -1061,8 +1070,8 @@ PROPS-END
 
 
 Revision-number: 16
-Prop-content-length: 119
-Content-length: 119
+Prop-content-length: 118
+Content-length: 118
 
 K 7
 svn:log
@@ -1070,12 +1079,12 @@ V 20
 (r16) right update 3
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:58.779056Z
+2010-01-19T04:14:27.049955Z
 PROPS-END
 
 Node-path: branches/right/urkkk
@@ -1092,8 +1101,8 @@ whamm
 
 
 Revision-number: 17
-Prop-content-length: 119
-Content-length: 119
+Prop-content-length: 118
+Content-length: 118
 
 K 7
 svn:log
@@ -1101,12 +1110,12 @@ V 20
 (r17) trunk update 1
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:59.221851Z
+2010-01-19T04:14:28.049615Z
 PROPS-END
 
 Node-path: trunk/vronk
@@ -1123,8 +1132,8 @@ pow
 
 
 Revision-number: 18
-Prop-content-length: 135
-Content-length: 135
+Prop-content-length: 134
+Content-length: 134
 
 K 7
 svn:log
@@ -1132,12 +1141,12 @@ V 36
 (r18) Merge right to left sub-branch
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:17:59.781666Z
+2010-01-19T04:14:31.061460Z
 PROPS-END
 
 Node-path: branches/left-sub
@@ -1262,8 +1271,8 @@ Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302
 
 
 Revision-number: 19
-Prop-content-length: 129
-Content-length: 129
+Prop-content-length: 128
+Content-length: 128
 
 K 7
 svn:log
@@ -1271,12 +1280,12 @@ V 30
 (r19) left sub-branch update 2
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:18:00.200531Z
+2010-01-19T04:14:32.049244Z
 PROPS-END
 
 Node-path: branches/left-sub/wham_eth
@@ -1293,8 +1302,8 @@ zowie
 
 
 Revision-number: 20
-Prop-content-length: 118
-Content-length: 118
+Prop-content-length: 117
+Content-length: 117
 
 K 7
 svn:log
@@ -1302,12 +1311,12 @@ V 19
 (r20) left update 5
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:18:00.659636Z
+2010-01-19T04:14:33.049332Z
 PROPS-END
 
 Node-path: branches/left/glurpp
@@ -1324,8 +1333,8 @@ eee_yow
 
 
 Revision-number: 21
-Prop-content-length: 147
-Content-length: 147
+Prop-content-length: 146
+Content-length: 146
 
 K 7
 svn:log
@@ -1333,12 +1342,12 @@ V 48
 (r21) Cherry-pick left sub-branch commit to left
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:18:01.194402Z
+2010-01-19T04:14:36.041839Z
 PROPS-END
 
 Node-path: branches/left
@@ -1364,8 +1373,8 @@ Text-copy-source-sha1: b165019b005c199237ba822c4404e771e93b654a
 
 
 Revision-number: 22
-Prop-content-length: 134
-Content-length: 134
+Prop-content-length: 133
+Content-length: 133
 
 K 7
 svn:log
@@ -1373,12 +1382,12 @@ V 35
 (r22) Merge left sub-branch to left
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:18:01.679218Z
+2010-01-19T04:14:39.045014Z
 PROPS-END
 
 Node-path: branches/left
@@ -1513,8 +1522,8 @@ Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302
 
 
 Revision-number: 23
-Prop-content-length: 126
-Content-length: 126
+Prop-content-length: 125
+Content-length: 125
 
 K 7
 svn:log
@@ -1522,12 +1531,12 @@ V 27
 (r23) Merge left to trunk 2
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:18:02.212349Z
+2010-01-19T04:14:42.052798Z
 PROPS-END
 
 Node-path: trunk
@@ -1591,8 +1600,8 @@ Text-copy-source-sha1: 9716527ebd70a75c27625cacbeb2d897c6e86178
 
 
 Revision-number: 24
-Prop-content-length: 131
-Content-length: 131
+Prop-content-length: 130
+Content-length: 130
 
 K 7
 svn:log
@@ -1600,12 +1609,12 @@ V 32
 (r24) non-merge right to trunk 2
 K 10
 svn:author
-V 4
-samv
+V 3
+adm
 K 8
 svn:date
 V 27
-2009-12-19T16:18:02.672148Z
+2010-01-19T04:14:44.038434Z
 PROPS-END
 
 Node-path: trunk
@@ -1623,3 +1632,291 @@ V 64
 PROPS-END
 
 
+Revision-number: 25
+Prop-content-length: 135
+Content-length: 135
+
+K 7
+svn:log
+V 37
+(r25) add subdirectory to left branch
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:46.052649Z
+PROPS-END
+
+Node-path: branches/left/subdir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/left/subdir/cowboy
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: f1d6530278ad409e68cc675476ad995f
+Text-content-sha1: 732d9e3e5c391ffd767a98b45ddcc848de778cea
+Content-length: 17
+
+PROPS-END
+Yeehaw
+
+
+Revision-number: 26
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 25
+(r26) merge left to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:49.040783Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 99
+Content-length: 99
+
+K 13
+svn:mergeinfo
+V 64
+/branches/left:2-25
+/branches/left-sub:4-19
+/branches/right:2-22
+PROPS-END
+
+
+Node-path: trunk/subdir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 25
+Node-copyfrom-path: branches/left/subdir
+
+
+Revision-number: 27
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 20
+(r28) partial update
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:53.049037Z
+PROPS-END
+
+Node-path: branches/partial
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 26
+Node-copyfrom-path: trunk/subdir
+
+
+Node-path: branches/partial/palindromes
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: 5d1c2024fb5efc4eef812856df1b080c
+Text-content-sha1: 5f8509ddd14c91a52864dd1447344e706f9bbc69
+Content-length: 18
+
+PROPS-END
+racecar
+
+
+Revision-number: 28
+Prop-content-length: 126
+Content-length: 126
+
+K 7
+svn:log
+V 28
+(r29) merge partial to trunk
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:14:56.041526Z
+PROPS-END
+
+Node-path: trunk/subdir
+Node-kind: dir
+Node-action: change
+Prop-content-length: 142
+Content-length: 142
+
+K 13
+svn:mergeinfo
+V 106
+/branches/left/subdir:2-25
+/branches/left-sub/subdir:4-19
+/branches/partial:27
+/branches/right/subdir:2-22
+PROPS-END
+
+
+Node-path: trunk/subdir/palindromes
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 27
+Node-copyfrom-path: branches/partial/palindromes
+Text-copy-source-md5: 5d1c2024fb5efc4eef812856df1b080c
+Text-copy-source-sha1: 5f8509ddd14c91a52864dd1447344e706f9bbc69
+
+
+Revision-number: 29
+Prop-content-length: 131
+Content-length: 131
+
+K 7
+svn:log
+V 33
+(r31) make bugfix branch from tag
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:15:00.039761Z
+PROPS-END
+
+Node-path: tags/v1.0
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 28
+Node-copyfrom-path: trunk
+
+
+Revision-number: 30
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 22
+(r32) commit to bugfix
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:15:03.043218Z
+PROPS-END
+
+Node-path: branches/bugfix
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 29
+Node-copyfrom-path: tags/v1.0
+
+
+Node-path: branches/bugfix/subdir/palindromes
+Node-kind: file
+Node-action: change
+Text-content-length: 14
+Text-content-md5: 3b12d98578a3f4320ba97e66da54fe5f
+Text-content-sha1: 672931c9e8ac2c408209efab2f015638b6d64042
+Content-length: 14
+
+racecar
+kayak
+
+
+Revision-number: 31
+Prop-content-length: 125
+Content-length: 125
+
+K 7
+svn:log
+V 27
+(r33) Merge BUGFIX to TRUNK
+K 10
+svn:author
+V 3
+adm
+K 8
+svn:date
+V 27
+2010-01-19T04:15:06.043723Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 133
+Content-length: 133
+
+K 13
+svn:mergeinfo
+V 98
+/branches/bugfix:30
+/branches/left:2-25
+/branches/left-sub:4-19
+/branches/right:2-22
+/tags/v1.0:29
+PROPS-END
+
+
+Node-path: trunk/subdir
+Node-kind: dir
+Node-action: change
+Prop-content-length: 190
+Content-length: 190
+
+K 13
+svn:mergeinfo
+V 154
+/branches/bugfix/subdir:30
+/branches/left/subdir:2-25
+/branches/left-sub/subdir:4-19
+/branches/partial:27
+/branches/right/subdir:2-22
+/tags/v1.0/subdir:29
+PROPS-END
+
+
+Node-path: trunk/subdir/palindromes
+Node-kind: file
+Node-action: change
+Text-content-length: 14
+Text-content-md5: 3b12d98578a3f4320ba97e66da54fe5f
+Text-content-sha1: 672931c9e8ac2c408209efab2f015638b6d64042
+Content-length: 14
+
+racecar
+kayak
+
+
diff --git a/t/t9153-git-svn-rewrite-uuid.sh b/t/t9153-git-svn-rewrite-uuid.sh
new file mode 100755 (executable)
index 0000000..88a2cfa
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Jay Soffian
+#
+
+test_description='git svn --rewrite-uuid test'
+
+. ./lib-git-svn.sh
+
+uuid=6cc8ada4-5932-4b4a-8242-3534ed8a3232
+
+test_expect_success 'load svn repo' "
+       svnadmin load -q '$rawsvnrepo' < '$TEST_DIRECTORY/t9153/svn.dump' &&
+       git svn init --minimize-url --rewrite-uuid='$uuid' '$svnrepo' &&
+       git svn fetch
+       "
+
+test_expect_success 'verify uuid' "
+       git cat-file commit refs/remotes/git-svn~0 | \
+          grep '^${git_svn_id}: .*@2 $uuid$' &&
+       git cat-file commit refs/remotes/git-svn~1 | \
+          grep '^${git_svn_id}: .*@1 $uuid$'
+       "
+
+test_done
diff --git a/t/t9153/svn.dump b/t/t9153/svn.dump
new file mode 100644 (file)
index 0000000..0ddfe70
--- /dev/null
@@ -0,0 +1,75 @@
+SVN-fs-dump-format-version: 2
+
+UUID: b4885626-c94f-4a6c-b179-00c030fc68e8
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-01-23T06:41:03.908576Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 11
+initial foo
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T06:41:48.353776Z
+PROPS-END
+
+Node-path: foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Text-content-sha1: f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 2
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 12
+now with bar
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T06:42:14.214640Z
+PROPS-END
+
+Node-path: foo
+Node-kind: file
+Node-action: change
+Text-content-length: 8
+Text-content-md5: f47c75614087a8dd938ba4acff252494
+Text-content-sha1: 4e48e2c9a3d2ca8a708cb0cc545700544efb5021
+Content-length: 8
+
+foo
+bar
+
+
diff --git a/t/t9154-git-svn-fancy-glob.sh b/t/t9154-git-svn-fancy-glob.sh
new file mode 100755 (executable)
index 0000000..a6a56a6
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Jay Soffian
+#
+
+test_description='git svn fancy glob test'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load svn repo' "
+       svnadmin load -q '$rawsvnrepo' < '$TEST_DIRECTORY/t9154/svn.dump' &&
+       git svn init --minimize-url -T trunk '$svnrepo' &&
+       git svn fetch
+       "
+
+test_expect_success 'add red branch' "
+       git config svn-remote.svn.branches 'branches/{red}:refs/remotes/*' &&
+       git svn fetch &&
+       git rev-parse refs/remotes/red &&
+       test_must_fail git rev-parse refs/remotes/green &&
+       test_must_fail git rev-parse refs/remotes/blue
+       "
+
+test_expect_success 'add green branch' "
+       GIT_CONFIG=.git/svn/.metadata git config --unset svn-remote.svn.branches-maxRev &&
+       git config svn-remote.svn.branches 'branches/{red,green}:refs/remotes/*' &&
+       git svn fetch &&
+       git rev-parse refs/remotes/red &&
+       git rev-parse refs/remotes/green &&
+       test_must_fail git rev-parse refs/remotes/blue
+       "
+
+test_expect_success 'add all branches' "
+       GIT_CONFIG=.git/svn/.metadata git config --unset svn-remote.svn.branches-maxRev &&
+       git config svn-remote.svn.branches 'branches/*:refs/remotes/*' &&
+       git svn fetch &&
+       git rev-parse refs/remotes/red &&
+       git rev-parse refs/remotes/green &&
+       git rev-parse refs/remotes/blue
+       "
+
+test_done
diff --git a/t/t9154/svn.dump b/t/t9154/svn.dump
new file mode 100644 (file)
index 0000000..3dfabb6
--- /dev/null
@@ -0,0 +1,222 @@
+SVN-fs-dump-format-version: 2
+
+UUID: a18093a0-5f0b-44e0-8d88-8911ac7078db
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-01-23T07:40:25.660053Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 104
+Content-length: 104
+
+K 7
+svn:log
+V 7
+initial
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:41:33.636365Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Text-content-sha1: f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 2
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 12
+add branches
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:42:37.290694Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/blue
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/green
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/red
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 3
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 10
+red change
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:43:02.208918Z
+PROPS-END
+
+Node-path: branches/red/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 8
+Text-content-md5: 64c3c8cf7d0233ab7627623a68888bd1
+Text-content-sha1: 95a0492027876adfd3891ec71ee37b79ee44d640
+Content-length: 8
+
+foo
+red
+
+
+Revision-number: 4
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 12
+green change
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:43:15.746586Z
+PROPS-END
+
+Node-path: branches/green/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 0209b6450891abc033d5eaaa9d3a8023
+Text-content-sha1: 87fc3bef9faeec48c0cd61dfc9851db377fdccf7
+Content-length: 10
+
+foo
+green
+
+
+Revision-number: 5
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 11
+blue change
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:43:29.364811Z
+PROPS-END
+
+Node-path: branches/blue/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 9
+Text-content-md5: 9fbe4c13d0bae86386ae5209b2e6b275
+Text-content-sha1: cc4575083459a16f9aaef796c4a2456d64691ba0
+Content-length: 9
+
+foo
+blue
+
+
+Revision-number: 6
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 12
+trunk change
+K 10
+svn:author
+V 3
+jay
+K 8
+svn:date
+V 27
+2010-01-23T07:44:01.313130Z
+PROPS-END
+
+Node-path: trunk/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 1c4db977d7a57c3bae582aab87948516
+Text-content-sha1: 469c08df449e702cf2a1fe746244a9ef3f837fad
+Content-length: 10
+
+foo
+trunk
+
+
index b49815d10806a57461bb98ffd89031680f84715a..131f03298809ad193cc75ab77deda6daaf713d1f 100755 (executable)
@@ -1092,9 +1092,12 @@ test_expect_success 'P: fail on blob mark in gitlink' '
 ### series Q (notes)
 ###
 
-note1_data="Note for the first commit"
-note2_data="Note for the second commit"
-note3_data="Note for the third commit"
+note1_data="The first note for the first commit"
+note2_data="The first note for the second commit"
+note3_data="The first note for the third commit"
+note1b_data="The second note for the first commit"
+note1c_data="The third note for the first commit"
+note2b_data="The second note for the second commit"
 
 test_tick
 cat >input <<INPUT_END
@@ -1169,7 +1172,45 @@ data <<EOF
 $note3_data
 EOF
 
+commit refs/notes/foobar
+mark :10
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes (:10)
+COMMIT
+
+N inline :3
+data <<EOF
+$note1b_data
+EOF
+
+commit refs/notes/foobar2
+mark :11
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes (:11)
+COMMIT
+
+N inline :3
+data <<EOF
+$note1c_data
+EOF
+
+commit refs/notes/foobar
+mark :12
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes (:12)
+COMMIT
+
+deleteall
+N inline :5
+data <<EOF
+$note2b_data
+EOF
+
 INPUT_END
+
 test_expect_success \
        'Q: commit notes' \
        'git fast-import <input &&
@@ -1224,8 +1265,8 @@ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 notes (:9)
 EOF
 test_expect_success \
-       'Q: verify notes commit' \
-       'git cat-file commit refs/notes/foobar | sed 1d >actual &&
+       'Q: verify first notes commit' \
+       'git cat-file commit refs/notes/foobar~2 | sed 1d >actual &&
        test_cmp expect actual'
 
 cat >expect.unsorted <<EOF
@@ -1235,23 +1276,310 @@ cat >expect.unsorted <<EOF
 EOF
 cat expect.unsorted | sort >expect
 test_expect_success \
-       'Q: verify notes tree' \
-       'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]*   / /" >actual &&
+       'Q: verify first notes tree' \
+       'git cat-file -p refs/notes/foobar~2^{tree} | sed "s/ [0-9a-f]* / /" >actual &&
         test_cmp expect actual'
 
 echo "$note1_data" >expect
 test_expect_success \
-       'Q: verify note for first commit' \
-       'git cat-file blob refs/notes/foobar:$commit1 >actual && test_cmp expect actual'
+       'Q: verify first note for first commit' \
+       'git cat-file blob refs/notes/foobar~2:$commit1 >actual && test_cmp expect actual'
 
 echo "$note2_data" >expect
 test_expect_success \
-       'Q: verify note for second commit' \
-       'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'
+       'Q: verify first note for second commit' \
+       'git cat-file blob refs/notes/foobar~2:$commit2 >actual && test_cmp expect actual'
 
 echo "$note3_data" >expect
 test_expect_success \
-       'Q: verify note for third commit' \
-       'git cat-file blob refs/notes/foobar:$commit3 >actual && test_cmp expect actual'
+       'Q: verify first note for third commit' \
+       'git cat-file blob refs/notes/foobar~2:$commit3 >actual && test_cmp expect actual'
+
+cat >expect <<EOF
+parent `git rev-parse --verify refs/notes/foobar~2`
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+notes (:10)
+EOF
+test_expect_success \
+       'Q: verify second notes commit' \
+       'git cat-file commit refs/notes/foobar^ | sed 1d >actual &&
+       test_cmp expect actual'
+
+cat >expect.unsorted <<EOF
+100644 blob $commit1
+100644 blob $commit2
+100644 blob $commit3
+EOF
+cat expect.unsorted | sort >expect
+test_expect_success \
+       'Q: verify second notes tree' \
+       'git cat-file -p refs/notes/foobar^^{tree} | sed "s/ [0-9a-f]*  / /" >actual &&
+        test_cmp expect actual'
+
+echo "$note1b_data" >expect
+test_expect_success \
+       'Q: verify second note for first commit' \
+       'git cat-file blob refs/notes/foobar^:$commit1 >actual && test_cmp expect actual'
+
+echo "$note2_data" >expect
+test_expect_success \
+       'Q: verify first note for second commit' \
+       'git cat-file blob refs/notes/foobar^:$commit2 >actual && test_cmp expect actual'
+
+echo "$note3_data" >expect
+test_expect_success \
+       'Q: verify first note for third commit' \
+       'git cat-file blob refs/notes/foobar^:$commit3 >actual && test_cmp expect actual'
+
+cat >expect <<EOF
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+notes (:11)
+EOF
+test_expect_success \
+       'Q: verify third notes commit' \
+       'git cat-file commit refs/notes/foobar2 | sed 1d >actual &&
+       test_cmp expect actual'
+
+cat >expect.unsorted <<EOF
+100644 blob $commit1
+EOF
+cat expect.unsorted | sort >expect
+test_expect_success \
+       'Q: verify third notes tree' \
+       'git cat-file -p refs/notes/foobar2^{tree} | sed "s/ [0-9a-f]*  / /" >actual &&
+        test_cmp expect actual'
+
+echo "$note1c_data" >expect
+test_expect_success \
+       'Q: verify third note for first commit' \
+       'git cat-file blob refs/notes/foobar2:$commit1 >actual && test_cmp expect actual'
+
+cat >expect <<EOF
+parent `git rev-parse --verify refs/notes/foobar^`
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+notes (:12)
+EOF
+test_expect_success \
+       'Q: verify fourth notes commit' \
+       'git cat-file commit refs/notes/foobar | sed 1d >actual &&
+       test_cmp expect actual'
+
+cat >expect.unsorted <<EOF
+100644 blob $commit2
+EOF
+cat expect.unsorted | sort >expect
+test_expect_success \
+       'Q: verify fourth notes tree' \
+       'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]*   / /" >actual &&
+        test_cmp expect actual'
+
+echo "$note2b_data" >expect
+test_expect_success \
+       'Q: verify second note for second commit' \
+       'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'
+
+###
+### series R (feature and option)
+###
+
+cat >input <<EOF
+feature no-such-feature-exists
+EOF
+
+test_expect_success 'R: abort on unsupported feature' '
+       test_must_fail git fast-import <input
+'
+
+cat >input <<EOF
+feature date-format=now
+EOF
+
+test_expect_success 'R: supported feature is accepted' '
+       git fast-import <input
+'
+
+cat >input << EOF
+blob
+data 3
+hi
+feature date-format=now
+EOF
+
+test_expect_success 'R: abort on receiving feature after data command' '
+       test_must_fail git fast-import <input
+'
+
+cat >input << EOF
+feature import-marks=git.marks
+feature import-marks=git2.marks
+EOF
+
+test_expect_success 'R: only one import-marks feature allowed per stream' '
+       test_must_fail git fast-import <input
+'
+
+cat >input << EOF
+feature export-marks=git.marks
+blob
+mark :1
+data 3
+hi
+
+EOF
+
+test_expect_success \
+    'R: export-marks feature results in a marks file being created' \
+    'cat input | git fast-import &&
+    grep :1 git.marks'
+
+test_expect_success \
+    'R: export-marks options can be overriden by commandline options' \
+    'cat input | git fast-import --export-marks=other.marks &&
+    grep :1 other.marks'
+
+cat >input << EOF
+feature import-marks=marks.out
+feature export-marks=marks.new
+EOF
+
+test_expect_success \
+    'R: import to output marks works without any content' \
+    'cat input | git fast-import &&
+    test_cmp marks.out marks.new'
+
+cat >input <<EOF
+feature import-marks=nonexistant.marks
+feature export-marks=marks.new
+EOF
+
+test_expect_success \
+    'R: import marks prefers commandline marks file over the stream' \
+    'cat input | git fast-import --import-marks=marks.out &&
+    test_cmp marks.out marks.new'
+
+
+cat >input <<EOF
+feature import-marks=nonexistant.marks
+feature export-marks=combined.marks
+EOF
+
+test_expect_success 'R: multiple --import-marks= should be honoured' '
+    head -n2 marks.out > one.marks &&
+    tail -n +3 marks.out > two.marks &&
+    git fast-import --import-marks=one.marks --import-marks=two.marks <input &&
+    test_cmp marks.out combined.marks
+'
+
+cat >input <<EOF
+feature relative-marks
+feature import-marks=relative.in
+feature export-marks=relative.out
+EOF
+
+test_expect_success 'R: feature relative-marks should be honoured' '
+    mkdir -p .git/info/fast-import/ &&
+    cp marks.new .git/info/fast-import/relative.in &&
+    git fast-import <input &&
+    test_cmp marks.new .git/info/fast-import/relative.out
+'
+
+cat >input <<EOF
+feature relative-marks
+feature import-marks=relative.in
+feature no-relative-marks
+feature export-marks=non-relative.out
+EOF
+
+test_expect_success 'R: feature no-relative-marks should be honoured' '
+    git fast-import <input &&
+    test_cmp marks.new non-relative.out
+'
+
+cat >input << EOF
+option git quiet
+blob
+data 3
+hi
+
+EOF
+
+touch empty
+
+test_expect_success 'R: quiet option results in no stats being output' '
+    cat input | git fast-import 2> output &&
+    test_cmp empty output
+'
+
+cat >input <<EOF
+option git non-existing-option
+EOF
+
+test_expect_success 'R: die on unknown option' '
+    test_must_fail git fast-import <input
+'
+
+test_expect_success 'R: unknown commandline options are rejected' '\
+    test_must_fail git fast-import --non-existing-option < /dev/null
+'
+
+cat >input <<EOF
+option non-existing-vcs non-existing-option
+EOF
+
+test_expect_success 'R: ignore non-git options' '
+    git fast-import <input
+'
+
+##
+## R: very large blobs
+##
+blobsize=$((2*1024*1024 + 53))
+test-genrandom bar $blobsize >expect
+cat >input <<INPUT_END
+commit refs/heads/big-file
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+R - big file
+COMMIT
+
+M 644 inline big1
+data $blobsize
+INPUT_END
+cat expect >>input
+cat >>input <<INPUT_END
+M 644 inline big2
+data $blobsize
+INPUT_END
+cat expect >>input
+echo >>input
+
+test_expect_success \
+       'R: blob bigger than threshold' \
+       'test_create_repo R &&
+        git --git-dir=R/.git fast-import --big-file-threshold=1 <input'
+test_expect_success \
+       'R: verify created pack' \
+       ': >verify &&
+        for p in R/.git/objects/pack/*.pack;
+        do
+          git verify-pack -v $p >>verify || exit;
+        done'
+test_expect_success \
+       'R: verify written objects' \
+       'git --git-dir=R/.git cat-file blob big-file:big1 >actual &&
+        test_cmp expect actual &&
+        a=$(git --git-dir=R/.git rev-parse big-file:big1) &&
+        b=$(git --git-dir=R/.git rev-parse big-file:big2) &&
+        test $a = $b'
+test_expect_success \
+       'R: blob appears only once' \
+       'n=$(grep $a verify | wc -l) &&
+        test 1 = $n'
 
 test_done
diff --git a/t/t9301-fast-export.sh b/t/t9301-fast-export.sh
deleted file mode 100755 (executable)
index 356964e..0000000
+++ /dev/null
@@ -1,374 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2007 Johannes E. Schindelin
-#
-
-test_description='git fast-export'
-. ./test-lib.sh
-
-test_expect_success 'setup' '
-
-       echo break it > file0 &&
-       git add file0 &&
-       test_tick &&
-       echo Wohlauf > file &&
-       git add file &&
-       test_tick &&
-       git commit -m initial &&
-       echo die Luft > file &&
-       echo geht frisch > file2 &&
-       git add file file2 &&
-       test_tick &&
-       git commit -m second &&
-       echo und > file2 &&
-       test_tick &&
-       git commit -m third file2 &&
-       test_tick &&
-       git tag rein &&
-       git checkout -b wer HEAD^ &&
-       echo lange > file2
-       test_tick &&
-       git commit -m sitzt file2 &&
-       test_tick &&
-       git tag -a -m valentin muss &&
-       git merge -s ours master
-
-'
-
-test_expect_success 'fast-export | fast-import' '
-
-       MASTER=$(git rev-parse --verify master) &&
-       REIN=$(git rev-parse --verify rein) &&
-       WER=$(git rev-parse --verify wer) &&
-       MUSS=$(git rev-parse --verify muss) &&
-       mkdir new &&
-       git --git-dir=new/.git init &&
-       git fast-export --all |
-       (cd new &&
-        git fast-import &&
-        test $MASTER = $(git rev-parse --verify refs/heads/master) &&
-        test $REIN = $(git rev-parse --verify refs/tags/rein) &&
-        test $WER = $(git rev-parse --verify refs/heads/wer) &&
-        test $MUSS = $(git rev-parse --verify refs/tags/muss))
-
-'
-
-test_expect_success 'fast-export master~2..master' '
-
-       git fast-export master~2..master |
-               sed "s/master/partial/" |
-               (cd new &&
-                git fast-import &&
-                test $MASTER != $(git rev-parse --verify refs/heads/partial) &&
-                git diff --exit-code master partial &&
-                git diff --exit-code master^ partial^ &&
-                test_must_fail git rev-parse partial~2)
-
-'
-
-test_expect_success 'iso-8859-1' '
-
-       git config i18n.commitencoding ISO8859-1 &&
-       # use author and committer name in ISO-8859-1 to match it.
-       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
-       test_tick &&
-       echo rosten >file &&
-       git commit -s -m den file &&
-       git fast-export wer^..wer |
-               sed "s/wer/i18n/" |
-               (cd new &&
-                git fast-import &&
-                git cat-file commit i18n | grep "Áéí óú")
-
-'
-test_expect_success 'import/export-marks' '
-
-       git checkout -b marks master &&
-       git fast-export --export-marks=tmp-marks HEAD &&
-       test -s tmp-marks &&
-       test $(wc -l < tmp-marks) -eq 3 &&
-       test $(
-               git fast-export --import-marks=tmp-marks\
-               --export-marks=tmp-marks HEAD |
-               grep ^commit |
-               wc -l) \
-       -eq 0 &&
-       echo change > file &&
-       git commit -m "last commit" file &&
-       test $(
-               git fast-export --import-marks=tmp-marks \
-               --export-marks=tmp-marks HEAD |
-               grep ^commit\  |
-               wc -l) \
-       -eq 1 &&
-       test $(wc -l < tmp-marks) -eq 4
-
-'
-
-cat > signed-tag-import << EOF
-tag sign-your-name
-from $(git rev-parse HEAD)
-tagger C O Mitter <committer@example.com> 1112911993 -0700
-data 210
-A message for a sign
------BEGIN PGP SIGNATURE-----
-Version: GnuPG v1.4.5 (GNU/Linux)
-
-fakedsignaturefakedsignaturefakedsignaturefakedsignaturfakedsign
-aturefakedsignaturefake=
-=/59v
------END PGP SIGNATURE-----
-EOF
-
-test_expect_success 'set up faked signed tag' '
-
-       cat signed-tag-import | git fast-import
-
-'
-
-test_expect_success 'signed-tags=abort' '
-
-       test_must_fail git fast-export --signed-tags=abort sign-your-name
-
-'
-
-test_expect_success 'signed-tags=verbatim' '
-
-       git fast-export --signed-tags=verbatim sign-your-name > output &&
-       grep PGP output
-
-'
-
-test_expect_success 'signed-tags=strip' '
-
-       git fast-export --signed-tags=strip sign-your-name > output &&
-       ! grep PGP output
-
-'
-
-test_expect_success 'setup submodule' '
-
-       git checkout -f master &&
-       mkdir sub &&
-       cd sub &&
-       git init  &&
-       echo test file > file &&
-       git add file &&
-       git commit -m sub_initial &&
-       cd .. &&
-       git submodule add "`pwd`/sub" sub &&
-       git commit -m initial &&
-       test_tick &&
-       cd sub &&
-       echo more data >> file &&
-       git add file &&
-       git commit -m sub_second &&
-       cd .. &&
-       git add sub &&
-       git commit -m second
-
-'
-
-test_expect_success 'submodule fast-export | fast-import' '
-
-       SUBENT1=$(git ls-tree master^ sub) &&
-       SUBENT2=$(git ls-tree master sub) &&
-       rm -rf new &&
-       mkdir new &&
-       git --git-dir=new/.git init &&
-       git fast-export --signed-tags=strip --all |
-       (cd new &&
-        git fast-import &&
-        test "$SUBENT1" = "$(git ls-tree refs/heads/master^ sub)" &&
-        test "$SUBENT2" = "$(git ls-tree refs/heads/master sub)" &&
-        git checkout master &&
-        git submodule init &&
-        git submodule update &&
-        cmp sub/file ../sub/file)
-
-'
-
-GIT_AUTHOR_NAME='A U Thor'; export GIT_AUTHOR_NAME
-GIT_COMMITTER_NAME='C O Mitter'; export GIT_COMMITTER_NAME
-
-test_expect_success 'setup copies' '
-
-       git config --unset i18n.commitencoding &&
-       git checkout -b copy rein &&
-       git mv file file3 &&
-       git commit -m move1 &&
-       test_tick &&
-       cp file2 file4 &&
-       git add file4 &&
-       git mv file2 file5 &&
-       git commit -m copy1 &&
-       test_tick &&
-       cp file3 file6 &&
-       git add file6 &&
-       git commit -m copy2 &&
-       test_tick &&
-       echo more text >> file6 &&
-       echo even more text >> file6 &&
-       git add file6 &&
-       git commit -m modify &&
-       test_tick &&
-       cp file6 file7 &&
-       echo test >> file7 &&
-       git add file7 &&
-       git commit -m copy_modify
-
-'
-
-test_expect_success 'fast-export -C -C | fast-import' '
-
-       ENTRY=$(git rev-parse --verify copy) &&
-       rm -rf new &&
-       mkdir new &&
-       git --git-dir=new/.git init &&
-       git fast-export -C -C --signed-tags=strip --all > output &&
-       grep "^C \"file6\" \"file7\"\$" output &&
-       cat output |
-       (cd new &&
-        git fast-import &&
-        test $ENTRY = $(git rev-parse --verify refs/heads/copy))
-
-'
-
-test_expect_success 'fast-export | fast-import when master is tagged' '
-
-       git tag -m msg last &&
-       git fast-export -C -C --signed-tags=strip --all > output &&
-       test $(grep -c "^tag " output) = 3
-
-'
-
-cat > tag-content << EOF
-object $(git rev-parse HEAD)
-type commit
-tag rosten
-EOF
-
-test_expect_success 'cope with tagger-less tags' '
-
-       TAG=$(git hash-object -t tag -w tag-content) &&
-       git update-ref refs/tags/sonnenschein $TAG &&
-       git fast-export -C -C --signed-tags=strip --all > output &&
-       test $(grep -c "^tag " output) = 4 &&
-       ! grep "Unspecified Tagger" output &&
-       git fast-export -C -C --signed-tags=strip --all \
-               --fake-missing-tagger > output &&
-       test $(grep -c "^tag " output) = 4 &&
-       grep "Unspecified Tagger" output
-
-'
-
-test_expect_success 'setup for limiting exports by PATH' '
-       mkdir limit-by-paths &&
-       cd limit-by-paths &&
-       git init &&
-       echo hi > there &&
-       git add there &&
-       git commit -m "First file" &&
-       echo foo > bar &&
-       git add bar &&
-       git commit -m "Second file" &&
-       git tag -a -m msg mytag &&
-       echo morefoo >> bar &&
-       git add bar &&
-       git commit -m "Change to second file" &&
-       cd ..
-'
-
-cat > limit-by-paths/expected << EOF
-blob
-mark :1
-data 3
-hi
-
-reset refs/tags/mytag
-commit refs/tags/mytag
-mark :2
-author A U Thor <author@example.com> 1112912713 -0700
-committer C O Mitter <committer@example.com> 1112912713 -0700
-data 11
-First file
-M 100644 :1 there
-
-EOF
-
-test_expect_success 'dropping tag of filtered out object' '
-       cd limit-by-paths &&
-       git fast-export --tag-of-filtered-object=drop mytag -- there > output &&
-       test_cmp output expected &&
-       cd ..
-'
-
-cat >> limit-by-paths/expected << EOF
-tag mytag
-from :2
-tagger C O Mitter <committer@example.com> 1112912713 -0700
-data 4
-msg
-
-EOF
-
-test_expect_success 'rewriting tag of filtered out object' '
-       cd limit-by-paths &&
-       git fast-export --tag-of-filtered-object=rewrite mytag -- there > output &&
-       test_cmp output expected &&
-       cd ..
-'
-
-cat > limit-by-paths/expected << EOF
-blob
-mark :1
-data 4
-foo
-
-blob
-mark :2
-data 3
-hi
-
-reset refs/heads/master
-commit refs/heads/master
-mark :3
-author A U Thor <author@example.com> 1112912713 -0700
-committer C O Mitter <committer@example.com> 1112912713 -0700
-data 12
-Second file
-M 100644 :1 bar
-M 100644 :2 there
-
-EOF
-
-test_expect_failure 'no exact-ref revisions included' '
-       cd limit-by-paths &&
-       git fast-export master~2..master~1 > output &&
-       test_cmp output expected &&
-       cd ..
-'
-
-
-test_expect_success 'set-up a few more tags for tag export tests' '
-       git checkout -f master &&
-       HEAD_TREE=`git show -s --pretty=raw HEAD | grep tree | sed "s/tree //"` &&
-       git tag    tree_tag        -m "tagging a tree" $HEAD_TREE &&
-       git tag -a tree_tag-obj    -m "tagging a tree" $HEAD_TREE &&
-       git tag    tag-obj_tag     -m "tagging a tag" tree_tag-obj &&
-       git tag -a tag-obj_tag-obj -m "tagging a tag" tree_tag-obj
-'
-
-test_expect_success 'tree_tag'        '
-       mkdir result &&
-       (cd result && git init) &&
-       git fast-export tree_tag > fe-stream &&
-       (cd result && git fast-import < ../fe-stream)
-'
-
-# NEEDSWORK: not just check return status, but validate the output
-test_expect_success 'tree_tag-obj'    'git fast-export tree_tag-obj'
-test_expect_success 'tag-obj_tag'     'git fast-export tag-obj_tag'
-test_expect_success 'tag-obj_tag-obj' 'git fast-export tag-obj_tag-obj'
-
-test_done
diff --git a/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh
new file mode 100755 (executable)
index 0000000..a5c99d8
--- /dev/null
@@ -0,0 +1,623 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Johan Herland
+#
+
+test_description='test git fast-import of notes objects'
+. ./test-lib.sh
+
+
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+first commit
+COMMIT
+
+M 644 inline foo
+data <<EOF
+file foo in first commit
+EOF
+
+M 755 inline bar
+data <<EOF
+file bar in first commit
+EOF
+
+M 644 inline baz/xyzzy
+data <<EOF
+file baz/xyzzy in first commit
+EOF
+
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+second commit
+COMMIT
+
+M 644 inline foo
+data <<EOF
+file foo in second commit
+EOF
+
+M 755 inline baz/xyzzy
+data <<EOF
+file baz/xyzzy in second commit
+EOF
+
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+third commit
+COMMIT
+
+M 644 inline foo
+data <<EOF
+file foo in third commit
+EOF
+
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+fourth commit
+COMMIT
+
+M 755 inline bar
+data <<EOF
+file bar in fourth commit
+EOF
+
+INPUT_END
+
+test_expect_success 'set up master branch' '
+
+       git fast-import <input &&
+       git whatchanged master
+'
+
+commit4=$(git rev-parse refs/heads/master)
+commit3=$(git rev-parse "$commit4^")
+commit2=$(git rev-parse "$commit4~2")
+commit1=$(git rev-parse "$commit4~3")
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+first notes commit
+COMMIT
+
+M 644 inline $commit1
+data <<EOF
+first note for first commit
+EOF
+
+M 755 inline $commit2
+data <<EOF
+first note for second commit
+EOF
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+    fourth commit
+    third commit
+    second commit
+    first note for second commit
+    first commit
+    first note for first commit
+EXPECT_END
+
+test_expect_success 'add notes with simple M command' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+second notes commit
+COMMIT
+
+from refs/notes/test^0
+N inline $commit3
+data <<EOF
+first note for third commit
+EOF
+
+N inline $commit4
+data <<EOF
+first note for fourth commit
+EOF
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+    fourth commit
+    first note for fourth commit
+    third commit
+    first note for third commit
+    second commit
+    first note for second commit
+    first commit
+    first note for first commit
+EXPECT_END
+
+test_expect_success 'add notes with simple N command' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+third notes commit
+COMMIT
+
+from refs/notes/test^0
+N inline $commit1
+data <<EOF
+second note for first commit
+EOF
+
+N inline $commit2
+data <<EOF
+second note for second commit
+EOF
+
+N inline $commit3
+data <<EOF
+second note for third commit
+EOF
+
+N inline $commit4
+data <<EOF
+second note for fourth commit
+EOF
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+    fourth commit
+    second note for fourth commit
+    third commit
+    second note for third commit
+    second commit
+    second note for second commit
+    first commit
+    second note for first commit
+EXPECT_END
+
+test_expect_success 'update existing notes with N command' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+fourth notes commit
+COMMIT
+
+from refs/notes/test^0
+M 644 inline $(echo "$commit3" | sed "s|^..|&/|")
+data <<EOF
+prefix of note for third commit
+EOF
+
+M 644 inline $(echo "$commit4" | sed "s|^..|&/|")
+data <<EOF
+prefix of note for fourth commit
+EOF
+
+M 644 inline $(echo "$commit4" | sed "s|^\(..\)\(..\)|\1/\2/|")
+data <<EOF
+pre-prefix of note for fourth commit
+EOF
+
+N inline $commit1
+data <<EOF
+third note for first commit
+EOF
+
+N inline $commit2
+data <<EOF
+third note for second commit
+EOF
+
+N inline $commit3
+data <<EOF
+third note for third commit
+EOF
+
+N inline $commit4
+data <<EOF
+third note for fourth commit
+EOF
+
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+    fourth commit
+    pre-prefix of note for fourth commit
+    prefix of note for fourth commit
+    third note for fourth commit
+    third commit
+    prefix of note for third commit
+    third note for third commit
+    second commit
+    third note for second commit
+    first commit
+    third note for first commit
+EXPECT_END
+
+test_expect_success 'add concatentation notes with M command' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+fifth notes commit
+COMMIT
+
+from refs/notes/test^0
+deleteall
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+    fourth commit
+    third commit
+    second commit
+    first commit
+EXPECT_END
+
+test_expect_success 'verify that deleteall also removes notes' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/test
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+sixth notes commit
+COMMIT
+
+from refs/notes/test^0
+M 644 inline $commit1
+data <<EOF
+third note for first commit
+EOF
+
+M 644 inline $commit3
+data <<EOF
+third note for third commit
+EOF
+
+N inline $commit1
+data <<EOF
+fourth note for first commit
+EOF
+
+N inline $commit3
+data <<EOF
+fourth note for third commit
+EOF
+
+INPUT_END
+
+cat >expect <<EXPECT_END
+    fourth commit
+    third commit
+    fourth note for third commit
+    second commit
+    first commit
+    fourth note for first commit
+EXPECT_END
+
+test_expect_success 'verify that later N commands override earlier M commands' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+# Write fast-import commands to create the given number of commits
+fast_import_commits () {
+       my_ref=$1
+       my_num_commits=$2
+       my_append_to_file=$3
+       my_i=0
+       while test $my_i -lt $my_num_commits
+       do
+               my_i=$(($my_i + 1))
+               test_tick
+               cat >>"$my_append_to_file" <<INPUT_END
+commit $my_ref
+mark :$my_i
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit #$my_i
+COMMIT
+
+M 644 inline file
+data <<EOF
+file contents in commit #$my_i
+EOF
+
+INPUT_END
+       done
+}
+
+# Write fast-import commands to create the given number of notes annotating
+# the commits created by fast_import_commits()
+fast_import_notes () {
+       my_notes_ref=$1
+       my_num_commits=$2
+       my_append_to_file=$3
+       my_note_append=$4
+       test_tick
+       cat >>"$my_append_to_file" <<INPUT_END
+commit $my_notes_ref
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+committing $my_num_commits notes
+COMMIT
+
+INPUT_END
+
+       my_i=0
+       while test $my_i -lt $my_num_commits
+       do
+               my_i=$(($my_i + 1))
+               cat >>"$my_append_to_file" <<INPUT_END
+N inline :$my_i
+data <<EOF
+note for commit #$my_i$my_note_append
+EOF
+
+INPUT_END
+       done
+}
+
+
+rm input expect
+num_commits=400
+# Create lots of commits
+fast_import_commits "refs/heads/many_commits" $num_commits input
+# Create one note per above commit
+fast_import_notes "refs/notes/many_notes" $num_commits input
+# Add a couple of non-notes as well
+test_tick
+cat >>input <<INPUT_END
+commit refs/notes/many_notes
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+committing some non-notes to the notes tree
+COMMIT
+
+M 755 inline foobar/non-note.txt
+data <<EOF
+This is not a note, but rather a regular file residing in a notes tree
+EOF
+
+M 644 inline deadbeef
+data <<EOF
+Non-note file
+EOF
+
+M 644 inline de/adbeef
+data <<EOF
+Another non-note file
+EOF
+
+INPUT_END
+# Finally create the expected output from all these notes and commits
+i=$num_commits
+while test $i -gt 0
+do
+       cat >>expect <<EXPECT_END
+    commit #$i
+    note for commit #$i
+EXPECT_END
+       i=$(($i - 1))
+done
+
+test_expect_success 'add lots of commits and notes' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits |
+           grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_expect_success 'verify that lots of notes trigger a fanout scheme' '
+
+       # None of the entries in the top-level notes tree should be a full SHA1
+       git ls-tree --name-only refs/notes/many_notes |
+       while read path
+       do
+               if test $(expr length "$path") -ge 40
+               then
+                       return 1
+               fi
+       done
+
+'
+
+cat >>expect_non-note1 << EOF
+This is not a note, but rather a regular file residing in a notes tree
+EOF
+
+cat >>expect_non-note2 << EOF
+Non-note file
+EOF
+
+cat >>expect_non-note3 << EOF
+Another non-note file
+EOF
+
+test_expect_success 'verify that non-notes are untouched by a fanout change' '
+
+       git cat-file -p refs/notes/many_notes:foobar/non-note.txt > actual &&
+       test_cmp expect_non-note1 actual &&
+       git cat-file -p refs/notes/many_notes:deadbeef > actual &&
+       test_cmp expect_non-note2 actual &&
+       git cat-file -p refs/notes/many_notes:de/adbeef > actual &&
+       test_cmp expect_non-note3 actual
+
+'
+remaining_notes=10
+test_tick
+cat >>input <<INPUT_END
+commit refs/notes/many_notes
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+removing all notes but $remaining_notes
+COMMIT
+from refs/notes/many_notes^0
+INPUT_END
+
+i=$remaining_notes
+while test $i -lt $num_commits
+do
+       i=$(($i + 1))
+       cat >>input <<INPUT_END
+N 0000000000000000000000000000000000000000 :$i
+INPUT_END
+done
+
+i=$num_commits
+rm expect
+while test $i -gt 0
+do
+       cat >>expect <<EXPECT_END
+    commit #$i
+EXPECT_END
+       if test $i -le $remaining_notes
+       then
+               cat >>expect <<EXPECT_END
+    note for commit #$i
+EXPECT_END
+       fi
+       i=$(($i - 1))
+done
+
+test_expect_success 'remove lots of notes' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits |
+           grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_expect_success 'verify that removing notes trigger fanout consolidation' '
+
+       # All entries in the top-level notes tree should be a full SHA1
+       git ls-tree --name-only -r refs/notes/many_notes |
+       while read path
+       do
+               # Explicitly ignore the non-note paths
+               test "$path" = "foobar/non-note.txt" && continue
+               test "$path" = "deadbeef" && continue
+               test "$path" = "de/adbeef" && continue
+
+               if test $(expr length "$path") -ne 40
+               then
+                       return 1
+               fi
+       done
+
+'
+
+test_expect_success 'verify that non-notes are untouched by a fanout change' '
+
+       git cat-file -p refs/notes/many_notes:foobar/non-note.txt > actual &&
+       test_cmp expect_non-note1 actual &&
+       git cat-file -p refs/notes/many_notes:deadbeef > actual &&
+       test_cmp expect_non-note2 actual &&
+       git cat-file -p refs/notes/many_notes:de/adbeef > actual &&
+       test_cmp expect_non-note3 actual
+
+'
+
+
+rm input expect
+num_notes_refs=10
+num_commits=16
+some_commits=8
+# Create commits
+fast_import_commits "refs/heads/more_commits" $num_commits input
+# Create one note per above commit per notes ref
+i=0
+while test $i -lt $num_notes_refs
+do
+       i=$(($i + 1))
+       fast_import_notes "refs/notes/more_notes_$i" $num_commits input
+done
+# Trigger branch reloading in git-fast-import by repeating the note creation
+i=0
+while test $i -lt $num_notes_refs
+do
+       i=$(($i + 1))
+       fast_import_notes "refs/notes/more_notes_$i" $some_commits input " (2)"
+done
+# Finally create the expected output from the notes in refs/notes/more_notes_1
+i=$num_commits
+while test $i -gt 0
+do
+       note_data="note for commit #$i"
+       if test $i -le $some_commits
+       then
+               note_data="$note_data (2)"
+       fi
+       cat >>expect <<EXPECT_END
+    commit #$i
+    $note_data
+EXPECT_END
+       i=$(($i - 1))
+done
+
+test_expect_success "add notes to $num_commits commits in each of $num_notes_refs refs" '
+
+       git fast-import --active-branches=5 <input &&
+       GIT_NOTES_REF=refs/notes/more_notes_1 git log refs/heads/more_commits |
+           grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_done
diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
new file mode 100755 (executable)
index 0000000..356964e
--- /dev/null
@@ -0,0 +1,374 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='git fast-export'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+       echo break it > file0 &&
+       git add file0 &&
+       test_tick &&
+       echo Wohlauf > file &&
+       git add file &&
+       test_tick &&
+       git commit -m initial &&
+       echo die Luft > file &&
+       echo geht frisch > file2 &&
+       git add file file2 &&
+       test_tick &&
+       git commit -m second &&
+       echo und > file2 &&
+       test_tick &&
+       git commit -m third file2 &&
+       test_tick &&
+       git tag rein &&
+       git checkout -b wer HEAD^ &&
+       echo lange > file2
+       test_tick &&
+       git commit -m sitzt file2 &&
+       test_tick &&
+       git tag -a -m valentin muss &&
+       git merge -s ours master
+
+'
+
+test_expect_success 'fast-export | fast-import' '
+
+       MASTER=$(git rev-parse --verify master) &&
+       REIN=$(git rev-parse --verify rein) &&
+       WER=$(git rev-parse --verify wer) &&
+       MUSS=$(git rev-parse --verify muss) &&
+       mkdir new &&
+       git --git-dir=new/.git init &&
+       git fast-export --all |
+       (cd new &&
+        git fast-import &&
+        test $MASTER = $(git rev-parse --verify refs/heads/master) &&
+        test $REIN = $(git rev-parse --verify refs/tags/rein) &&
+        test $WER = $(git rev-parse --verify refs/heads/wer) &&
+        test $MUSS = $(git rev-parse --verify refs/tags/muss))
+
+'
+
+test_expect_success 'fast-export master~2..master' '
+
+       git fast-export master~2..master |
+               sed "s/master/partial/" |
+               (cd new &&
+                git fast-import &&
+                test $MASTER != $(git rev-parse --verify refs/heads/partial) &&
+                git diff --exit-code master partial &&
+                git diff --exit-code master^ partial^ &&
+                test_must_fail git rev-parse partial~2)
+
+'
+
+test_expect_success 'iso-8859-1' '
+
+       git config i18n.commitencoding ISO8859-1 &&
+       # use author and committer name in ISO-8859-1 to match it.
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+       test_tick &&
+       echo rosten >file &&
+       git commit -s -m den file &&
+       git fast-export wer^..wer |
+               sed "s/wer/i18n/" |
+               (cd new &&
+                git fast-import &&
+                git cat-file commit i18n | grep "Áéí óú")
+
+'
+test_expect_success 'import/export-marks' '
+
+       git checkout -b marks master &&
+       git fast-export --export-marks=tmp-marks HEAD &&
+       test -s tmp-marks &&
+       test $(wc -l < tmp-marks) -eq 3 &&
+       test $(
+               git fast-export --import-marks=tmp-marks\
+               --export-marks=tmp-marks HEAD |
+               grep ^commit |
+               wc -l) \
+       -eq 0 &&
+       echo change > file &&
+       git commit -m "last commit" file &&
+       test $(
+               git fast-export --import-marks=tmp-marks \
+               --export-marks=tmp-marks HEAD |
+               grep ^commit\  |
+               wc -l) \
+       -eq 1 &&
+       test $(wc -l < tmp-marks) -eq 4
+
+'
+
+cat > signed-tag-import << EOF
+tag sign-your-name
+from $(git rev-parse HEAD)
+tagger C O Mitter <committer@example.com> 1112911993 -0700
+data 210
+A message for a sign
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+fakedsignaturefakedsignaturefakedsignaturefakedsignaturfakedsign
+aturefakedsignaturefake=
+=/59v
+-----END PGP SIGNATURE-----
+EOF
+
+test_expect_success 'set up faked signed tag' '
+
+       cat signed-tag-import | git fast-import
+
+'
+
+test_expect_success 'signed-tags=abort' '
+
+       test_must_fail git fast-export --signed-tags=abort sign-your-name
+
+'
+
+test_expect_success 'signed-tags=verbatim' '
+
+       git fast-export --signed-tags=verbatim sign-your-name > output &&
+       grep PGP output
+
+'
+
+test_expect_success 'signed-tags=strip' '
+
+       git fast-export --signed-tags=strip sign-your-name > output &&
+       ! grep PGP output
+
+'
+
+test_expect_success 'setup submodule' '
+
+       git checkout -f master &&
+       mkdir sub &&
+       cd sub &&
+       git init  &&
+       echo test file > file &&
+       git add file &&
+       git commit -m sub_initial &&
+       cd .. &&
+       git submodule add "`pwd`/sub" sub &&
+       git commit -m initial &&
+       test_tick &&
+       cd sub &&
+       echo more data >> file &&
+       git add file &&
+       git commit -m sub_second &&
+       cd .. &&
+       git add sub &&
+       git commit -m second
+
+'
+
+test_expect_success 'submodule fast-export | fast-import' '
+
+       SUBENT1=$(git ls-tree master^ sub) &&
+       SUBENT2=$(git ls-tree master sub) &&
+       rm -rf new &&
+       mkdir new &&
+       git --git-dir=new/.git init &&
+       git fast-export --signed-tags=strip --all |
+       (cd new &&
+        git fast-import &&
+        test "$SUBENT1" = "$(git ls-tree refs/heads/master^ sub)" &&
+        test "$SUBENT2" = "$(git ls-tree refs/heads/master sub)" &&
+        git checkout master &&
+        git submodule init &&
+        git submodule update &&
+        cmp sub/file ../sub/file)
+
+'
+
+GIT_AUTHOR_NAME='A U Thor'; export GIT_AUTHOR_NAME
+GIT_COMMITTER_NAME='C O Mitter'; export GIT_COMMITTER_NAME
+
+test_expect_success 'setup copies' '
+
+       git config --unset i18n.commitencoding &&
+       git checkout -b copy rein &&
+       git mv file file3 &&
+       git commit -m move1 &&
+       test_tick &&
+       cp file2 file4 &&
+       git add file4 &&
+       git mv file2 file5 &&
+       git commit -m copy1 &&
+       test_tick &&
+       cp file3 file6 &&
+       git add file6 &&
+       git commit -m copy2 &&
+       test_tick &&
+       echo more text >> file6 &&
+       echo even more text >> file6 &&
+       git add file6 &&
+       git commit -m modify &&
+       test_tick &&
+       cp file6 file7 &&
+       echo test >> file7 &&
+       git add file7 &&
+       git commit -m copy_modify
+
+'
+
+test_expect_success 'fast-export -C -C | fast-import' '
+
+       ENTRY=$(git rev-parse --verify copy) &&
+       rm -rf new &&
+       mkdir new &&
+       git --git-dir=new/.git init &&
+       git fast-export -C -C --signed-tags=strip --all > output &&
+       grep "^C \"file6\" \"file7\"\$" output &&
+       cat output |
+       (cd new &&
+        git fast-import &&
+        test $ENTRY = $(git rev-parse --verify refs/heads/copy))
+
+'
+
+test_expect_success 'fast-export | fast-import when master is tagged' '
+
+       git tag -m msg last &&
+       git fast-export -C -C --signed-tags=strip --all > output &&
+       test $(grep -c "^tag " output) = 3
+
+'
+
+cat > tag-content << EOF
+object $(git rev-parse HEAD)
+type commit
+tag rosten
+EOF
+
+test_expect_success 'cope with tagger-less tags' '
+
+       TAG=$(git hash-object -t tag -w tag-content) &&
+       git update-ref refs/tags/sonnenschein $TAG &&
+       git fast-export -C -C --signed-tags=strip --all > output &&
+       test $(grep -c "^tag " output) = 4 &&
+       ! grep "Unspecified Tagger" output &&
+       git fast-export -C -C --signed-tags=strip --all \
+               --fake-missing-tagger > output &&
+       test $(grep -c "^tag " output) = 4 &&
+       grep "Unspecified Tagger" output
+
+'
+
+test_expect_success 'setup for limiting exports by PATH' '
+       mkdir limit-by-paths &&
+       cd limit-by-paths &&
+       git init &&
+       echo hi > there &&
+       git add there &&
+       git commit -m "First file" &&
+       echo foo > bar &&
+       git add bar &&
+       git commit -m "Second file" &&
+       git tag -a -m msg mytag &&
+       echo morefoo >> bar &&
+       git add bar &&
+       git commit -m "Change to second file" &&
+       cd ..
+'
+
+cat > limit-by-paths/expected << EOF
+blob
+mark :1
+data 3
+hi
+
+reset refs/tags/mytag
+commit refs/tags/mytag
+mark :2
+author A U Thor <author@example.com> 1112912713 -0700
+committer C O Mitter <committer@example.com> 1112912713 -0700
+data 11
+First file
+M 100644 :1 there
+
+EOF
+
+test_expect_success 'dropping tag of filtered out object' '
+       cd limit-by-paths &&
+       git fast-export --tag-of-filtered-object=drop mytag -- there > output &&
+       test_cmp output expected &&
+       cd ..
+'
+
+cat >> limit-by-paths/expected << EOF
+tag mytag
+from :2
+tagger C O Mitter <committer@example.com> 1112912713 -0700
+data 4
+msg
+
+EOF
+
+test_expect_success 'rewriting tag of filtered out object' '
+       cd limit-by-paths &&
+       git fast-export --tag-of-filtered-object=rewrite mytag -- there > output &&
+       test_cmp output expected &&
+       cd ..
+'
+
+cat > limit-by-paths/expected << EOF
+blob
+mark :1
+data 4
+foo
+
+blob
+mark :2
+data 3
+hi
+
+reset refs/heads/master
+commit refs/heads/master
+mark :3
+author A U Thor <author@example.com> 1112912713 -0700
+committer C O Mitter <committer@example.com> 1112912713 -0700
+data 12
+Second file
+M 100644 :1 bar
+M 100644 :2 there
+
+EOF
+
+test_expect_failure 'no exact-ref revisions included' '
+       cd limit-by-paths &&
+       git fast-export master~2..master~1 > output &&
+       test_cmp output expected &&
+       cd ..
+'
+
+
+test_expect_success 'set-up a few more tags for tag export tests' '
+       git checkout -f master &&
+       HEAD_TREE=`git show -s --pretty=raw HEAD | grep tree | sed "s/tree //"` &&
+       git tag    tree_tag        -m "tagging a tree" $HEAD_TREE &&
+       git tag -a tree_tag-obj    -m "tagging a tree" $HEAD_TREE &&
+       git tag    tag-obj_tag     -m "tagging a tag" tree_tag-obj &&
+       git tag -a tag-obj_tag-obj -m "tagging a tag" tree_tag-obj
+'
+
+test_expect_success 'tree_tag'        '
+       mkdir result &&
+       (cd result && git init) &&
+       git fast-export tree_tag > fe-stream &&
+       (cd result && git fast-import < ../fe-stream)
+'
+
+# NEEDSWORK: not just check return status, but validate the output
+test_expect_success 'tree_tag-obj'    'git fast-export tree_tag-obj'
+test_expect_success 'tag-obj_tag'     'git fast-export tag-obj_tag'
+test_expect_success 'tag-obj_tag-obj' 'git fast-export tag-obj_tag-obj'
+
+test_done
index c2ec3cb4bd97693ef8f8f25064297f019053e03a..4327eb8baa2b00b0833fdbceac27bb8291370bda 100755 (executable)
@@ -96,7 +96,7 @@ EOF
 
 test_expect_success 'pserver authentication' \
   'cat request-anonymous | git-cvsserver pserver >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'pserver authentication failure (non-anonymous user)' \
   'if cat request-git | git-cvsserver pserver >log 2>&1
@@ -105,11 +105,11 @@ test_expect_success 'pserver authentication failure (non-anonymous user)' \
    else
        true
    fi &&
-   sed -ne \$p log | grep "^I HATE YOU$"'
+   sed -ne \$p log | grep "^I HATE YOU\$"'
 
 test_expect_success 'pserver authentication (login)' \
   'cat login-anonymous | git-cvsserver pserver >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'pserver authentication failure (login/non-anonymous user)' \
   'if cat login-git | git-cvsserver pserver >log 2>&1
@@ -118,7 +118,7 @@ test_expect_success 'pserver authentication failure (login/non-anonymous user)'
    else
        true
    fi &&
-   sed -ne \$p log | grep "^I HATE YOU$"'
+   sed -ne \$p log | grep "^I HATE YOU\$"'
 
 
 # misuse pserver authentication for testing of req_Root
@@ -156,7 +156,7 @@ test_expect_success 'req_Root failure (conflicting roots)' \
 
 test_expect_success 'req_Root (strict paths)' \
   'cat request-anonymous | git-cvsserver --strict-paths pserver "$SERVERDIR" >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'req_Root failure (strict-paths)' '
     ! cat request-anonymous |
@@ -165,7 +165,7 @@ test_expect_success 'req_Root failure (strict-paths)' '
 
 test_expect_success 'req_Root (w/o strict-paths)' \
   'cat request-anonymous | git-cvsserver pserver "$WORKDIR/" >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'req_Root failure (w/o strict-paths)' '
     ! cat request-anonymous |
@@ -183,7 +183,7 @@ EOF
 
 test_expect_success 'req_Root (base-path)' \
   'cat request-base | git-cvsserver --strict-paths --base-path "$WORKDIR/" pserver "$SERVERDIR" >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'req_Root failure (base-path)' '
     ! cat request-anonymous |
@@ -194,14 +194,14 @@ GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false || exit 1
 
 test_expect_success 'req_Root (export-all)' \
   'cat request-anonymous | git-cvsserver --export-all pserver "$WORKDIR" >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 test_expect_success 'req_Root failure (export-all w/o whitelist)' \
   '! (cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 || false)'
 
 test_expect_success 'req_Root (everything together)' \
   'cat request-base | git-cvsserver --export-all --strict-paths --base-path "$WORKDIR/" pserver "$SERVERDIR" >log 2>&1 &&
-   sed -ne \$p log | grep "^I LOVE YOU$"'
+   sed -ne \$p log | grep "^I LOVE YOU\$"'
 
 GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true || exit 1
 
index 40637d6782c3ad5ffbff99d352206b8b2ef01b43..ed7b513f3e82706f6455bb32f4f19a5161bb0415 100755 (executable)
@@ -11,14 +11,6 @@ repository using cvs CLI client via git-cvsserver server'
 
 . ./test-lib.sh
 
-q_to_nul () {
-    perl -pe 'y/Q/\000/'
-}
-
-q_to_cr () {
-    tr Q '\015'
-}
-
 marked_as () {
     foundEntry="$(grep "^/$2/" "$1/CVS/Entries")"
     if [ x"$foundEntry" = x"" ] ; then
index 0688a57e1d8cd252232095203af48013c2cbfc60..d196cc5ca93b07bcf20e4629455ea7f03b0907c7 100755 (executable)
@@ -33,7 +33,6 @@ test_expect_success \
     grep "403 - Snapshot format not allowed" gitweb.output &&
     gitweb_run "p=.git;a=snapshot;h=HEAD;sf=zip" &&
     grep "403 - Unsupported snapshot format" gitweb.output'
-test_debug 'cat gitweb.output'
 
 
 cat >>gitweb_config.perl <<\EOF
@@ -50,7 +49,6 @@ test_expect_success \
     grep "403 - Snapshot format not allowed" gitweb.output &&
     gitweb_run "p=.git;a=snapshot;h=HEAD;sf=zip" &&
     grep "Status: 200 OK" gitweb.output'
-test_debug 'cat gitweb.output'
 
 
 cat >>gitweb_config.perl <<\EOF
@@ -72,7 +70,7 @@ test_expect_success \
     'snapshots: tgz explicitly enabled' \
     'gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tgz" &&
     grep "Status: 200 OK" gitweb.output'
-test_debug 'cat gitweb.output'
+test_debug 'cat gitweb.headers'
 
 
 # ----------------------------------------------------------------------
@@ -82,7 +80,7 @@ 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_debug 'cat gitweb.headers'
 
 test_expect_success 'snapshots: bad tree-ish id' '
        gitweb_run "p=.git;a=snapshot;h=frizzumFrazzum;sf=tgz" &&
@@ -105,7 +103,7 @@ test_expect_success 'snapshots: good object id' '
        gitweb_run "p=.git;a=snapshot;h=$ID;sf=tgz" &&
        grep "Status: 200 OK" gitweb.output
 '
-test_debug 'cat gitweb.output'
+test_debug 'cat gitweb.headers'
 
 test_expect_success 'snapshots: bad object id' '
        gitweb_run "p=.git;a=snapshot;h=abcdef01234;sf=tgz" &&
@@ -114,4 +112,26 @@ test_expect_success 'snapshots: bad object id' '
 test_debug 'cat gitweb.output'
 
 
+# ----------------------------------------------------------------------
+# load checking
+
+# always hit the load limit
+cat >>gitweb_config.perl <<\EOF
+our $maxload = -1;
+EOF
+
+test_expect_success 'load checking: load too high (default action)' '
+       gitweb_run "p=.git" &&
+       grep "Status: 503 Service Unavailable" gitweb.headers &&
+       grep "503 - The load average on the server is too high" gitweb.body
+'
+test_debug 'cat gitweb.log' # just in case
+test_debug 'cat gitweb.headers'
+
+# turn off load checking
+cat >>gitweb_config.perl <<\EOF
+our $maxload = undef;
+EOF
+
+
 test_done
index ec3336aba5a65a468bc6ce71f33a9cca76dbfe0f..afd3053f96b789a73274217b384d245583500c04 100644 (file)
@@ -74,6 +74,12 @@ case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in
                ;;
 esac
 
+# Convenience
+#
+# A regexp to match 5 and 40 hexdigits
+_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"
+
 # Each test should start with something like this, after copyright notices:
 #
 # test_description='Description of this test...
@@ -105,6 +111,8 @@ do
                verbose=t; shift ;;
        -q|--q|--qu|--qui|--quie|--quiet)
                quiet=t; shift ;;
+       --with-dashes)
+               with_dashes=t; shift ;;
        --no-color)
                color=; shift ;;
        --no-python)
@@ -211,6 +219,33 @@ test_set_editor () {
        export EDITOR
 }
 
+test_decode_color () {
+       sed     -e 's/.\[1m/<WHITE>/g' \
+               -e 's/.\[31m/<RED>/g' \
+               -e 's/.\[32m/<GREEN>/g' \
+               -e 's/.\[33m/<YELLOW>/g' \
+               -e 's/.\[34m/<BLUE>/g' \
+               -e 's/.\[35m/<MAGENTA>/g' \
+               -e 's/.\[36m/<CYAN>/g' \
+               -e 's/.\[m/<RESET>/g'
+}
+
+q_to_nul () {
+       perl -pe 'y/Q/\000/'
+}
+
+q_to_cr () {
+       tr Q '\015'
+}
+
+append_cr () {
+       sed -e 's/$/Q/' | tr Q '\015'
+}
+
+remove_cr () {
+       tr '\015' Q | sed -e 's/Q$//'
+}
+
 test_tick () {
        if test -z "${test_tick+set}"
        then
@@ -551,19 +586,8 @@ test_done () {
 # Test the binaries we have just built.  The tests are kept in
 # t/ subdirectory and are run in 'trash directory' subdirectory.
 TEST_DIRECTORY=$(pwd)
-if test -z "$valgrind"
+if test -n "$valgrind"
 then
-       if test -z "$GIT_TEST_INSTALLED"
-       then
-               PATH=$TEST_DIRECTORY/..:$PATH
-               GIT_EXEC_PATH=$TEST_DIRECTORY/..
-       else
-               GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path)  ||
-               error "Cannot run git from $GIT_TEST_INSTALLED."
-               PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH
-               GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
-       fi
-else
        make_symlink () {
                test -h "$2" &&
                test "$1" = "$(readlink "$2")" || {
@@ -625,6 +649,24 @@ else
        PATH=$GIT_VALGRIND/bin:$PATH
        GIT_EXEC_PATH=$GIT_VALGRIND/bin
        export GIT_VALGRIND
+elif test -n "$GIT_TEST_INSTALLED" ; then
+       GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path)  ||
+       error "Cannot run git from $GIT_TEST_INSTALLED."
+       PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH
+       GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
+else # normal case, use ../bin-wrappers only unless $with_dashes:
+       git_bin_dir="$TEST_DIRECTORY/../bin-wrappers"
+       if ! test -x "$git_bin_dir/git" ; then
+               if test -z "$with_dashes" ; then
+                       say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH"
+               fi
+               with_dashes=t
+       fi
+       PATH="$git_bin_dir:$PATH"
+       GIT_EXEC_PATH=$TEST_DIRECTORY/..
+       if test -n "$with_dashes" ; then
+               PATH="$TEST_DIRECTORY/..:$PATH"
+       fi
 fi
 GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
 unset GIT_CONFIG
@@ -632,20 +674,29 @@ GIT_CONFIG_NOSYSTEM=1
 GIT_CONFIG_NOGLOBAL=1
 export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL
 
+. ../GIT-BUILD-OPTIONS
+
 GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
 export GITPERLLIB
 test -d ../templates/blt || {
        error "You haven't built things yet, have you?"
 }
 
+if test -z "$GIT_TEST_INSTALLED" && test -z "$NO_PYTHON"
+then
+       GITPYTHONLIB="$(pwd)/../git_remote_helpers/build/lib"
+       export GITPYTHONLIB
+       test -d ../git_remote_helpers/build || {
+               error "You haven't built git_remote_helpers yet, have you?"
+       }
+fi
+
 if ! test -x ../test-chmtime; then
        echo >&2 'You need to build test-chmtime:'
        echo >&2 'Run "make test-chmtime" in the source (toplevel) directory'
        exit 1
 fi
 
-. ../GIT-BUILD-OPTIONS
-
 # Test repository
 test="trash directory.$(basename "$0" .sh)"
 test -n "$root" && test="$root/$test"
@@ -729,6 +780,7 @@ case $(uname -s) in
 esac
 
 test -z "$NO_PERL" && test_set_prereq PERL
+test -z "$NO_PYTHON" && test_set_prereq PYTHON
 
 # test whether the filesystem supports symbolic links
 ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
diff --git a/test-index-version.c b/test-index-version.c
new file mode 100644 (file)
index 0000000..bfaad9e
--- /dev/null
@@ -0,0 +1,14 @@
+#include "cache.h"
+
+int main(int argc, const char **argv)
+{
+       struct cache_header hdr;
+       int version;
+
+       memset(&hdr,0,sizeof(hdr));
+       if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr))
+               return 0;
+       version = ntohl(hdr.hdr_version);
+       printf("%d\n", version);
+       return 0;
+}
diff --git a/test-run-command.c b/test-run-command.c
new file mode 100644 (file)
index 0000000..0612bfa
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * test-run-command.c: test run command API.
+ *
+ * (C) 2009 Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "git-compat-util.h"
+#include "run-command.h"
+#include <string.h>
+#include <errno.h>
+
+int main(int argc, char **argv)
+{
+       struct child_process proc;
+
+       memset(&proc, 0, sizeof(proc));
+
+       if (argc < 3)
+               return 1;
+       proc.argv = (const char **)argv+2;
+
+       if (!strcmp(argv[1], "start-command-ENOENT")) {
+               if (start_command(&proc) < 0 && errno == ENOENT)
+                       return 0;
+               fprintf(stderr, "FAIL %s\n", argv[1]);
+               return 1;
+       }
+
+       fprintf(stderr, "check usage\n");
+       return 1;
+}
index 5078c7100f16e9d7ac08aad05e3797ad5b6626bc..107742891f6e68d4b103f61a425a585770b0d155 100644 (file)
@@ -6,6 +6,9 @@
 #include "diff.h"
 #include "revision.h"
 #include "quote.h"
+#include "remote.h"
+
+static int debug;
 
 struct helper_data
 {
@@ -13,15 +16,93 @@ struct helper_data
        struct child_process *helper;
        FILE *out;
        unsigned fetch : 1,
+               import : 1,
                option : 1,
-               push : 1;
+               push : 1,
+               connect : 1,
+               no_disconnect_req : 1;
+       /* These go from remote name (as in "list") to private name */
+       struct refspec *refspecs;
+       int refspec_nr;
+       /* Transport options for fetch-pack/send-pack (should one of
+        * those be invoked).
+        */
+       struct git_transport_options transport_options;
 };
 
+static void sendline(struct helper_data *helper, struct strbuf *buffer)
+{
+       if (debug)
+               fprintf(stderr, "Debug: Remote helper: -> %s", buffer->buf);
+       if (write_in_full(helper->helper->in, buffer->buf, buffer->len)
+               != buffer->len)
+               die_errno("Full write to remote helper failed");
+}
+
+static int recvline_fh(FILE *helper, struct strbuf *buffer)
+{
+       strbuf_reset(buffer);
+       if (debug)
+               fprintf(stderr, "Debug: Remote helper: Waiting...\n");
+       if (strbuf_getline(buffer, helper, '\n') == EOF) {
+               if (debug)
+                       fprintf(stderr, "Debug: Remote helper quit.\n");
+               exit(128);
+       }
+
+       if (debug)
+               fprintf(stderr, "Debug: Remote helper: <- %s\n", buffer->buf);
+       return 0;
+}
+
+static int recvline(struct helper_data *helper, struct strbuf *buffer)
+{
+       return recvline_fh(helper->out, buffer);
+}
+
+static void xchgline(struct helper_data *helper, struct strbuf *buffer)
+{
+       sendline(helper, buffer);
+       recvline(helper, buffer);
+}
+
+static void write_constant(int fd, const char *str)
+{
+       if (debug)
+               fprintf(stderr, "Debug: Remote helper: -> %s", str);
+       if (write_in_full(fd, str, strlen(str)) != strlen(str))
+               die_errno("Full write to remote helper failed");
+}
+
+const char *remove_ext_force(const char *url)
+{
+       if (url) {
+               const char *colon = strchr(url, ':');
+               if (colon && colon[1] == ':')
+                       return colon + 2;
+       }
+       return url;
+}
+
+static void do_take_over(struct transport *transport)
+{
+       struct helper_data *data;
+       data = (struct helper_data *)transport->data;
+       transport_take_over(transport, data->helper);
+       fclose(data->out);
+       free(data);
+}
+
 static struct child_process *get_helper(struct transport *transport)
 {
        struct helper_data *data = transport->data;
        struct strbuf buf = STRBUF_INIT;
        struct child_process *helper;
+       const char **refspecs = NULL;
+       int refspec_nr = 0;
+       int refspec_alloc = 0;
+       int duped;
+       int code;
 
        if (data->helper)
                return data->helper;
@@ -31,40 +112,99 @@ static struct child_process *get_helper(struct transport *transport)
        helper->out = -1;
        helper->err = 0;
        helper->argv = xcalloc(4, sizeof(*helper->argv));
-       strbuf_addf(&buf, "remote-%s", data->name);
+       strbuf_addf(&buf, "git-remote-%s", data->name);
        helper->argv[0] = strbuf_detach(&buf, NULL);
        helper->argv[1] = transport->remote->name;
-       helper->argv[2] = transport->url;
-       helper->git_cmd = 1;
-       if (start_command(helper))
-               die("Unable to run helper: git %s", helper->argv[0]);
+       helper->argv[2] = remove_ext_force(transport->url);
+       helper->git_cmd = 0;
+       helper->silent_exec_failure = 1;
+       code = start_command(helper);
+       if (code < 0 && errno == ENOENT)
+               die("Unable to find remote helper for '%s'", data->name);
+       else if (code != 0)
+               exit(code);
+
        data->helper = helper;
+       data->no_disconnect_req = 0;
+
+       /*
+        * Open the output as FILE* so strbuf_getline() can be used.
+        * Do this with duped fd because fclose() will close the fd,
+        * and stuff like taking over will require the fd to remain.
+        */
+       duped = dup(helper->out);
+       if (duped < 0)
+               die_errno("Can't dup helper output fd");
+       data->out = xfdopen(duped, "r");
 
-       write_str_in_full(helper->in, "capabilities\n");
+       write_constant(helper->in, "capabilities\n");
 
-       data->out = xfdopen(helper->out, "r");
        while (1) {
-               if (strbuf_getline(&buf, data->out, '\n') == EOF)
-                       exit(128); /* child died, message supplied already */
+               const char *capname;
+               int mandatory = 0;
+               recvline(data, &buf);
 
                if (!*buf.buf)
                        break;
-               if (!strcmp(buf.buf, "fetch"))
+
+               if (*buf.buf == '*') {
+                       capname = buf.buf + 1;
+                       mandatory = 1;
+               } else
+                       capname = buf.buf;
+
+               if (debug)
+                       fprintf(stderr, "Debug: Got cap %s\n", capname);
+               if (!strcmp(capname, "fetch"))
                        data->fetch = 1;
-               if (!strcmp(buf.buf, "option"))
+               else if (!strcmp(capname, "option"))
                        data->option = 1;
-               if (!strcmp(buf.buf, "push"))
+               else if (!strcmp(capname, "push"))
                        data->push = 1;
+               else if (!strcmp(capname, "import"))
+                       data->import = 1;
+               else if (!data->refspecs && !prefixcmp(capname, "refspec ")) {
+                       ALLOC_GROW(refspecs,
+                                  refspec_nr + 1,
+                                  refspec_alloc);
+                       refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec "));
+               } else if (!strcmp(capname, "connect")) {
+                       data->connect = 1;
+               } else if (mandatory) {
+                       die("Unknown madatory capability %s. This remote "
+                           "helper probably needs newer version of Git.\n",
+                           capname);
+               }
+       }
+       if (refspecs) {
+               int i;
+               data->refspec_nr = refspec_nr;
+               data->refspecs = parse_fetch_refspec(refspec_nr, refspecs);
+               for (i = 0; i < refspec_nr; i++) {
+                       free((char *)refspecs[i]);
+               }
+               free(refspecs);
        }
+       strbuf_release(&buf);
+       if (debug)
+               fprintf(stderr, "Debug: Capabilities complete.\n");
        return data->helper;
 }
 
 static int disconnect_helper(struct transport *transport)
 {
        struct helper_data *data = transport->data;
+       struct strbuf buf = STRBUF_INIT;
+
        if (data->helper) {
-               write_str_in_full(data->helper->in, "\n");
+               if (debug)
+                       fprintf(stderr, "Debug: Disconnecting.\n");
+               if (!data->no_disconnect_req) {
+                       strbuf_addf(&buf, "\n");
+                       sendline(data, &buf);
+               }
                close(data->helper->in);
+               close(data->helper->out);
                fclose(data->out);
                finish_command(data->helper);
                free((char *)data->helper->argv[0]);
@@ -72,7 +212,6 @@ static int disconnect_helper(struct transport *transport)
                free(data->helper);
                data->helper = NULL;
        }
-       free(data);
        return 0;
 }
 
@@ -92,10 +231,11 @@ static int set_helper_option(struct transport *transport,
                          const char *name, const char *value)
 {
        struct helper_data *data = transport->data;
-       struct child_process *helper = get_helper(transport);
        struct strbuf buf = STRBUF_INIT;
        int i, ret, is_bool = 0;
 
+       get_helper(transport);
+
        if (!data->option)
                return 1;
 
@@ -118,12 +258,7 @@ static int set_helper_option(struct transport *transport,
                quote_c_style(value, &buf, NULL, 0);
        strbuf_addch(&buf, '\n');
 
-       if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
-               die_errno("cannot send option to %s", data->name);
-
-       strbuf_reset(&buf);
-       if (strbuf_getline(&buf, data->out, '\n') == EOF)
-               exit(128); /* child died, message supplied already */
+       xchgline(data, &buf);
 
        if (!strcmp(buf.buf, "ok"))
                ret = 0;
@@ -144,7 +279,7 @@ static void standard_options(struct transport *t)
        char buf[16];
        int n;
        int v = t->verbose;
-       int no_progress = v < 0 || (!t->progress && !isatty(1));
+       int no_progress = v < 0 || (!t->progress && !isatty(2));
 
        set_helper_option(t, "progress", !no_progress ? "true" : "false");
 
@@ -154,8 +289,18 @@ static void standard_options(struct transport *t)
        set_helper_option(t, "verbosity", buf);
 }
 
+static int release_helper(struct transport *transport)
+{
+       struct helper_data *data = transport->data;
+       free_refspec(data->refspec_nr, data->refspecs);
+       data->refspecs = NULL;
+       disconnect_helper(transport);
+       free(transport->data);
+       return 0;
+}
+
 static int fetch_with_fetch(struct transport *transport,
-                           int nr_heads, const struct ref **to_fetch)
+                           int nr_heads, struct ref **to_fetch)
 {
        struct helper_data *data = transport->data;
        int i;
@@ -173,13 +318,10 @@ static int fetch_with_fetch(struct transport *transport,
        }
 
        strbuf_addch(&buf, '\n');
-       if (write_in_full(data->helper->in, buf.buf, buf.len) != buf.len)
-               die_errno("cannot send fetch to %s", data->name);
+       sendline(data, &buf);
 
        while (1) {
-               strbuf_reset(&buf);
-               if (strbuf_getline(&buf, data->out, '\n') == EOF)
-                       exit(128); /* child died, message supplied already */
+               recvline(data, &buf);
 
                if (!prefixcmp(buf.buf, "lock ")) {
                        const char *name = buf.buf + 5;
@@ -197,12 +339,169 @@ static int fetch_with_fetch(struct transport *transport,
        return 0;
 }
 
+static int get_importer(struct transport *transport, struct child_process *fastimport)
+{
+       struct child_process *helper = get_helper(transport);
+       memset(fastimport, 0, sizeof(*fastimport));
+       fastimport->in = helper->out;
+       fastimport->argv = xcalloc(5, sizeof(*fastimport->argv));
+       fastimport->argv[0] = "fast-import";
+       fastimport->argv[1] = "--quiet";
+
+       fastimport->git_cmd = 1;
+       return start_command(fastimport);
+}
+
+static int fetch_with_import(struct transport *transport,
+                            int nr_heads, struct ref **to_fetch)
+{
+       struct child_process fastimport;
+       struct helper_data *data = transport->data;
+       int i;
+       struct ref *posn;
+       struct strbuf buf = STRBUF_INIT;
+
+       get_helper(transport);
+
+       if (get_importer(transport, &fastimport))
+               die("Couldn't run fast-import");
+
+       for (i = 0; i < nr_heads; i++) {
+               posn = to_fetch[i];
+               if (posn->status & REF_STATUS_UPTODATE)
+                       continue;
+
+               strbuf_addf(&buf, "import %s\n", posn->name);
+               sendline(data, &buf);
+               strbuf_reset(&buf);
+       }
+       disconnect_helper(transport);
+       finish_command(&fastimport);
+       free(fastimport.argv);
+       fastimport.argv = NULL;
+
+       for (i = 0; i < nr_heads; i++) {
+               char *private;
+               posn = to_fetch[i];
+               if (posn->status & REF_STATUS_UPTODATE)
+                       continue;
+               if (data->refspecs)
+                       private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name);
+               else
+                       private = strdup(posn->name);
+               read_ref(private, posn->old_sha1);
+               free(private);
+       }
+       strbuf_release(&buf);
+       return 0;
+}
+
+static int process_connect_service(struct transport *transport,
+                                  const char *name, const char *exec)
+{
+       struct helper_data *data = transport->data;
+       struct strbuf cmdbuf = STRBUF_INIT;
+       struct child_process *helper;
+       int r, duped, ret = 0;
+       FILE *input;
+
+       helper = get_helper(transport);
+
+       /*
+        * Yes, dup the pipe another time, as we need unbuffered version
+        * of input pipe as FILE*. fclose() closes the underlying fd and
+        * stream buffering only can be changed before first I/O operation
+        * on it.
+        */
+       duped = dup(helper->out);
+       if (duped < 0)
+               die_errno("Can't dup helper output fd");
+       input = xfdopen(duped, "r");
+       setvbuf(input, NULL, _IONBF, 0);
+
+       /*
+        * Handle --upload-pack and friends. This is fire and forget...
+        * just warn if it fails.
+        */
+       if (strcmp(name, exec)) {
+               r = set_helper_option(transport, "servpath", exec);
+               if (r > 0)
+                       warning("Setting remote service path not supported by protocol.");
+               else if (r < 0)
+                       warning("Invalid remote service path.");
+       }
+
+       if (data->connect)
+               strbuf_addf(&cmdbuf, "connect %s\n", name);
+       else
+               goto exit;
+
+       sendline(data, &cmdbuf);
+       recvline_fh(input, &cmdbuf);
+       if (!strcmp(cmdbuf.buf, "")) {
+               data->no_disconnect_req = 1;
+               if (debug)
+                       fprintf(stderr, "Debug: Smart transport connection "
+                               "ready.\n");
+               ret = 1;
+       } else if (!strcmp(cmdbuf.buf, "fallback")) {
+               if (debug)
+                       fprintf(stderr, "Debug: Falling back to dumb "
+                               "transport.\n");
+       } else
+               die("Unknown response to connect: %s",
+                       cmdbuf.buf);
+
+exit:
+       fclose(input);
+       return ret;
+}
+
+static int process_connect(struct transport *transport,
+                                    int for_push)
+{
+       struct helper_data *data = transport->data;
+       const char *name;
+       const char *exec;
+
+       name = for_push ? "git-receive-pack" : "git-upload-pack";
+       if (for_push)
+               exec = data->transport_options.receivepack;
+       else
+               exec = data->transport_options.uploadpack;
+
+       return process_connect_service(transport, name, exec);
+}
+
+static int connect_helper(struct transport *transport, const char *name,
+                  const char *exec, int fd[2])
+{
+       struct helper_data *data = transport->data;
+
+       /* Get_helper so connect is inited. */
+       get_helper(transport);
+       if (!data->connect)
+               die("Operation not supported by protocol.");
+
+       if (!process_connect_service(transport, name, exec))
+               die("Can't connect to subservice %s.", name);
+
+       fd[0] = data->helper->out;
+       fd[1] = data->helper->in;
+       return 0;
+}
+
 static int fetch(struct transport *transport,
-                int nr_heads, const struct ref **to_fetch)
+                int nr_heads, struct ref **to_fetch)
 {
        struct helper_data *data = transport->data;
        int i, count;
 
+       if (process_connect(transport, 0)) {
+               do_take_over(transport);
+               return transport->fetch(transport, nr_heads, to_fetch);
+       }
+
        count = 0;
        for (i = 0; i < nr_heads; i++)
                if (!(to_fetch[i]->status & REF_STATUS_UPTODATE))
@@ -214,6 +513,9 @@ static int fetch(struct transport *transport,
        if (data->fetch)
                return fetch_with_fetch(transport, nr_heads, to_fetch);
 
+       if (data->import)
+               return fetch_with_import(transport, nr_heads, to_fetch);
+
        return -1;
 }
 
@@ -227,24 +529,32 @@ static int push_refs(struct transport *transport,
        struct child_process *helper;
        struct ref *ref;
 
-       if (!remote_refs)
+       if (process_connect(transport, 1)) {
+               do_take_over(transport);
+               return transport->push_refs(transport, remote_refs, flags);
+       }
+
+       if (!remote_refs) {
+               fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
+                       "Perhaps you should specify a branch such as 'master'.\n");
                return 0;
+       }
 
        helper = get_helper(transport);
        if (!data->push)
                return 1;
 
        for (ref = remote_refs; ref; ref = ref->next) {
-               if (ref->peer_ref)
-                       hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-               else if (!mirror)
+               if (!ref->peer_ref && !mirror)
                        continue;
 
-               ref->deletion = is_null_sha1(ref->new_sha1);
-               if (!ref->deletion &&
-                       !hashcmp(ref->old_sha1, ref->new_sha1)) {
-                       ref->status = REF_STATUS_UPTODATE;
+               /* Check for statuses set by set_ref_status_for_push() */
+               switch (ref->status) {
+               case REF_STATUS_REJECT_NONFASTFORWARD:
+               case REF_STATUS_UPTODATE:
                        continue;
+               default:
+                       ; /* do nothing */
                }
 
                if (force_all)
@@ -275,17 +585,14 @@ static int push_refs(struct transport *transport,
        }
 
        strbuf_addch(&buf, '\n');
-       if (write_in_full(helper->in, buf.buf, buf.len) != buf.len)
-               exit(128);
+       sendline(data, &buf);
 
        ref = remote_refs;
        while (1) {
                char *refname, *msg;
                int status;
 
-               strbuf_reset(&buf);
-               if (strbuf_getline(&buf, data->out, '\n') == EOF)
-                       exit(128); /* child died, message supplied already */
+               recvline(data, &buf);
                if (!buf.len)
                        break;
 
@@ -336,6 +643,15 @@ static int push_refs(struct transport *transport,
                        continue;
                }
 
+               if (ref->status != REF_STATUS_NONE) {
+                       /*
+                        * Earlier, the ref was marked not to be pushed, so ignore the ref
+                        * status reported by the remote helper if the latter is 'no match'.
+                        */
+                       if (status == REF_STATUS_NONE)
+                               continue;
+               }
+
                ref->status = status;
                ref->remote_status = msg;
        }
@@ -343,6 +659,22 @@ static int push_refs(struct transport *transport,
        return 0;
 }
 
+static int has_attribute(const char *attrs, const char *attr) {
+       int len;
+       if (!attrs)
+               return 0;
+
+       len = strlen(attr);
+       for (;;) {
+               const char *space = strchrnul(attrs, ' ');
+               if (len == space - attrs && !strncmp(attrs, attr, len))
+                       return 1;
+               if (!*space)
+                       return 0;
+               attrs = space + 1;
+       }
+}
+
 static struct ref *get_refs_list(struct transport *transport, int for_push)
 {
        struct helper_data *data = transport->data;
@@ -354,6 +686,11 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
 
        helper = get_helper(transport);
 
+       if (process_connect(transport, for_push)) {
+               do_take_over(transport);
+               return transport->get_refs_list(transport, for_push);
+       }
+
        if (data->push && for_push)
                write_str_in_full(helper->in, "list for-push\n");
        else
@@ -361,8 +698,7 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
 
        while (1) {
                char *eov, *eon;
-               if (strbuf_getline(&buf, data->out, '\n') == EOF)
-                       exit(128); /* child died, message supplied already */
+               recvline(data, &buf);
 
                if (!*buf.buf)
                        break;
@@ -379,8 +715,16 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
                        (*tail)->symref = xstrdup(buf.buf + 1);
                else if (buf.buf[0] != '?')
                        get_sha1_hex(buf.buf, (*tail)->old_sha1);
+               if (eon) {
+                       if (has_attribute(eon + 1, "unchanged")) {
+                               (*tail)->status |= REF_STATUS_UPTODATE;
+                               read_ref((*tail)->name, (*tail)->old_sha1);
+                       }
+               }
                tail = &((*tail)->next);
        }
+       if (debug)
+               fprintf(stderr, "Debug: Read ref listing.\n");
        strbuf_release(&buf);
 
        for (posn = ret; posn; posn = posn->next)
@@ -394,11 +738,16 @@ int transport_helper_init(struct transport *transport, const char *name)
        struct helper_data *data = xcalloc(sizeof(*data), 1);
        data->name = name;
 
+       if (getenv("GIT_TRANSPORT_HELPER_DEBUG"))
+               debug = 1;
+
        transport->data = data;
        transport->set_option = set_helper_option;
        transport->get_refs_list = get_refs_list;
        transport->fetch = fetch;
        transport->push_refs = push_refs;
-       transport->disconnect = disconnect_helper;
+       transport->disconnect = release_helper;
+       transport->connect = connect_helper;
+       transport->smart_options = &(data->transport_options);
        return 0;
 }
index 42b2c59a7bdf68464ea508a75dc02fe75517bcce..3846aacb476b552cefddd8774b9a055e353b6ebc 100644 (file)
@@ -8,6 +8,7 @@
 #include "bundle.h"
 #include "dir.h"
 #include "refs.h"
+#include "branch.h"
 
 /* rsync support */
 
@@ -135,6 +136,53 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list)
        }
 }
 
+static void set_upstreams(struct transport *transport, struct ref *refs,
+       int pretend)
+{
+       struct ref *ref;
+       for (ref = refs; ref; ref = ref->next) {
+               const char *localname;
+               const char *tmp;
+               const char *remotename;
+               unsigned char sha[20];
+               int flag = 0;
+               /*
+                * Check suitability for tracking. Must be successful /
+                * already up-to-date ref create/modify (not delete).
+                */
+               if (ref->status != REF_STATUS_OK &&
+                       ref->status != REF_STATUS_UPTODATE)
+                       continue;
+               if (!ref->peer_ref)
+                       continue;
+               if (!ref->new_sha1 || is_null_sha1(ref->new_sha1))
+                       continue;
+
+               /* Follow symbolic refs (mainly for HEAD). */
+               localname = ref->peer_ref->name;
+               remotename = ref->name;
+               tmp = resolve_ref(localname, sha, 1, &flag);
+               if (tmp && flag & REF_ISSYMREF &&
+                       !prefixcmp(tmp, "refs/heads/"))
+                       localname = tmp;
+
+               /* Both source and destination must be local branches. */
+               if (!localname || prefixcmp(localname, "refs/heads/"))
+                       continue;
+               if (!remotename || prefixcmp(remotename, "refs/heads/"))
+                       continue;
+
+               if (!pretend)
+                       install_branch_config(BRANCH_CONFIG_VERBOSE,
+                               localname + 11, transport->remote->name,
+                               remotename);
+               else
+                       printf("Would set upstream of '%s' to '%s' of '%s'\n",
+                               localname + 11, remotename + 11,
+                               transport->remote->name);
+       }
+}
+
 static const char *rsync_url(const char *url)
 {
        return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url;
@@ -204,7 +252,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
 }
 
 static int fetch_objs_via_rsync(struct transport *transport,
-                               int nr_objs, const struct ref **to_fetch)
+                               int nr_objs, struct ref **to_fetch)
 {
        struct strbuf buf = STRBUF_INIT;
        struct child_process rsync;
@@ -379,7 +427,7 @@ static struct ref *get_refs_from_bundle(struct transport *transport, int for_pus
 }
 
 static int fetch_refs_from_bundle(struct transport *transport,
-                              int nr_heads, const struct ref **to_fetch)
+                              int nr_heads, struct ref **to_fetch)
 {
        struct bundle_transport_data *data = transport->data;
        return unbundle(&data->header, data->fd);
@@ -395,41 +443,36 @@ static int close_bundle(struct transport *transport)
 }
 
 struct git_transport_data {
-       unsigned thin : 1;
-       unsigned keep : 1;
-       unsigned followtags : 1;
-       int depth;
+       struct git_transport_options options;
        struct child_process *conn;
        int fd[2];
-       const char *uploadpack;
-       const char *receivepack;
+       unsigned got_remote_heads : 1;
        struct extra_have_objects extra_have;
 };
 
-static int set_git_option(struct transport *connection,
+static int set_git_option(struct git_transport_options *opts,
                          const char *name, const char *value)
 {
-       struct git_transport_data *data = connection->data;
        if (!strcmp(name, TRANS_OPT_UPLOADPACK)) {
-               data->uploadpack = value;
+               opts->uploadpack = value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) {
-               data->receivepack = value;
+               opts->receivepack = value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_THIN)) {
-               data->thin = !!value;
+               opts->thin = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_FOLLOWTAGS)) {
-               data->followtags = !!value;
+               opts->followtags = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_KEEP)) {
-               data->keep = !!value;
+               opts->keep = !!value;
                return 0;
        } else if (!strcmp(name, TRANS_OPT_DEPTH)) {
                if (!value)
-                       data->depth = 0;
+                       opts->depth = 0;
                else
-                       data->depth = atoi(value);
+                       opts->depth = atoi(value);
                return 0;
        }
        return 1;
@@ -438,9 +481,15 @@ static int set_git_option(struct transport *connection,
 static int connect_setup(struct transport *transport, int for_push, int verbose)
 {
        struct git_transport_data *data = transport->data;
+
+       if (data->conn)
+               return 0;
+
        data->conn = git_connect(data->fd, transport->url,
-                                for_push ? data->receivepack : data->uploadpack,
+                                for_push ? data->options.receivepack :
+                                data->options.uploadpack,
                                 verbose ? CONNECT_VERBOSE : 0);
+
        return 0;
 }
 
@@ -452,12 +501,13 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
        connect_setup(transport, for_push, 0);
        get_remote_heads(data->fd[0], &refs, 0, NULL,
                         for_push ? REF_NORMAL : 0, &data->extra_have);
+       data->got_remote_heads = 1;
 
        return refs;
 }
 
 static int fetch_refs_via_pack(struct transport *transport,
-                              int nr_heads, const struct ref **to_fetch)
+                              int nr_heads, struct ref **to_fetch)
 {
        struct git_transport_data *data = transport->data;
        char **heads = xmalloc(nr_heads * sizeof(*heads));
@@ -469,22 +519,23 @@ static int fetch_refs_via_pack(struct transport *transport,
        struct ref *refs_tmp = NULL;
 
        memset(&args, 0, sizeof(args));
-       args.uploadpack = data->uploadpack;
-       args.keep_pack = data->keep;
+       args.uploadpack = data->options.uploadpack;
+       args.keep_pack = data->options.keep;
        args.lock_pack = 1;
-       args.use_thin_pack = data->thin;
-       args.include_tag = data->followtags;
+       args.use_thin_pack = data->options.thin;
+       args.include_tag = data->options.followtags;
        args.verbose = (transport->verbose > 0);
        args.quiet = (transport->verbose < 0);
-       args.no_progress = args.quiet || (!transport->progress && !isatty(1));
-       args.depth = data->depth;
+       args.no_progress = args.quiet || (!transport->progress && !isatty(2));
+       args.depth = data->options.depth;
 
        for (i = 0; i < nr_heads; i++)
                origh[i] = heads[i] = xstrdup(to_fetch[i]->name);
 
-       if (!data->conn) {
+       if (!data->got_remote_heads) {
                connect_setup(transport, 0, 0);
                get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);
+               data->got_remote_heads = 1;
        }
 
        refs = fetch_pack(&args, data->fd, data->conn,
@@ -495,6 +546,7 @@ static int fetch_refs_via_pack(struct transport *transport,
        if (finish_connect(data->conn))
                refs = NULL;
        data->conn = NULL;
+       data->got_remote_heads = 0;
 
        free_refs(refs_tmp);
 
@@ -723,18 +775,19 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
        struct send_pack_args args;
        int ret;
 
-       if (!data->conn) {
+       if (!data->got_remote_heads) {
                struct ref *tmp_refs;
                connect_setup(transport, 1, 0);
 
                get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL,
                                 NULL);
+               data->got_remote_heads = 1;
        }
 
        memset(&args, 0, sizeof(args));
        args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
        args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
-       args.use_thin_pack = data->thin;
+       args.use_thin_pack = data->options.thin;
        args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
        args.quiet = !!(flags & TRANSPORT_PUSH_QUIET);
        args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
@@ -746,15 +799,28 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
        close(data->fd[0]);
        ret |= finish_connect(data->conn);
        data->conn = NULL;
+       data->got_remote_heads = 0;
 
        return ret;
 }
 
+static int connect_git(struct transport *transport, const char *name,
+                      const char *executable, int fd[2])
+{
+       struct git_transport_data *data = transport->data;
+       data->conn = git_connect(data->fd, transport->url,
+                                executable, 0);
+       fd[0] = data->fd[0];
+       fd[1] = data->fd[1];
+       return 0;
+}
+
 static int disconnect_git(struct transport *transport)
 {
        struct git_transport_data *data = transport->data;
        if (data->conn) {
-               packet_flush(data->fd[1]);
+               if (data->got_remote_heads)
+                       packet_flush(data->fd[1]);
                close(data->fd[0]);
                close(data->fd[1]);
                finish_connect(data->conn);
@@ -764,6 +830,32 @@ static int disconnect_git(struct transport *transport)
        return 0;
 }
 
+void transport_take_over(struct transport *transport,
+                        struct child_process *child)
+{
+       struct git_transport_data *data;
+
+       if (!transport->smart_options)
+               die("Bug detected: Taking over transport requires non-NULL "
+                   "smart_options field.");
+
+       data = xcalloc(1, sizeof(*data));
+       data->options = *transport->smart_options;
+       data->conn = child;
+       data->fd[0] = data->conn->out;
+       data->fd[1] = data->conn->in;
+       data->got_remote_heads = 0;
+       transport->data = data;
+
+       transport->set_option = NULL;
+       transport->get_refs_list = get_refs_via_connect;
+       transport->fetch = fetch_refs_via_pack;
+       transport->push = NULL;
+       transport->push_refs = git_transport_push;
+       transport->disconnect = disconnect_git;
+       transport->smart_options = &(data->options);
+}
+
 static int is_local(const char *url)
 {
        const char *colon = strchr(url, ':');
@@ -780,53 +872,119 @@ static int is_file(const char *url)
        return S_ISREG(buf.st_mode);
 }
 
+static int is_url(const char *url)
+{
+       const char *url2, *first_slash;
+
+       if (!url)
+               return 0;
+       url2 = url;
+       first_slash = strchr(url, '/');
+
+       /* Input with no slash at all or slash first can't be URL. */
+       if (!first_slash || first_slash == url)
+               return 0;
+       /* Character before must be : and next must be /. */
+       if (first_slash[-1] != ':' || first_slash[1] != '/')
+               return 0;
+       /* There must be something before the :// */
+       if (first_slash == url + 1)
+               return 0;
+       /*
+        * Check all characters up to first slash - 1. Only alphanum
+        * is allowed.
+        */
+       url2 = url;
+       while (url2 < first_slash - 1) {
+               if (!isalnum((unsigned char)*url2))
+                       return 0;
+               url2++;
+       }
+
+       /* Valid enough. */
+       return 1;
+}
+
+static int external_specification_len(const char *url)
+{
+       return strchr(url, ':') - url;
+}
+
 struct transport *transport_get(struct remote *remote, const char *url)
 {
+       const char *helper;
        struct transport *ret = xcalloc(1, sizeof(*ret));
 
        if (!remote)
                die("No remote provided to transport_get()");
 
        ret->remote = remote;
+       helper = remote->foreign_vcs;
+
+       if (!url && remote->url)
+               url = remote->url[0];
        ret->url = url;
 
-       if (!prefixcmp(url, "rsync:")) {
+       /* maybe it is a foreign URL? */
+       if (url) {
+               const char *p = url;
+
+               while (isalnum(*p))
+                       p++;
+               if (!prefixcmp(p, "::"))
+                       helper = xstrndup(url, p - url);
+       }
+
+       if (helper) {
+               transport_helper_init(ret, helper);
+       } else if (!prefixcmp(url, "rsync:")) {
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
                ret->push = rsync_transport_push;
-
-       } else if (!prefixcmp(url, "http://")
-               || !prefixcmp(url, "https://")
-               || !prefixcmp(url, "ftp://")) {
-               transport_helper_init(ret, "curl");
-#ifdef NO_CURL
-               error("git was compiled without libcurl support.");
-#endif
-
+               ret->smart_options = NULL;
        } else if (is_local(url) && is_file(url)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
                ret->get_refs_list = get_refs_from_bundle;
                ret->fetch = fetch_refs_from_bundle;
                ret->disconnect = close_bundle;
-
-       } else {
+               ret->smart_options = NULL;
+       } else if (!is_url(url)
+               || !prefixcmp(url, "file://")
+               || !prefixcmp(url, "git://")
+               || !prefixcmp(url, "ssh://")
+               || !prefixcmp(url, "git+ssh://")
+               || !prefixcmp(url, "ssh+git://")) {
+               /* These are builtin smart transports. */
                struct git_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
-               ret->set_option = set_git_option;
+               ret->set_option = NULL;
                ret->get_refs_list = get_refs_via_connect;
                ret->fetch = fetch_refs_via_pack;
                ret->push_refs = git_transport_push;
+               ret->connect = connect_git;
                ret->disconnect = disconnect_git;
+               ret->smart_options = &(data->options);
 
-               data->thin = 1;
                data->conn = NULL;
-               data->uploadpack = "git-upload-pack";
+               data->got_remote_heads = 0;
+       } else {
+               /* Unknown protocol in URL. Pass to external handler. */
+               int len = external_specification_len(url);
+               char *handler = xmalloc(len + 1);
+               handler[len] = 0;
+               strncpy(handler, url, len);
+               transport_helper_init(ret, handler);
+       }
+
+       if (ret->smart_options) {
+               ret->smart_options->thin = 1;
+               ret->smart_options->uploadpack = "git-upload-pack";
                if (remote->uploadpack)
-                       data->uploadpack = remote->uploadpack;
-               data->receivepack = "git-receive-pack";
+                       ret->smart_options->uploadpack = remote->uploadpack;
+               ret->smart_options->receivepack = "git-receive-pack";
                if (remote->receivepack)
-                       data->receivepack = remote->receivepack;
+                       ret->smart_options->receivepack = remote->receivepack;
        }
 
        return ret;
@@ -835,8 +993,23 @@ struct transport *transport_get(struct remote *remote, const char *url)
 int transport_set_option(struct transport *transport,
                         const char *name, const char *value)
 {
+       int git_reports = 1, protocol_reports = 1;
+
+       if (transport->smart_options)
+               git_reports = set_git_option(transport->smart_options,
+                                            name, value);
+
        if (transport->set_option)
-               return transport->set_option(transport, name, value);
+               protocol_reports = transport->set_option(transport, name,
+                                                       value);
+
+       /* If either report is 0, report 0 (success). */
+       if (!git_reports || !protocol_reports)
+               return 0;
+       /* If either reports -1 (invalid value), report -1. */
+       if ((git_reports == -1) || (protocol_reports == -1))
+               return -1;
+       /* Otherwise if both report unknown, report unknown. */
        return 1;
 }
 
@@ -847,9 +1020,13 @@ int transport_push(struct transport *transport,
        *nonfastforward = 0;
        verify_remote_names(refspec_nr, refspec);
 
-       if (transport->push)
+       if (transport->push) {
+               /* Maybe FIXME. But no important transport uses this case. */
+               if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
+                       die("This transport does not support using --set-upstream");
+
                return transport->push(transport, refspec_nr, refspec, flags);
-       if (transport->push_refs) {
+       } else if (transport->push_refs) {
                struct ref *remote_refs =
                        transport->get_refs_list(transport, 1);
                struct ref *local_refs = get_local_heads();
@@ -857,7 +1034,8 @@ int transport_push(struct transport *transport,
                int verbose = flags & TRANSPORT_PUSH_VERBOSE;
                int quiet = flags & TRANSPORT_PUSH_QUIET;
                int porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
-               int ret;
+               int pretend = flags & TRANSPORT_PUSH_DRY_RUN;
+               int ret, err;
 
                if (flags & TRANSPORT_PUSH_ALL)
                        match_flags |= MATCH_REFS_ALL;
@@ -869,13 +1047,23 @@ int transport_push(struct transport *transport,
                        return -1;
                }
 
+               set_ref_status_for_push(remote_refs,
+                       flags & TRANSPORT_PUSH_MIRROR,
+                       flags & TRANSPORT_PUSH_FORCE);
+
                ret = transport->push_refs(transport, remote_refs, flags);
+               err = push_had_errors(remote_refs);
+
+               ret |= err;
 
-               if (!quiet || push_had_errors(remote_refs))
+               if (!quiet || err)
                        print_push_status(transport->url, remote_refs,
                                        verbose | porcelain, porcelain,
                                        nonfastforward);
 
+               if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
+                       set_upstreams(transport, remote_refs, pretend);
+
                if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
                        struct ref *ref;
                        for (ref = remote_refs; ref; ref = ref->next)
@@ -893,19 +1081,21 @@ const struct ref *transport_get_remote_refs(struct transport *transport)
 {
        if (!transport->remote_refs)
                transport->remote_refs = transport->get_refs_list(transport, 0);
+
        return transport->remote_refs;
 }
 
-int transport_fetch_refs(struct transport *transport, const struct ref *refs)
+int transport_fetch_refs(struct transport *transport, struct ref *refs)
 {
        int rc;
        int nr_heads = 0, nr_alloc = 0, nr_refs = 0;
-       const struct ref **heads = NULL;
-       const struct ref *rm;
+       struct ref **heads = NULL;
+       struct ref *rm;
 
        for (rm = refs; rm; rm = rm->next) {
                nr_refs++;
                if (rm->peer_ref &&
+                   !is_null_sha1(rm->old_sha1) &&
                    !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1))
                        continue;
                ALLOC_GROW(heads, nr_heads + 1, nr_alloc);
@@ -926,6 +1116,7 @@ int transport_fetch_refs(struct transport *transport, const struct ref *refs)
        }
 
        rc = transport->fetch(transport, nr_heads, heads);
+
        free(heads);
        return rc;
 }
@@ -939,6 +1130,15 @@ void transport_unlock_pack(struct transport *transport)
        }
 }
 
+int transport_connect(struct transport *transport, const char *name,
+                     const char *exec, int fd[2])
+{
+       if (transport->connect)
+               return transport->connect(transport, name, exec, fd);
+       else
+               die("Operation not supported by protocol");
+}
+
 int transport_disconnect(struct transport *transport)
 {
        int ret = 0;
index e4e6177e2632b4a764703c11ef0a8033eafea037..7cea5cc7234185b1c37ada818dfa1a9114621ad2 100644 (file)
@@ -4,6 +4,15 @@
 #include "cache.h"
 #include "remote.h"
 
+struct git_transport_options {
+       unsigned thin : 1;
+       unsigned keep : 1;
+       unsigned followtags : 1;
+       int depth;
+       const char *uploadpack;
+       const char *receivepack;
+};
+
 struct transport {
        struct remote *remote;
        const char *url;
@@ -18,16 +27,61 @@ struct transport {
        int (*set_option)(struct transport *connection, const char *name,
                          const char *value);
 
+       /**
+        * Returns a list of the remote side's refs. In order to allow
+        * the transport to try to share connections, for_push is a
+        * hint as to whether the ultimate operation is a push or a fetch.
+        *
+        * If the transport is able to determine the remote hash for
+        * the ref without a huge amount of effort, it should store it
+        * in the ref's old_sha1 field; otherwise it should be all 0.
+        **/
        struct ref *(*get_refs_list)(struct transport *transport, int for_push);
-       int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs);
+
+       /**
+        * Fetch the objects for the given refs. Note that this gets
+        * an array, and should ignore the list structure.
+        *
+        * If the transport did not get hashes for refs in
+        * get_refs_list(), it should set the old_sha1 fields in the
+        * provided refs now.
+        **/
+       int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
+
+       /**
+        * Push the objects and refs. Send the necessary objects, and
+        * then, for any refs where peer_ref is set and
+        * peer_ref->new_sha1 is different from old_sha1, tell the
+        * remote side to update each ref in the list from old_sha1 to
+        * peer_ref->new_sha1.
+        *
+        * Where possible, set the status for each ref appropriately.
+        *
+        * The transport must modify new_sha1 in the ref to the new
+        * value if the remote accepted the change. Note that this
+        * could be a different value from peer_ref->new_sha1 if the
+        * process involved generating new commits.
+        **/
        int (*push_refs)(struct transport *transport, struct ref *refs, int flags);
        int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
+       int (*connect)(struct transport *connection, const char *name,
+                      const char *executable, int fd[2]);
 
+       /** get_refs_list(), fetch(), and push_refs() can keep
+        * resources (such as a connection) reserved for futher
+        * use. disconnect() releases these resources.
+        **/
        int (*disconnect)(struct transport *connection);
        char *pack_lockfile;
        signed verbose : 3;
-       /* Force progress even if the output is not a tty */
+       /* Force progress even if stderr is not a tty */
        unsigned progress : 1;
+       /*
+        * If transport is at least potentially smart, this points to
+        * git_transport_options structure to use in case transport
+        * actually turns out to be smart.
+        */
+       struct git_transport_options *smart_options;
 };
 
 #define TRANSPORT_PUSH_ALL 1
@@ -37,6 +91,7 @@ struct transport {
 #define TRANSPORT_PUSH_VERBOSE 16
 #define TRANSPORT_PUSH_PORCELAIN 32
 #define TRANSPORT_PUSH_QUIET 64
+#define TRANSPORT_PUSH_SET_UPSTREAM 128
 
 /* Returns a transport suitable for the url */
 struct transport *transport_get(struct remote *, const char *);
@@ -74,10 +129,15 @@ int transport_push(struct transport *connection,
 
 const struct ref *transport_get_remote_refs(struct transport *transport);
 
-int transport_fetch_refs(struct transport *transport, const struct ref *refs);
+int transport_fetch_refs(struct transport *transport, struct ref *refs);
 void transport_unlock_pack(struct transport *transport);
 int transport_disconnect(struct transport *transport);
 char *transport_anonymize_url(const char *url);
+void transport_take_over(struct transport *transport,
+                        struct child_process *child);
+
+int transport_connect(struct transport *transport, const char *name,
+                     const char *exec, int fd[2]);
 
 /* Transport methods defined outside transport.c */
 int transport_helper_init(struct transport *transport, const char *name);
index 0459e54d3d89a413330f52ab34662f51924b04ea..fe9f52c4796512f40869e511b55a2d97fa84532e 100644 (file)
@@ -68,7 +68,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
                if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
                        newbase[baselen + pathlen1] = 0;
                        opt->change(opt, mode1, mode2,
-                                   sha1, sha2, newbase);
+                                   sha1, sha2, newbase, 0, 0);
                        newbase[baselen + pathlen1] = '/';
                }
                retval = diff_tree_sha1(sha1, sha2, newbase, opt);
@@ -77,7 +77,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
        }
 
        fullname = malloc_fullname(base, baselen, path1, pathlen1);
-       opt->change(opt, mode1, mode2, sha1, sha2, fullname);
+       opt->change(opt, mode1, mode2, sha1, sha2, fullname, 0, 0);
        free(fullname);
        return 0;
 }
@@ -241,7 +241,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
 
                if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
                        newbase[baselen + pathlen] = 0;
-                       opt->add_remove(opt, *prefix, mode, sha1, newbase);
+                       opt->add_remove(opt, *prefix, mode, sha1, newbase, 0);
                        newbase[baselen + pathlen] = '/';
                }
 
@@ -252,7 +252,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
                free(newbase);
        } else {
                char *fullname = malloc_fullname(base, baselen, path, pathlen);
-               opt->add_remove(opt, prefix[0], mode, sha1, fullname);
+               opt->add_remove(opt, prefix[0], mode, sha1, fullname, 0);
                free(fullname);
        }
 }
@@ -286,7 +286,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru
        int baselen = strlen(base);
 
        for (;;) {
-               if (DIFF_OPT_TST(opt, QUIET) && DIFF_OPT_TST(opt, HAS_CHANGES))
+               if (DIFF_OPT_TST(opt, QUICK) &&
+                   DIFF_OPT_TST(opt, HAS_CHANGES))
                        break;
                if (opt->nr_paths) {
                        skip_uninteresting(t1, base, baselen, opt);
index 02e2aed7737207225f1b96eed774a1b75dd6d8d9..08796c23228fbfb6eb255c479f0196768ee83b27 100644 (file)
@@ -60,13 +60,6 @@ void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1)
        return buf;
 }
 
-static int entry_compare(struct name_entry *a, struct name_entry *b)
-{
-       return df_name_compare(
-                       a->path, tree_entry_len(a->path, a->sha1), a->mode,
-                       b->path, tree_entry_len(b->path, b->sha1), b->mode);
-}
-
 static void entry_clear(struct name_entry *a)
 {
        memset(a, 0, sizeof(*a));
@@ -138,66 +131,264 @@ char *make_traverse_path(char *path, const struct traverse_info *info, const str
        return path;
 }
 
+struct tree_desc_skip {
+       struct tree_desc_skip *prev;
+       const void *ptr;
+};
+
+struct tree_desc_x {
+       struct tree_desc d;
+       struct tree_desc_skip *skip;
+};
+
+static int name_compare(const char *a, int a_len,
+                       const char *b, int b_len)
+{
+       int len = (a_len < b_len) ? a_len : b_len;
+       int cmp = memcmp(a, b, len);
+       if (cmp)
+               return cmp;
+       return (a_len - b_len);
+}
+
+static int check_entry_match(const char *a, int a_len, const char *b, int b_len)
+{
+       /*
+        * The caller wants to pick *a* from a tree or nothing.
+        * We are looking at *b* in a tree.
+        *
+        * (0) If a and b are the same name, we are trivially happy.
+        *
+        * There are three possibilities where *a* could be hiding
+        * behind *b*.
+        *
+        * (1) *a* == "t",   *b* == "ab"  i.e. *b* sorts earlier than *a* no
+        *                                matter what.
+        * (2) *a* == "t",   *b* == "t-2" and "t" is a subtree in the tree;
+        * (3) *a* == "t-2", *b* == "t"   and "t-2" is a blob in the tree.
+        *
+        * Otherwise we know *a* won't appear in the tree without
+        * scanning further.
+        */
+
+       int cmp = name_compare(a, a_len, b, b_len);
+
+       /* Most common case first -- reading sync'd trees */
+       if (!cmp)
+               return cmp;
+
+       if (0 < cmp) {
+               /* a comes after b; it does not matter if it is case (3)
+               if (b_len < a_len && !memcmp(a, b, b_len) && a[b_len] < '/')
+                       return 1;
+               */
+               return 1; /* keep looking */
+       }
+
+       /* b comes after a; are we looking at case (2)? */
+       if (a_len < b_len && !memcmp(a, b, a_len) && b[a_len] < '/')
+               return 1; /* keep looking */
+
+       return -1; /* a cannot appear in the tree */
+}
+
+/*
+ * From the extended tree_desc, extract the first name entry, while
+ * paying attention to the candidate "first" name.  Most importantly,
+ * when looking for an entry, if there are entries that sorts earlier
+ * in the tree object representation than that name, skip them and
+ * process the named entry first.  We will remember that we haven't
+ * processed the first entry yet, and in the later call skip the
+ * entry we processed early when update_extended_entry() is called.
+ *
+ * E.g. if the underlying tree object has these entries:
+ *
+ *    blob    "t-1"
+ *    blob    "t-2"
+ *    tree    "t"
+ *    blob    "t=1"
+ *
+ * and the "first" asks for "t", remember that we still need to
+ * process "t-1" and "t-2" but extract "t".  After processing the
+ * entry "t" from this call, the caller will let us know by calling
+ * update_extended_entry() that we can remember "t" has been processed
+ * already.
+ */
+
+static void extended_entry_extract(struct tree_desc_x *t,
+                                  struct name_entry *a,
+                                  const char *first,
+                                  int first_len)
+{
+       const char *path;
+       int len;
+       struct tree_desc probe;
+       struct tree_desc_skip *skip;
+
+       /*
+        * Extract the first entry from the tree_desc, but skip the
+        * ones that we already returned in earlier rounds.
+        */
+       while (1) {
+               if (!t->d.size) {
+                       entry_clear(a);
+                       break; /* not found */
+               }
+               entry_extract(&t->d, a);
+               for (skip = t->skip; skip; skip = skip->prev)
+                       if (a->path == skip->ptr)
+                               break; /* found */
+               if (!skip)
+                       break;
+               /* We have processed this entry already. */
+               update_tree_entry(&t->d);
+       }
+
+       if (!first || !a->path)
+               return;
+
+       /*
+        * The caller wants "first" from this tree, or nothing.
+        */
+       path = a->path;
+       len = tree_entry_len(a->path, a->sha1);
+       switch (check_entry_match(first, first_len, path, len)) {
+       case -1:
+               entry_clear(a);
+       case 0:
+               return;
+       default:
+               break;
+       }
+
+       /*
+        * We need to look-ahead -- we suspect that a subtree whose
+        * name is "first" may be hiding behind the current entry "path".
+        */
+       probe = t->d;
+       while (probe.size) {
+               entry_extract(&probe, a);
+               path = a->path;
+               len = tree_entry_len(a->path, a->sha1);
+               switch (check_entry_match(first, first_len, path, len)) {
+               case -1:
+                       entry_clear(a);
+               case 0:
+                       return;
+               default:
+                       update_tree_entry(&probe);
+                       break;
+               }
+               /* keep looking */
+       }
+       entry_clear(a);
+}
+
+static void update_extended_entry(struct tree_desc_x *t, struct name_entry *a)
+{
+       if (t->d.entry.path == a->path) {
+               update_tree_entry(&t->d);
+       } else {
+               /* we have returned this entry early */
+               struct tree_desc_skip *skip = xmalloc(sizeof(*skip));
+               skip->ptr = a->path;
+               skip->prev = t->skip;
+               t->skip = skip;
+       }
+}
+
+static void free_extended_entry(struct tree_desc_x *t)
+{
+       struct tree_desc_skip *p, *s;
+
+       for (s = t->skip; s; s = p) {
+               p = s->prev;
+               free(s);
+       }
+}
+
 int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
 {
        int ret = 0;
        struct name_entry *entry = xmalloc(n*sizeof(*entry));
+       int i;
+       struct tree_desc_x *tx = xcalloc(n, sizeof(*tx));
+
+       for (i = 0; i < n; i++)
+               tx[i].d = t[i];
 
        for (;;) {
-               unsigned long mask = 0;
-               unsigned long dirmask = 0;
-               int i, last;
+               unsigned long mask, dirmask;
+               const char *first = NULL;
+               int first_len = 0;
+               struct name_entry *e;
+               int len;
 
-               last = -1;
                for (i = 0; i < n; i++) {
-                       if (!t[i].size)
+                       e = entry + i;
+                       extended_entry_extract(tx + i, e, NULL, 0);
+               }
+
+               /*
+                * A tree may have "t-2" at the current location even
+                * though it may have "t" that is a subtree behind it,
+                * and another tree may return "t".  We want to grab
+                * all "t" from all trees to match in such a case.
+                */
+               for (i = 0; i < n; i++) {
+                       e = entry + i;
+                       if (!e->path)
                                continue;
-                       entry_extract(t+i, entry+i);
-                       if (last >= 0) {
-                               int cmp = entry_compare(entry+i, entry+last);
-
-                               /*
-                                * Is the new name bigger than the old one?
-                                * Ignore it
-                                */
-                               if (cmp > 0)
+                       len = tree_entry_len(e->path, e->sha1);
+                       if (!first) {
+                               first = e->path;
+                               first_len = len;
+                               continue;
+                       }
+                       if (name_compare(e->path, len, first, first_len) < 0) {
+                               first = e->path;
+                               first_len = len;
+                       }
+               }
+
+               if (first) {
+                       for (i = 0; i < n; i++) {
+                               e = entry + i;
+                               extended_entry_extract(tx + i, e, first, first_len);
+                               /* Cull the ones that are not the earliest */
+                               if (!e->path)
                                        continue;
-                               /*
-                                * Is the new name smaller than the old one?
-                                * Ignore all old ones
-                                */
-                               if (cmp < 0)
-                                       mask = 0;
+                               len = tree_entry_len(e->path, e->sha1);
+                               if (name_compare(e->path, len, first, first_len))
+                                       entry_clear(e);
                        }
+               }
+
+               /* Now we have in entry[i] the earliest name from the trees */
+               mask = 0;
+               dirmask = 0;
+               for (i = 0; i < n; i++) {
+                       if (!entry[i].path)
+                               continue;
                        mask |= 1ul << i;
                        if (S_ISDIR(entry[i].mode))
                                dirmask |= 1ul << i;
-                       last = i;
                }
                if (!mask)
                        break;
-               dirmask &= mask;
-
-               /*
-                * Clear all the unused name-entries.
-                */
-               for (i = 0; i < n; i++) {
-                       if (mask & (1ul << i))
-                               continue;
-                       entry_clear(entry + i);
-               }
                ret = info->fn(n, mask, dirmask, entry, info);
                if (ret < 0)
                        break;
-               if (ret)
-                       mask &= ret;
+               mask &= ret;
                ret = 0;
-               for (i = 0; i < n; i++) {
+               for (i = 0; i < n; i++)
                        if (mask & (1ul << i))
-                               update_tree_entry(t + i);
-               }
+                               update_extended_entry(tx + i, entry + i);
        }
        free(entry);
+       for (i = 0; i < n; i++)
+               free_extended_entry(tx + i);
+       free(tx);
        return ret;
 }
 
diff --git a/unpack-file.c b/unpack-file.c
deleted file mode 100644 (file)
index e9d8934..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#include "cache.h"
-#include "blob.h"
-#include "exec_cmd.h"
-
-static char *create_temp_file(unsigned char *sha1)
-{
-       static char path[50];
-       void *buf;
-       enum object_type type;
-       unsigned long size;
-       int fd;
-
-       buf = read_sha1_file(sha1, &type, &size);
-       if (!buf || type != OBJ_BLOB)
-               die("unable to read blob object %s", sha1_to_hex(sha1));
-
-       strcpy(path, ".merge_file_XXXXXX");
-       fd = xmkstemp(path);
-       if (write_in_full(fd, buf, size) != size)
-               die_errno("unable to write temp-file");
-       close(fd);
-       return path;
-}
-
-int main(int argc, char **argv)
-{
-       unsigned char sha1[20];
-
-       git_extract_argv0_path(argv[0]);
-
-       if (argc != 2 || !strcmp(argv[1], "-h"))
-               usage("git unpack-file <sha1>");
-       if (get_sha1(argv[1], sha1))
-               die("Not a valid object name %s", argv[1]);
-
-       setup_git_directory();
-       git_config(git_default_config, NULL);
-
-       puts(create_temp_file(sha1));
-       return 0;
-}
index dd5999c3562219b7993420b22257f4088ab82b8d..75f54cac97f62ddaad736c2cd582cc6cdeaaebfa 100644 (file)
@@ -32,6 +32,12 @@ static struct unpack_trees_error_msgs unpack_plumbing_errors = {
 
        /* bind_overlap */
        "Entry '%s' overlaps with '%s'.  Cannot bind.",
+
+       /* sparse_not_uptodate_file */
+       "Entry '%s' not uptodate. Cannot update sparse checkout.",
+
+       /* would_lose_orphaned */
+       "Working tree file '%s' would be %s by sparse checkout update.",
 };
 
 #define ERRORMSG(o,fld) \
@@ -61,8 +67,16 @@ static void unlink_entry(struct cache_entry *ce)
 {
        if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce)))
                return;
-       if (unlink_or_warn(ce->name))
-               return;
+       if (S_ISGITLINK(ce->ce_mode)) {
+               if (rmdir(ce->name)) {
+                       warning("unable to rmdir %s: %s",
+                               ce->name, strerror(errno));
+                       return;
+               }
+       }
+       else
+               if (unlink_or_warn(ce->name))
+                       return;
        schedule_dir_for_removal(ce->name, ce_namelen(ce));
 }
 
@@ -78,7 +92,7 @@ static int check_updates(struct unpack_trees_options *o)
        if (o->update && o->verbose_update) {
                for (total = cnt = 0; cnt < index->cache_nr; cnt++) {
                        struct cache_entry *ce = index->cache[cnt];
-                       if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
+                       if (ce->ce_flags & (CE_UPDATE | CE_REMOVE | CE_WT_REMOVE))
                                total++;
                }
 
@@ -92,6 +106,13 @@ static int check_updates(struct unpack_trees_options *o)
        for (i = 0; i < index->cache_nr; i++) {
                struct cache_entry *ce = index->cache[i];
 
+               if (ce->ce_flags & CE_WT_REMOVE) {
+                       display_progress(progress, ++cnt);
+                       if (o->update)
+                               unlink_entry(ce);
+                       continue;
+               }
+
                if (ce->ce_flags & CE_REMOVE) {
                        display_progress(progress, ++cnt);
                        if (o->update)
@@ -118,6 +139,57 @@ static int check_updates(struct unpack_trees_options *o)
        return errs != 0;
 }
 
+static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o);
+static int verify_absent_sparse(struct cache_entry *ce, const char *action, struct unpack_trees_options *o);
+
+static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o)
+{
+       const char *basename;
+
+       if (ce_stage(ce))
+               return 0;
+
+       basename = strrchr(ce->name, '/');
+       basename = basename ? basename+1 : ce->name;
+       return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0;
+}
+
+static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o)
+{
+       int was_skip_worktree = ce_skip_worktree(ce);
+
+       if (will_have_skip_worktree(ce, o))
+               ce->ce_flags |= CE_SKIP_WORKTREE;
+       else
+               ce->ce_flags &= ~CE_SKIP_WORKTREE;
+
+       /*
+        * We only care about files getting into the checkout area
+        * If merge strategies want to remove some, go ahead, this
+        * flag will be removed eventually in unpack_trees() if it's
+        * outside checkout area.
+        */
+       if (ce->ce_flags & CE_REMOVE)
+               return 0;
+
+       if (!was_skip_worktree && ce_skip_worktree(ce)) {
+               /*
+                * If CE_UPDATE is set, verify_uptodate() must be called already
+                * also stat info may have lost after merged_entry() so calling
+                * verify_uptodate() again may fail
+                */
+               if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o))
+                       return -1;
+               ce->ce_flags |= CE_WT_REMOVE;
+       }
+       if (was_skip_worktree && !ce_skip_worktree(ce)) {
+               if (verify_absent_sparse(ce, "overwritten", o))
+                       return -1;
+               ce->ce_flags |= CE_UPDATE;
+       }
+       return 0;
+}
+
 static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o)
 {
        int ret = o->fn(src, o);
@@ -126,23 +198,142 @@ static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_o
        return ret;
 }
 
-static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_options *o)
+static void mark_ce_used(struct cache_entry *ce, struct unpack_trees_options *o)
+{
+       ce->ce_flags |= CE_UNPACKED;
+
+       if (o->cache_bottom < o->src_index->cache_nr &&
+           o->src_index->cache[o->cache_bottom] == ce) {
+               int bottom = o->cache_bottom;
+               while (bottom < o->src_index->cache_nr &&
+                      o->src_index->cache[bottom]->ce_flags & CE_UNPACKED)
+                       bottom++;
+               o->cache_bottom = bottom;
+       }
+}
+
+static void mark_all_ce_unused(struct index_state *index)
+{
+       int i;
+       for (i = 0; i < index->cache_nr; i++)
+               index->cache[i]->ce_flags &= ~CE_UNPACKED;
+}
+
+static int locate_in_src_index(struct cache_entry *ce,
+                              struct unpack_trees_options *o)
+{
+       struct index_state *index = o->src_index;
+       int len = ce_namelen(ce);
+       int pos = index_name_pos(index, ce->name, len);
+       if (pos < 0)
+               pos = -1 - pos;
+       return pos;
+}
+
+/*
+ * We call unpack_index_entry() with an unmerged cache entry
+ * only in diff-index, and it wants a single callback.  Skip
+ * the other unmerged entry with the same name.
+ */
+static void mark_ce_used_same_name(struct cache_entry *ce,
+                                  struct unpack_trees_options *o)
+{
+       struct index_state *index = o->src_index;
+       int len = ce_namelen(ce);
+       int pos;
+
+       for (pos = locate_in_src_index(ce, o); pos < index->cache_nr; pos++) {
+               struct cache_entry *next = index->cache[pos];
+               if (len != ce_namelen(next) ||
+                   memcmp(ce->name, next->name, len))
+                       break;
+               mark_ce_used(next, o);
+       }
+}
+
+static struct cache_entry *next_cache_entry(struct unpack_trees_options *o)
+{
+       const struct index_state *index = o->src_index;
+       int pos = o->cache_bottom;
+
+       while (pos < index->cache_nr) {
+               struct cache_entry *ce = index->cache[pos];
+               if (!(ce->ce_flags & CE_UNPACKED))
+                       return ce;
+               pos++;
+       }
+       return NULL;
+}
+
+static void add_same_unmerged(struct cache_entry *ce,
+                             struct unpack_trees_options *o)
+{
+       struct index_state *index = o->src_index;
+       int len = ce_namelen(ce);
+       int pos = index_name_pos(index, ce->name, len);
+
+       if (0 <= pos)
+               die("programming error in a caller of mark_ce_used_same_name");
+       for (pos = -pos - 1; pos < index->cache_nr; pos++) {
+               struct cache_entry *next = index->cache[pos];
+               if (len != ce_namelen(next) ||
+                   memcmp(ce->name, next->name, len))
+                       break;
+               add_entry(o, next, 0, 0);
+               mark_ce_used(next, o);
+       }
+}
+
+static int unpack_index_entry(struct cache_entry *ce,
+                             struct unpack_trees_options *o)
 {
        struct cache_entry *src[5] = { ce, NULL, };
+       int ret;
 
-       o->pos++;
+       mark_ce_used(ce, o);
        if (ce_stage(ce)) {
                if (o->skip_unmerged) {
                        add_entry(o, ce, 0, 0);
                        return 0;
                }
        }
-       return call_unpack_fn(src, o);
+       ret = call_unpack_fn(src, o);
+       if (ce_stage(ce))
+               mark_ce_used_same_name(ce, o);
+       return ret;
+}
+
+static int find_cache_pos(struct traverse_info *, const struct name_entry *);
+
+static void restore_cache_bottom(struct traverse_info *info, int bottom)
+{
+       struct unpack_trees_options *o = info->data;
+
+       if (o->diff_index_cached)
+               return;
+       o->cache_bottom = bottom;
+}
+
+static int switch_cache_bottom(struct traverse_info *info)
+{
+       struct unpack_trees_options *o = info->data;
+       int ret, pos;
+
+       if (o->diff_index_cached)
+               return 0;
+       ret = o->cache_bottom;
+       pos = find_cache_pos(info->prev, &info->name);
+
+       if (pos < -1)
+               o->cache_bottom = -2 - pos;
+       else if (pos < 0)
+               o->cache_bottom = o->src_index->cache_nr;
+       return ret;
 }
 
 static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info)
 {
-       int i;
+       int i, ret, bottom;
        struct tree_desc t[MAX_UNPACK_TREES];
        struct traverse_info newinfo;
        struct name_entry *p;
@@ -163,7 +354,11 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long
                        sha1 = names[i].sha1;
                fill_tree_descriptor(t+i, sha1);
        }
-       return traverse_trees(n, t, &newinfo);
+
+       bottom = switch_cache_bottom(&newinfo);
+       ret = traverse_trees(n, t, &newinfo);
+       restore_cache_bottom(&newinfo, bottom);
+       return ret;
 }
 
 /*
@@ -212,6 +407,20 @@ static int compare_entry(const struct cache_entry *ce, const struct traverse_inf
        return ce_namelen(ce) > traverse_path_len(info, n);
 }
 
+static int ce_in_traverse_path(const struct cache_entry *ce,
+                              const struct traverse_info *info)
+{
+       if (!info->prev)
+               return 1;
+       if (do_compare_entry(ce, info->prev, &info->name))
+               return 0;
+       /*
+        * If ce (blob) is the same name as the path (which is a tree
+        * we will be descending into), it won't be inside it.
+        */
+       return (info->pathlen < ce_namelen(ce));
+}
+
 static struct cache_entry *create_ce_entry(const struct traverse_info *info, const struct name_entry *n, int stage)
 {
        int len = traverse_path_len(info, n);
@@ -288,6 +497,114 @@ static int unpack_failed(struct unpack_trees_options *o, const char *message)
        return -1;
 }
 
+/* NEEDSWORK: give this a better name and share with tree-walk.c */
+static int name_compare(const char *a, int a_len,
+                       const char *b, int b_len)
+{
+       int len = (a_len < b_len) ? a_len : b_len;
+       int cmp = memcmp(a, b, len);
+       if (cmp)
+               return cmp;
+       return (a_len - b_len);
+}
+
+/*
+ * The tree traversal is looking at name p.  If we have a matching entry,
+ * return it.  If name p is a directory in the index, do not return
+ * anything, as we will want to match it when the traversal descends into
+ * the directory.
+ */
+static int find_cache_pos(struct traverse_info *info,
+                         const struct name_entry *p)
+{
+       int pos;
+       struct unpack_trees_options *o = info->data;
+       struct index_state *index = o->src_index;
+       int pfxlen = info->pathlen;
+       int p_len = tree_entry_len(p->path, p->sha1);
+
+       for (pos = o->cache_bottom; pos < index->cache_nr; pos++) {
+               struct cache_entry *ce = index->cache[pos];
+               const char *ce_name, *ce_slash;
+               int cmp, ce_len;
+
+               if (!ce_in_traverse_path(ce, info))
+                       continue;
+               if (ce->ce_flags & CE_UNPACKED)
+                       continue;
+               ce_name = ce->name + pfxlen;
+               ce_slash = strchr(ce_name, '/');
+               if (ce_slash)
+                       ce_len = ce_slash - ce_name;
+               else
+                       ce_len = ce_namelen(ce) - pfxlen;
+               cmp = name_compare(p->path, p_len, ce_name, ce_len);
+               /*
+                * Exact match; if we have a directory we need to
+                * delay returning it.
+                */
+               if (!cmp)
+                       return ce_slash ? -2 - pos : pos;
+               if (0 < cmp)
+                       continue; /* keep looking */
+               /*
+                * ce_name sorts after p->path; could it be that we
+                * have files under p->path directory in the index?
+                * E.g.  ce_name == "t-i", and p->path == "t"; we may
+                * have "t/a" in the index.
+                */
+               if (p_len < ce_len && !memcmp(ce_name, p->path, p_len) &&
+                   ce_name[p_len] < '/')
+                       continue; /* keep looking */
+               break;
+       }
+       return -1;
+}
+
+static struct cache_entry *find_cache_entry(struct traverse_info *info,
+                                           const struct name_entry *p)
+{
+       int pos = find_cache_pos(info, p);
+       struct unpack_trees_options *o = info->data;
+
+       if (0 <= pos)
+               return o->src_index->cache[pos];
+       else
+               return NULL;
+}
+
+static void debug_path(struct traverse_info *info)
+{
+       if (info->prev) {
+               debug_path(info->prev);
+               if (*info->prev->name.path)
+                       putchar('/');
+       }
+       printf("%s", info->name.path);
+}
+
+static void debug_name_entry(int i, struct name_entry *n)
+{
+       printf("ent#%d %06o %s\n", i,
+              n->path ? n->mode : 0,
+              n->path ? n->path : "(missing)");
+}
+
+static void debug_unpack_callback(int n,
+                                 unsigned long mask,
+                                 unsigned long dirmask,
+                                 struct name_entry *names,
+                                 struct traverse_info *info)
+{
+       int i;
+       printf("* unpack mask %lu, dirmask %lu, cnt %d ",
+              mask, dirmask, n);
+       debug_path(info);
+       putchar('\n');
+       for (i = 0; i < n; i++)
+               debug_name_entry(i, names + i);
+}
+
 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, };
@@ -298,25 +615,38 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
        while (!p->mode)
                p++;
 
+       if (o->debug_unpack)
+               debug_unpack_callback(n, mask, dirmask, names, info);
+
        /* Are we supposed to look at the index too? */
        if (o->merge) {
-               while (o->pos < o->src_index->cache_nr) {
-                       struct cache_entry *ce = o->src_index->cache[o->pos];
-                       int cmp = compare_entry(ce, info, p);
+               while (1) {
+                       int cmp;
+                       struct cache_entry *ce;
+
+                       if (o->diff_index_cached)
+                               ce = next_cache_entry(o);
+                       else
+                               ce = find_cache_entry(info, p);
+
+                       if (!ce)
+                               break;
+                       cmp = compare_entry(ce, info, p);
                        if (cmp < 0) {
                                if (unpack_index_entry(ce, o) < 0)
                                        return unpack_failed(o, NULL);
                                continue;
                        }
                        if (!cmp) {
-                               o->pos++;
                                if (ce_stage(ce)) {
                                        /*
-                                        * If we skip unmerged index entries, we'll skip this
-                                        * entry *and* the tree entries associated with it!
+                                        * If we skip unmerged index
+                                        * entries, we'll skip this
+                                        * entry *and* the tree
+                                        * entries associated with it!
                                         */
                                        if (o->skip_unmerged) {
-                                               add_entry(o, ce, 0, 0);
+                                               add_same_unmerged(ce, o);
                                                return mask;
                                        }
                                }
@@ -329,6 +659,13 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
        if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0)
                return -1;
 
+       if (src[0]) {
+               if (ce_stage(src[0]))
+                       mark_ce_used_same_name(src[0], o);
+               else
+                       mark_ce_used(src[0], o);
+       }
+
        /* Now handle any directories.. */
        if (dirmask) {
                unsigned long conflicts = mask & ~dirmask;
@@ -345,11 +682,13 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
                        matches = cache_tree_matches_traversal(o->src_index->cache_tree,
                                                               names, info);
                        /*
-                        * Everything under the name matches.  Adjust o->pos to
-                        * skip the entire hierarchy.
+                        * Everything under the name matches; skip the
+                        * entire hierarchy.  diff_index_cached codepath
+                        * special cases D/F conflicts in such a way that
+                        * it does not do any look-ahead, so this is safe.
                         */
                        if (matches) {
-                               o->pos += matches;
+                               o->cache_bottom += matches;
                                return mask;
                        }
                }
@@ -369,8 +708,9 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
  */
 int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
 {
-       int ret;
+       int i, ret;
        static struct cache_entry *dfc;
+       struct exclude_list el;
 
        if (len > MAX_UNPACK_TREES)
                die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
@@ -380,13 +720,22 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
        state.quiet = 1;
        state.refresh_cache = 1;
 
+       memset(&el, 0, sizeof(el));
+       if (!core_apply_sparse_checkout || !o->update)
+               o->skip_sparse_checkout = 1;
+       if (!o->skip_sparse_checkout) {
+               if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, NULL, &el, 0) < 0)
+                       o->skip_sparse_checkout = 1;
+               else
+                       o->el = &el;
+       }
+
        memset(&o->result, 0, sizeof(o->result));
        o->result.initialized = 1;
-       if (o->src_index) {
-               o->result.timestamp.sec = o->src_index->timestamp.sec;
-               o->result.timestamp.nsec = o->src_index->timestamp.nsec;
-       }
+       o->result.timestamp.sec = o->src_index->timestamp.sec;
+       o->result.timestamp.nsec = o->src_index->timestamp.nsec;
        o->merge_size = len;
+       mark_all_ce_unused(o->src_index);
 
        if (!dfc)
                dfc = xcalloc(1, cache_entry_size(0));
@@ -400,27 +749,87 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                info.fn = unpack_callback;
                info.data = o;
 
+               if (o->prefix) {
+                       /*
+                        * Unpack existing index entries that sort before the
+                        * prefix the tree is spliced into.  Note that o->merge
+                        * is always true in this case.
+                        */
+                       while (1) {
+                               struct cache_entry *ce = next_cache_entry(o);
+                               if (!ce)
+                                       break;
+                               if (ce_in_traverse_path(ce, &info))
+                                       break;
+                               if (unpack_index_entry(ce, o) < 0)
+                                       goto return_failed;
+                       }
+               }
+
                if (traverse_trees(len, t, &info) < 0)
-                       return unpack_failed(o, NULL);
+                       goto return_failed;
        }
 
        /* Any left-over entries in the index? */
        if (o->merge) {
-               while (o->pos < o->src_index->cache_nr) {
-                       struct cache_entry *ce = o->src_index->cache[o->pos];
+               while (1) {
+                       struct cache_entry *ce = next_cache_entry(o);
+                       if (!ce)
+                               break;
                        if (unpack_index_entry(ce, o) < 0)
-                               return unpack_failed(o, NULL);
+                               goto return_failed;
                }
        }
+       mark_all_ce_unused(o->src_index);
+
+       if (o->trivial_merges_only && o->nontrivial_merge) {
+               ret = unpack_failed(o, "Merge requires file-level merging");
+               goto done;
+       }
 
-       if (o->trivial_merges_only && o->nontrivial_merge)
-               return unpack_failed(o, "Merge requires file-level merging");
+       if (!o->skip_sparse_checkout) {
+               int empty_worktree = 1;
+               for (i = 0;i < o->result.cache_nr;i++) {
+                       struct cache_entry *ce = o->result.cache[i];
+
+                       if (apply_sparse_checkout(ce, o)) {
+                               ret = -1;
+                               goto done;
+                       }
+                       /*
+                        * Merge strategies may set CE_UPDATE|CE_REMOVE outside checkout
+                        * area as a result of ce_skip_worktree() shortcuts in
+                        * verify_absent() and verify_uptodate(). Clear them.
+                        */
+                       if (ce_skip_worktree(ce))
+                               ce->ce_flags &= ~(CE_UPDATE | CE_REMOVE);
+                       else
+                               empty_worktree = 0;
+
+               }
+               if (o->result.cache_nr && empty_worktree) {
+                       ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory");
+                       goto done;
+               }
+       }
 
        o->src_index = NULL;
        ret = check_updates(o) ? (-2) : 0;
        if (o->dst_index)
                *o->dst_index = o->result;
+
+done:
+       for (i = 0;i < el.nr;i++)
+               free(el.excludes[i]);
+       if (el.excludes)
+               free(el.excludes);
+
        return ret;
+
+return_failed:
+       mark_all_ce_unused(o->src_index);
+       ret = unpack_failed(o, NULL);
+       goto done;
 }
 
 /* Here come the merge functions */
@@ -436,6 +845,8 @@ static int same(struct cache_entry *a, struct cache_entry *b)
                return 0;
        if (!a && !b)
                return 1;
+       if ((a->ce_flags | b->ce_flags) & CE_CONFLICTED)
+               return 0;
        return a->ce_mode == b->ce_mode &&
               !hashcmp(a->sha1, b->sha1);
 }
@@ -445,16 +856,17 @@ static int same(struct cache_entry *a, struct cache_entry *b)
  * When a CE gets turned into an unmerged entry, we
  * want it to be up-to-date
  */
-static int verify_uptodate(struct cache_entry *ce,
-               struct unpack_trees_options *o)
+static int verify_uptodate_1(struct cache_entry *ce,
+                                  struct unpack_trees_options *o,
+                                  const char *error_msg)
 {
        struct stat st;
 
-       if (o->index_only || o->reset || ce_uptodate(ce))
+       if (o->index_only || (!ce_skip_worktree(ce) && (o->reset || ce_uptodate(ce))))
                return 0;
 
        if (!lstat(ce->name, &st)) {
-               unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID);
+               unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
                if (!changed)
                        return 0;
                /*
@@ -471,7 +883,21 @@ static int verify_uptodate(struct cache_entry *ce,
        if (errno == ENOENT)
                return 0;
        return o->gently ? -1 :
-               error(ERRORMSG(o, not_uptodate_file), ce->name);
+               error(error_msg, ce->name);
+}
+
+static int verify_uptodate(struct cache_entry *ce,
+                          struct unpack_trees_options *o)
+{
+       if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
+               return 0;
+       return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file));
+}
+
+static int verify_uptodate_sparse(struct cache_entry *ce,
+                                 struct unpack_trees_options *o)
+{
+       return verify_uptodate_1(ce, o, ERRORMSG(o, sparse_not_uptodate_file));
 }
 
 static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o)
@@ -522,7 +948,9 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
         * in that directory.
         */
        namelen = strlen(ce->name);
-       for (i = o->pos; i < o->src_index->cache_nr; i++) {
+       for (i = locate_in_src_index(ce, o);
+            i < o->src_index->cache_nr;
+            i++) {
                struct cache_entry *ce2 = o->src_index->cache[i];
                int len = ce_namelen(ce2);
                if (len < namelen ||
@@ -530,12 +958,14 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
                    ce2->name[namelen] != '/')
                        break;
                /*
-                * ce2->name is an entry in the subdirectory.
+                * ce2->name is an entry in the subdirectory to be
+                * removed.
                 */
                if (!ce_stage(ce2)) {
                        if (verify_uptodate(ce2, o))
                                return -1;
                        add_entry(o, ce2, CE_REMOVE, 0);
+                       mark_ce_used(ce2, o);
                }
                cnt++;
        }
@@ -572,15 +1002,16 @@ static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst,
        struct cache_entry *src;
 
        src = index_name_exists(o->src_index, dst->name, ce_namelen(dst), 1);
-       return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID);
+       return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
 }
 
 /*
  * We do not want to remove or overwrite a working tree file that
  * is not tracked, unless it is ignored.
  */
-static int verify_absent(struct cache_entry *ce, const char *action,
-                        struct unpack_trees_options *o)
+static int verify_absent_1(struct cache_entry *ce, const char *action,
+                                struct unpack_trees_options *o,
+                                const char *error_msg)
 {
        struct stat st;
 
@@ -591,7 +1022,6 @@ static int verify_absent(struct cache_entry *ce, const char *action,
                return 0;
 
        if (!lstat(ce->name, &st)) {
-               int ret;
                int dtype = ce_to_dtype(ce);
                struct cache_entry *result;
 
@@ -619,28 +1049,8 @@ static int verify_absent(struct cache_entry *ce, const char *action,
                         * files that are in "foo/" we would lose
                         * them.
                         */
-                       ret = verify_clean_subdirectory(ce, action, o);
-                       if (ret < 0)
-                               return ret;
-
-                       /*
-                        * If this removed entries from the index,
-                        * what that means is:
-                        *
-                        * (1) the caller unpack_callback() saw path/foo
-                        * in the index, and it has not removed it because
-                        * it thinks it is handling 'path' as blob with
-                        * D/F conflict;
-                        * (2) we will return "ok, we placed a merged entry
-                        * in the index" which would cause o->pos to be
-                        * incremented by one;
-                        * (3) however, original o->pos now has 'path/foo'
-                        * marked with "to be removed".
-                        *
-                        * We need to increment it by the number of
-                        * deleted entries here.
-                        */
-                       o->pos += ret;
+                       if (verify_clean_subdirectory(ce, action, o) < 0)
+                               return -1;
                        return 0;
                }
 
@@ -660,13 +1070,30 @@ static int verify_absent(struct cache_entry *ce, const char *action,
        }
        return 0;
 }
+static int verify_absent(struct cache_entry *ce, const char *action,
+                        struct unpack_trees_options *o)
+{
+       if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
+               return 0;
+       return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked));
+}
+
+static int verify_absent_sparse(struct cache_entry *ce, const char *action,
+                        struct unpack_trees_options *o)
+{
+       return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_orphaned));
+}
 
 static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
                struct unpack_trees_options *o)
 {
        int update = CE_UPDATE;
 
-       if (old) {
+       if (!old) {
+               if (verify_absent(merge, "overwritten", o))
+                       return -1;
+               invalidate_ce_path(merge, o);
+       } else if (!(old->ce_flags & CE_CONFLICTED)) {
                /*
                 * See if we can re-use the old CE directly?
                 * That way we get the uptodate stat info.
@@ -680,13 +1107,16 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
                } else {
                        if (verify_uptodate(old, o))
                                return -1;
+                       if (ce_skip_worktree(old))
+                               update |= CE_SKIP_WORKTREE;
                        invalidate_ce_path(old, o);
                }
-       }
-       else {
-               if (verify_absent(merge, "overwritten", o))
-                       return -1;
-               invalidate_ce_path(merge, o);
+       } else {
+               /*
+                * Previously unmerged entry left as an existence
+                * marker by read_index_unmerged();
+                */
+               invalidate_ce_path(old, o);
        }
 
        add_entry(o, merge, update, CE_STAGEMASK);
@@ -702,7 +1132,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
                        return -1;
                return 0;
        }
-       if (verify_uptodate(old, o))
+       if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o))
                return -1;
        add_entry(o, ce, CE_REMOVE, 0);
        invalidate_ce_path(ce, o);
@@ -767,7 +1197,8 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
                remote = NULL;
        }
 
-       /* First, if there's a #16 situation, note that to prevent #13
+       /*
+        * First, if there's a #16 situation, note that to prevent #13
         * and #14.
         */
        if (!same(remote, head)) {
@@ -781,7 +1212,8 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
                }
        }
 
-       /* We start with cases where the index is allowed to match
+       /*
+        * We start with cases where the index is allowed to match
         * something other than the head: #14(ALT) and #2ALT, where it
         * is permitted to match the result instead.
         */
@@ -811,12 +1243,13 @@ int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
        if (!head && !remote && any_anc_missing)
                return 0;
 
-       /* Under the new "aggressive" rule, we resolve mostly trivial
+       /*
+        * Under the "aggressive" rule, we resolve mostly trivial
         * cases that we historically had git-merge-one-file resolve.
         */
        if (o->aggressive) {
-               int head_deleted = !head && !df_conflict_head;
-               int remote_deleted = !remote && !df_conflict_remote;
+               int head_deleted = !head;
+               int remote_deleted = !remote;
                struct cache_entry *ce = NULL;
 
                if (index)
@@ -1004,10 +1437,10 @@ int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o)
 
        if (old && same(old, a)) {
                int update = 0;
-               if (o->reset && !ce_uptodate(old)) {
+               if (o->reset && !ce_uptodate(old) && !ce_skip_worktree(old)) {
                        struct stat st;
                        if (lstat(old->name, &st) ||
-                           ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID))
+                           ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE))
                                update |= CE_UPDATE;
                }
                add_entry(o, old, update, 0);
index d19df44f4070ca1bda29382e56fbced53aacd994..ef70eab39025fcdaccda059692ae447a13fa0aeb 100644 (file)
@@ -4,6 +4,7 @@
 #define MAX_UNPACK_TREES 8
 
 struct unpack_trees_options;
+struct exclude_list;
 
 typedef int (*merge_fn_t)(struct cache_entry **src,
                struct unpack_trees_options *options);
@@ -14,6 +15,8 @@ struct unpack_trees_error_msgs {
        const char *not_uptodate_dir;
        const char *would_lose_untracked;
        const char *bind_overlap;
+       const char *sparse_not_uptodate_file;
+       const char *would_lose_orphaned;
 };
 
 struct unpack_trees_options {
@@ -28,9 +31,11 @@ struct unpack_trees_options {
                     skip_unmerged,
                     initial_checkout,
                     diff_index_cached,
+                    debug_unpack,
+                    skip_sparse_checkout,
                     gently;
        const char *prefix;
-       int pos;
+       int cache_bottom;
        struct dir_struct *dir;
        merge_fn_t fn;
        struct unpack_trees_error_msgs msgs;
@@ -44,6 +49,8 @@ struct unpack_trees_options {
        struct index_state *dst_index;
        struct index_state *src_index;
        struct index_state result;
+
+       struct exclude_list *el; /* for internal use */
 };
 
 extern int unpack_trees(unsigned n, struct tree_desc *t,
index 57529ae63d49826952b29860b3d4106b60250c7b..df992490d5f86b5eff2b87e90090b1ec576aae9a 100644 (file)
@@ -198,7 +198,7 @@ struct userdiff_driver *userdiff_find_by_path(const char *path)
        struct git_attr_check check;
 
        if (!attr)
-               attr = git_attr("diff", 4);
+               attr = git_attr("diff");
        check.attr = attr;
 
        if (!path)
diff --git a/utf8.c b/utf8.c
index 7ddff23fa77fbadf7723bca03d24ad5b8f2baca2..ab326ac83e0d9e81b06abff58b00a98341adcd36 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -163,7 +163,7 @@ static int git_wcwidth(ucs_char_t ch)
  * If the string was not a valid UTF-8, *start pointer is set to NULL
  * and the return value is undefined.
  */
-ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p)
+static ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p)
 {
        unsigned char *s = (unsigned char *)*start;
        ucs_char_t ch;
diff --git a/utf8.h b/utf8.h
index ae30ae4c6e501e4766db93c94253fd404cd29357..c9738d83d991d89bbdd4c5a6442fcad2fdaaa4df 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -3,7 +3,6 @@
 
 typedef unsigned int ucs_char_t;  /* assuming 32bit int */
 
-ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p);
 int utf8_width(const char **start, size_t *remainder_p);
 int utf8_strwidth(const char *string);
 int is_utf8(const char *text);
diff --git a/var.c b/var.c
deleted file mode 100644 (file)
index d9892f8..0000000
--- a/var.c
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Eric Biederman, 2005
- */
-#include "cache.h"
-#include "exec_cmd.h"
-
-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);
-};
-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++)
-               if ((val = ptr->read(0)))
-                       printf("%s=%s\n", ptr->name, val);
-}
-
-static const char *read_var(const char *var)
-{
-       struct git_var *ptr;
-       const char *val;
-       val = NULL;
-       for (ptr = git_vars; ptr->read; ptr++) {
-               if (strcmp(var, ptr->name) == 0) {
-                       val = ptr->read(IDENT_ERROR_ON_NO_NAME);
-                       break;
-               }
-       }
-       return val;
-}
-
-static int show_config(const char *var, const char *value, void *cb)
-{
-       if (value)
-               printf("%s=%s\n", var, value);
-       else
-               printf("%s\n", var);
-       return git_default_config(var, value, cb);
-}
-
-int main(int argc, char **argv)
-{
-       const char *val;
-       int nongit;
-       if (argc != 2) {
-               usage(var_usage);
-       }
-
-       git_extract_argv0_path(argv[0]);
-
-       setup_git_directory_gently(&nongit);
-       val = NULL;
-
-       if (strcmp(argv[1], "-l") == 0) {
-               git_config(show_config, NULL);
-               list_vars();
-               return 0;
-       }
-       git_config(git_default_config, NULL);
-       val = read_var(argv[1]);
-       if (!val)
-               usage(var_usage);
-
-       printf("%s\n", val);
-
-       return 0;
-}
diff --git a/wrap-for-bin.sh b/wrap-for-bin.sh
new file mode 100644 (file)
index 0000000..c5075c9
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# wrap-for-bin.sh: Template for git executable wrapper scripts
+# to run test suite against sandbox, but with only bindir-installed
+# executables in PATH.  The Makefile copies this into various
+# files in bin-wrappers, substituting
+# @@BUILD_DIR@@ and @@PROG@@.
+
+GIT_EXEC_PATH='@@BUILD_DIR@@'
+GIT_TEMPLATE_DIR='@@BUILD_DIR@@/templates/blt'
+GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib'
+PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH"
+export GIT_EXEC_PATH GIT_TEMPLATE_DIR GITPERLLIB PATH
+
+exec "${GIT_EXEC_PATH}/@@PROG@@" "$@"
index c9be1400c005e25b003acecc0cb037dd2f07e56f..0e3e20a3fd38f6f99da44483ee0bb9753f2b217a 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -34,6 +34,16 @@ void *xmalloc(size_t size)
        return ret;
 }
 
+void *xmallocz(size_t size)
+{
+       void *ret;
+       if (size + 1 < size)
+               die("Data too large to fit into virtual memory space.");
+       ret = xmalloc(size + 1);
+       ((char*)ret)[size] = 0;
+       return ret;
+}
+
 /*
  * xmemdupz() allocates (len + 1) bytes of memory, duplicates "len" bytes of
  * "data" to the allocated memory, zero terminates the allocated memory,
@@ -42,10 +52,7 @@ void *xmalloc(size_t size)
  */
 void *xmemdupz(const void *data, size_t len)
 {
-       char *p = xmalloc(len + 1);
-       memcpy(p, data, len);
-       p[len] = '\0';
-       return p;
+       return memcpy(xmallocz(len), data, len);
 }
 
 char *xstrndup(const char *str, size_t len)
diff --git a/ws.c b/ws.c
index 760b5743fa11f25dd4facf8beeb02e7aa28d09e1..c0893386e6c8aa3af002e847d228dfc5ef64a9cf 100644 (file)
--- a/ws.c
+++ b/ws.c
@@ -64,7 +64,7 @@ static void setup_whitespace_attr_check(struct git_attr_check *check)
        static struct git_attr *attr_whitespace;
 
        if (!attr_whitespace)
-               attr_whitespace = git_attr("whitespace", 10);
+               attr_whitespace = git_attr("whitespace");
        check[0].attr = attr_whitespace;
 }
 
index 38eb24536b34e40af4c43eb1631e08624e8993d6..5807fc3211a3aa8f886694776fe8c86b5bc5eb59 100644 (file)
@@ -47,28 +47,33 @@ void wt_status_prepare(struct wt_status *s)
 static void wt_status_print_unmerged_header(struct wt_status *s)
 {
        const char *c = color(WT_STATUS_HEADER, s);
+
        color_fprintf_ln(s->fp, c, "# Unmerged paths:");
        if (!advice_status_hints)
                return;
-       if (!s->is_initial)
+       if (s->in_merge)
+               ;
+       else if (!s->is_initial)
                color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
        else
                color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
-       color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to mark resolution)");
+       color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" as appropriate to mark resolution)");
        color_fprintf_ln(s->fp, c, "#");
 }
 
 static void wt_status_print_cached_header(struct wt_status *s)
 {
        const char *c = color(WT_STATUS_HEADER, s);
+
        color_fprintf_ln(s->fp, c, "# Changes to be committed:");
        if (!advice_status_hints)
                return;
-       if (!s->is_initial) {
+       if (s->in_merge)
+               ; /* NEEDSWORK: use "git reset --unresolve"??? */
+       else if (!s->is_initial)
                color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
-       } else {
+       else
                color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
-       }
        color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -76,6 +81,7 @@ static void wt_status_print_dirty_header(struct wt_status *s,
                                         int has_deleted)
 {
        const char *c = color(WT_STATUS_HEADER, s);
+
        color_fprintf_ln(s->fp, c, "# Changed but not updated:");
        if (!advice_status_hints)
                return;
@@ -277,6 +283,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
        rev.diffopt.format_callback_data = s;
+       rev.prune_data = s->pathspec;
        run_diff_files(&rev, 0);
 }
 
@@ -293,6 +300,7 @@ static void wt_status_collect_changes_index(struct wt_status *s)
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 200;
        rev.diffopt.break_opt = 0;
+       rev.prune_data = s->pathspec;
        run_diff_index(&rev, 1);
 }
 
@@ -305,6 +313,8 @@ static void wt_status_collect_changes_initial(struct wt_status *s)
                struct wt_status_change_data *d;
                struct cache_entry *ce = active_cache[i];
 
+               if (!ce_path_match(ce, s->pathspec))
+                       continue;
                it = string_list_insert(ce->name, &s->change);
                d = it->util;
                if (!d) {
@@ -333,11 +343,13 @@ static void wt_status_collect_untracked(struct wt_status *s)
                        DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
        setup_standard_excludes(&dir);
 
-       fill_directory(&dir, NULL);
+       fill_directory(&dir, s->pathspec);
        for (i = 0; i < dir.nr; i++) {
                struct dir_entry *ent = dir.entries[i];
                if (!cache_name_is_other(ent->name, ent->len))
                        continue;
+               if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
+                       continue;
                s->workdir_untracked = 1;
                string_list_insert(ent->name, &s->untracked);
        }
@@ -447,7 +459,7 @@ static void wt_status_print_changed(struct wt_status *s)
        wt_status_print_trailer(s);
 }
 
-static void wt_status_print_submodule_summary(struct wt_status *s)
+static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted)
 {
        struct child_process sm_summary;
        char summary_limit[64];
@@ -456,11 +468,11 @@ static void wt_status_print_submodule_summary(struct wt_status *s)
        const char *argv[] = {
                "submodule",
                "summary",
-               "--cached",
+               uncommitted ? "--files" : "--cached",
                "--for-status",
                "--summary-limit",
                summary_limit,
-               s->amend ? "HEAD^" : "HEAD",
+               uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD"),
                NULL
        };
 
@@ -541,10 +553,8 @@ static void wt_status_print_tracking(struct wt_status *s)
 
 void wt_status_print(struct wt_status *s)
 {
-       unsigned char sha1[20];
        const char *branch_color = color(WT_STATUS_HEADER, s);
 
-       s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
        if (s->branch) {
                const char *on_what = "On branch ";
                const char *branch_name = s->branch;
@@ -561,8 +571,6 @@ void wt_status_print(struct wt_status *s)
                        wt_status_print_tracking(s);
        }
 
-       wt_status_collect(s);
-
        if (s->is_initial) {
                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
@@ -572,8 +580,10 @@ void wt_status_print(struct wt_status *s)
        wt_status_print_updated(s);
        wt_status_print_unmerged(s);
        wt_status_print_changed(s);
-       if (s->submodule_summary)
-               wt_status_print_submodule_summary(s);
+       if (s->submodule_summary) {
+               wt_status_print_submodule_summary(s, 0);  /* staged */
+               wt_status_print_submodule_summary(s, 1);  /* unstaged */
+       }
        if (s->show_untracked_files)
                wt_status_print_untracked(s);
        else if (s->commitable)
@@ -598,3 +608,107 @@ void wt_status_print(struct wt_status *s)
                        printf("nothing to commit (working directory clean)\n");
        }
 }
+
+static void wt_shortstatus_unmerged(int null_termination, struct string_list_item *it,
+                          struct wt_status *s)
+{
+       struct wt_status_change_data *d = it->util;
+       const char *how = "??";
+
+       switch (d->stagemask) {
+       case 1: how = "DD"; break; /* both deleted */
+       case 2: how = "AU"; break; /* added by us */
+       case 3: how = "UD"; break; /* deleted by them */
+       case 4: how = "UA"; break; /* added by them */
+       case 5: how = "DU"; break; /* deleted by us */
+       case 6: how = "AA"; break; /* both added */
+       case 7: how = "UU"; break; /* both modified */
+       }
+       color_fprintf(s->fp, color(WT_STATUS_UNMERGED, s), "%s", how);
+       if (null_termination) {
+               fprintf(stdout, " %s%c", it->string, 0);
+       } else {
+               struct strbuf onebuf = STRBUF_INIT;
+               const char *one;
+               one = quote_path(it->string, -1, &onebuf, s->prefix);
+               printf(" %s\n", one);
+               strbuf_release(&onebuf);
+       }
+}
+
+static void wt_shortstatus_status(int null_termination, struct string_list_item *it,
+                        struct wt_status *s)
+{
+       struct wt_status_change_data *d = it->util;
+
+       if (d->index_status)
+               color_fprintf(s->fp, color(WT_STATUS_UPDATED, s), "%c", d->index_status);
+       else
+               putchar(' ');
+       if (d->worktree_status)
+               color_fprintf(s->fp, color(WT_STATUS_CHANGED, s), "%c", d->worktree_status);
+       else
+               putchar(' ');
+       putchar(' ');
+       if (null_termination) {
+               fprintf(stdout, "%s%c", it->string, 0);
+               if (d->head_path)
+                       fprintf(stdout, "%s%c", d->head_path, 0);
+       } else {
+               struct strbuf onebuf = STRBUF_INIT;
+               const char *one;
+               if (d->head_path) {
+                       one = quote_path(d->head_path, -1, &onebuf, s->prefix);
+                       printf("%s -> ", one);
+                       strbuf_release(&onebuf);
+               }
+               one = quote_path(it->string, -1, &onebuf, s->prefix);
+               printf("%s\n", one);
+               strbuf_release(&onebuf);
+       }
+}
+
+static void wt_shortstatus_untracked(int null_termination, struct string_list_item *it,
+                           struct wt_status *s)
+{
+       if (null_termination) {
+               fprintf(stdout, "?? %s%c", it->string, 0);
+       } else {
+               struct strbuf onebuf = STRBUF_INIT;
+               const char *one;
+               one = quote_path(it->string, -1, &onebuf, s->prefix);
+               color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "??");
+               printf(" %s\n", one);
+               strbuf_release(&onebuf);
+       }
+}
+
+void wt_shortstatus_print(struct wt_status *s, int null_termination)
+{
+       int i;
+       for (i = 0; i < s->change.nr; i++) {
+               struct wt_status_change_data *d;
+               struct string_list_item *it;
+
+               it = &(s->change.items[i]);
+               d = it->util;
+               if (d->stagemask)
+                       wt_shortstatus_unmerged(null_termination, it, s);
+               else
+                       wt_shortstatus_status(null_termination, it, s);
+       }
+       for (i = 0; i < s->untracked.nr; i++) {
+               struct string_list_item *it;
+
+               it = &(s->untracked.items[i]);
+               wt_shortstatus_untracked(null_termination, it, s);
+       }
+}
+
+void wt_porcelain_print(struct wt_status *s, int null_termination)
+{
+       s->use_color = 0;
+       s->relative_paths = 0;
+       s->prefix = NULL;
+       wt_shortstatus_print(s, null_termination);
+}
index a0e75177bec925e95b4257811aae0bdee93c41f1..c60f40a34a89ccdacdc864203d4441cf3e20bcc5 100644 (file)
@@ -31,8 +31,10 @@ struct wt_status {
        int is_initial;
        char *branch;
        const char *reference;
+       const char **pathspec;
        int verbose;
        int amend;
+       int in_merge;
        int nowarn;
        int use_color;
        int relative_paths;
@@ -55,4 +57,7 @@ void wt_status_prepare(struct wt_status *s);
 void wt_status_print(struct wt_status *s);
 void wt_status_collect(struct wt_status *s);
 
+void wt_shortstatus_print(struct wt_status *s, int null_termination);
+void wt_porcelain_print(struct wt_status *s, int null_termination);
+
 #endif /* STATUS_H */
index 4da052a3fff6768fca05b2f3399c5d87ec1daa2e..3f6229edbeb21bb1ca0c423d88390f3b5a05b5a2 100644 (file)
@@ -58,6 +58,12 @@ extern "C" {
 #define XDL_MERGE_ZEALOUS_ALNUM 3
 #define XDL_MERGE_LEVEL_MASK 0x0f
 
+/* merge favor modes */
+#define XDL_MERGE_FAVOR_OURS 1
+#define XDL_MERGE_FAVOR_THEIRS 2
+#define XDL_MERGE_FAVOR(flags) (((flags)>>4) & 3)
+#define XDL_MERGE_FLAGS(level, style, favor) ((level)|(style)|((favor)<<4))
+
 /* merge output styles */
 #define XDL_MERGE_DIFF3 0x8000
 #define XDL_MERGE_STYLE_MASK 0x8000
@@ -108,9 +114,16 @@ long xdl_mmfile_size(mmfile_t *mmf);
 int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
             xdemitconf_t const *xecfg, xdemitcb_t *ecb);
 
+typedef struct s_xmparam {
+       xpparam_t xpp;
+       int marker_size;
+} xmparam_t;
+
+#define DEFAULT_CONFLICT_MARKER_SIZE 7
+
 int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
                mmfile_t *mf2, const char *name2,
-               xpparam_t const *xpp, int level, mmbuffer_t *result);
+               xmparam_t const *xmp, int flags, mmbuffer_t *result);
 
 #ifdef __cplusplus
 }
index 1cb65a95166a8cb60af590118f59d78aa4d24b74..8cbe45e6755487dbe3759398375a11d05f6d91bc 100644 (file)
@@ -145,13 +145,15 @@ static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest)
 static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
                              xdfenv_t *xe2, const char *name2,
                              int size, int i, int style,
-                             xdmerge_t *m, char *dest)
+                             xdmerge_t *m, char *dest, int marker_size)
 {
-       const int marker_size = 7;
        int marker1_size = (name1 ? strlen(name1) + 1 : 0);
        int marker2_size = (name2 ? strlen(name2) + 1 : 0);
        int j;
 
+       if (marker_size <= 0)
+               marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+
        /* Before conflicting part */
        size += xdl_recs_copy(xe1, i, m->i1 - i, 0,
                              dest ? dest + size : NULL);
@@ -214,14 +216,20 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
 
 static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
                                 xdfenv_t *xe2, const char *name2,
-                                xdmerge_t *m, char *dest, int style)
+                                int favor,
+                                xdmerge_t *m, char *dest, int style,
+                                int marker_size)
 {
        int size, i;
 
        for (size = i = 0; m; m = m->next) {
+               if (favor && !m->mode)
+                       m->mode = favor;
+
                if (m->mode == 0)
                        size = fill_conflict_hunk(xe1, name1, xe2, name2,
-                                                 size, i, style, m, dest);
+                                                 size, i, style, m, dest,
+                                                 marker_size);
                else if (m->mode == 1)
                        size += xdl_recs_copy(xe1, i, m->i1 + m->chg1 - i, 0,
                                              dest ? dest + size : NULL);
@@ -386,11 +394,13 @@ static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m,
  */
 static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
                xdfenv_t *xe2, xdchange_t *xscr2, const char *name2,
-               int flags, xpparam_t const *xpp, mmbuffer_t *result) {
+               int flags, xmparam_t const *xmp, mmbuffer_t *result) {
        xdmerge_t *changes, *c;
+       xpparam_t const *xpp = &xmp->xpp;
        int i0, i1, i2, chg0, chg1, chg2;
        int level = flags & XDL_MERGE_LEVEL_MASK;
        int style = flags & XDL_MERGE_STYLE_MASK;
+       int favor = XDL_MERGE_FAVOR(flags);
 
        if (style == XDL_MERGE_DIFF3) {
                /*
@@ -522,26 +532,29 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
        }
        /* output */
        if (result) {
+               int marker_size = xmp->marker_size;
                int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2,
-                       changes, NULL, style);
+                                                favor, changes, NULL, style,
+                                                marker_size);
                result->ptr = xdl_malloc(size);
                if (!result->ptr) {
                        xdl_cleanup_merge(changes);
                        return -1;
                }
                result->size = size;
-               xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes,
-                                     result->ptr, style);
+               xdl_fill_merge_buffer(xe1, name1, xe2, name2, favor, changes,
+                                     result->ptr, style, marker_size);
        }
        return xdl_cleanup_merge(changes);
 }
 
 int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
                mmfile_t *mf2, const char *name2,
-               xpparam_t const *xpp, int flags, mmbuffer_t *result) {
+               xmparam_t const *xmp, int flags, mmbuffer_t *result) {
        xdchange_t *xscr1, *xscr2;
        xdfenv_t xe1, xe2;
        int status;
+       xpparam_t const *xpp = &xmp->xpp;
 
        result->ptr = NULL;
        result->size = 0;
@@ -574,7 +587,7 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
        } else {
                status = xdl_do_merge(&xe1, xscr1, name1,
                                      &xe2, xscr2, name2,
-                                     flags, xpp, result);
+                                     flags, xmp, result);
        }
        xdl_free_script(xscr1);
        xdl_free_script(xscr2);