Code

Merge branch 'tr/grep-threading'
authorJunio C Hamano <gitster@pobox.com>
Thu, 22 Dec 2011 19:27:28 +0000 (11:27 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 22 Dec 2011 19:27:28 +0000 (11:27 -0800)
* tr/grep-threading:
  grep: disable threading in non-worktree case
  grep: enable threading with -p and -W using lazy attribute lookup
  grep: load funcname patterns for -W

215 files changed:
.gitignore
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/RelNotes/1.7.6.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.7.5.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.8.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.9.txt
Documentation/config.txt
Documentation/git-credential-cache--daemon.txt [new file with mode: 0644]
Documentation/git-credential-cache.txt [new file with mode: 0644]
Documentation/git-credential-store.txt [new file with mode: 0644]
Documentation/git-fsck.txt
Documentation/git-mv.txt
Documentation/git-stripspace.txt
Documentation/git-tag.txt
Documentation/git.txt
Documentation/gitcredentials.txt [new file with mode: 0644]
Documentation/pretty-formats.txt
Documentation/technical/api-credentials.txt [new file with mode: 0644]
INSTALL
Makefile
archive.c
branch.c
branch.h
builtin.h
builtin/add.c
builtin/apply.c
builtin/blame.c
builtin/branch.c
builtin/checkout.c
builtin/clone.c
builtin/commit-tree.c
builtin/commit.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/for-each-ref.c
builtin/fsck.c
builtin/init-db.c
builtin/log.c
builtin/merge.c
builtin/mv.c
builtin/notes.c
builtin/pack-objects.c
builtin/receive-pack.c
builtin/remote.c
builtin/reset.c
builtin/revert.c
builtin/send-pack.c
builtin/show-branch.c
builtin/stripspace.c
builtin/symbolic-ref.c
builtin/tag.c
builtin/upload-archive.c
bulk-checkin.c [new file with mode: 0644]
bulk-checkin.h [new file with mode: 0644]
cache-tree.c
cache-tree.h
cache.h
commit.c
commit.h
compat/setenv.c
compat/snprintf.c
compat/terminal.c [new file with mode: 0644]
compat/terminal.h [new file with mode: 0644]
config.c
config.mak.in
configure.ac
connect.c
contrib/credential/osxkeychain/.gitignore [new file with mode: 0644]
contrib/credential/osxkeychain/Makefile [new file with mode: 0644]
contrib/credential/osxkeychain/git-credential-osxkeychain.c [new file with mode: 0644]
contrib/fast-import/git-p4
convert.c
credential-cache--daemon.c [new file with mode: 0644]
credential-cache.c [new file with mode: 0644]
credential-store.c [new file with mode: 0644]
credential.c [new file with mode: 0644]
credential.h [new file with mode: 0644]
csum-file.c
csum-file.h
daemon.c
environment.c
fast-import.c
gettext.c
gettext.h
git-compat-util.h
git-gui/.gitattributes
git-gui/GIT-VERSION-GEN
git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/browser.tcl
git-gui/lib/choose_rev.tcl
git-gui/lib/class.tcl
git-gui/lib/commit.tcl
git-gui/lib/diff.tcl
git-gui/lib/index.tcl
git-gui/lib/line.tcl
git-gui/lib/option.tcl
git-gui/lib/search.tcl
git-gui/lib/sshkey.tcl
git-gui/lib/themed.tcl
git-gui/lib/tools.tcl
git-gui/lib/transport.tcl
git-rebase--interactive.sh
git-request-pull.sh
git-sh-i18n.sh
git.c
gitk-git/gitk
gitweb/gitweb.perl
gitweb/static/gitweb.css
http-backend.c
http-fetch.c
http-push.c
http.c
http.h
imap-send.c
merge-recursive.c
notes-cache.c
notes-merge.c
notes-merge.h
pack-write.c
pack.h
perl/Git/I18N.pm [new file with mode: 0644]
perl/Makefile
perl/Makefile.PL
po/.gitignore
po/README [new file with mode: 0644]
po/is.po [new file with mode: 0644]
pretty.c
prompt.c [new file with mode: 0644]
prompt.h [new file with mode: 0644]
reflog-walk.c
reflog-walk.h
refs.c
refs.h
remote-curl.c
remote.c
sequencer.c
sequencer.h
sha1_file.c
shell.c
show-index.c
strbuf.c
strbuf.h
submodule.c
t/lib-credential.sh [new file with mode: 0755]
t/lib-gettext.sh [new file with mode: 0644]
t/lib-httpd/apache.conf
t/t0090-cache-tree.sh [new file with mode: 0755]
t/t0200-gettext-basic.sh [new file with mode: 0755]
t/t0200/test.c [new file with mode: 0644]
t/t0200/test.perl [new file with mode: 0644]
t/t0200/test.sh [new file with mode: 0644]
t/t0201-gettext-fallbacks.sh
t/t0202-gettext-perl.sh [new file with mode: 0755]
t/t0202/test.pl [new file with mode: 0644]
t/t0203-gettext-setlocale-sanity.sh [new file with mode: 0755]
t/t0204-gettext-reencode-sanity.sh [new file with mode: 0755]
t/t0205-gettext-poison.sh [new file with mode: 0755]
t/t0300-credentials.sh [new file with mode: 0755]
t/t0301-credential-cache.sh [new file with mode: 0755]
t/t0302-credential-store.sh [new file with mode: 0755]
t/t0303-credential-external.sh [new file with mode: 0755]
t/t1007-hash-object.sh
t/t1013-loose-object-format.sh
t/t1050-large.sh
t/t1300-repo-config.sh
t/t1412-reflog-loop.sh
t/t1501-worktree.sh
t/t1510-repo-setup.sh
t/t1511-rev-parse-caret.sh
t/t2018-checkout-branch.sh
t/t2023-checkout-m.sh [new file with mode: 0755]
t/t3030-merge-recursive.sh
t/t3040-subprojects-basic.sh
t/t3200-branch.sh
t/t3310-notes-merge-manual-resolve.sh
t/t3400-rebase.sh
t/t3401-rebase-partial.sh
t/t3418-rebase-continue.sh
t/t3419-rebase-patch-id.sh
t/t3510-cherry-pick-sequence.sh
t/t3900-i18n-commit.sh
t/t4131-apply-fake-ancestor.sh
t/t4136-apply-check.sh [new file with mode: 0755]
t/t5000-tar-tree.sh
t/t5150-request-pull.sh
t/t5500-fetch-pack.sh
t/t5527-fetch-odd-refs.sh [new file with mode: 0755]
t/t5540-http-push.sh
t/t5541-http-push.sh
t/t5550-http-fetch.sh
t/t6006-rev-list-format.sh
t/t7006-pager.sh
t/t7106-reset-sequence.sh [deleted file]
t/t7501-commit.sh
t/t9301-fast-import-notes.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9805-skip-submit-edit.sh
t/t9807-submit.sh [new file with mode: 0755]
t/t9808-chdir.sh [new file with mode: 0755]
t/test-lib.sh
test-credential.c [new file with mode: 0644]
test-dump-cache-tree.c
test-scrap-cache-tree.c [new file with mode: 0644]
transport.c
tree-diff.c
unix-socket.c [new file with mode: 0644]
unix-socket.h [new file with mode: 0644]
upload-pack.c
userdiff.c
wrap-for-bin.sh
wt-status.c
zlib.c

index 8572c8c0b0199589a8c1875825f5b2e7e4dc4a86..3b7680ea1e5baa58430798a9836481975dbb6234 100644 (file)
@@ -30,6 +30,9 @@
 /git-commit-tree
 /git-config
 /git-count-objects
+/git-credential-cache
+/git-credential-cache--daemon
+/git-credential-store
 /git-cvsexportcommit
 /git-cvsimport
 /git-cvsserver
 /gitweb/static/gitweb.js
 /gitweb/static/gitweb.min.*
 /test-chmtime
+/test-credential
 /test-ctype
 /test-date
 /test-delta
 /test-dump-cache-tree
+/test-scrap-cache-tree
 /test-genrandom
 /test-index-version
 /test-line-buffer
index fe1c1e5bc26e683540cb9fe5f43320192be9185d..483008699f923be17926e8ed938ae17868f6ddf5 100644 (file)
@@ -81,6 +81,10 @@ For shell scripts specifically (not exhaustive):
      are ERE elements not BRE (note that \? and \+ are not even part
      of BRE -- making them accessible from BRE is a GNU extension).
 
+ - Use Git's gettext wrappers in git-sh-i18n to make the user
+   interface translatable. See "Marking strings for translation" in
+   po/README.
+
 For C programs:
 
  - We use tabs to indent, and interpret tabs as taking up to
@@ -144,6 +148,9 @@ For C programs:
  - When we pass <string, length> pair to functions, we should try to
    pass them in that order.
 
+ - Use Git's gettext wrappers to make the user interface
+   translatable. See "Marking strings for translation" in po/README.
+
 Writing Documentation:
 
  Every user-visible change should be reflected in the documentation.
index 304b31edee2e5c4e998c48cc571c66e8264cc581..116f17587e724271d343de0082e7ef0071d459e3 100644 (file)
@@ -7,6 +7,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \
 MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
        gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \
        gitdiffcore.txt gitnamespaces.txt gitrevisions.txt gitworkflows.txt
+MAN7_TXT += gitcredentials.txt
 
 MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
 MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
diff --git a/Documentation/RelNotes/1.7.6.5.txt b/Documentation/RelNotes/1.7.6.5.txt
new file mode 100644 (file)
index 0000000..6713132
--- /dev/null
@@ -0,0 +1,26 @@
+Git v1.7.6.5 Release Notes
+==========================
+
+Fixes since v1.7.6.4
+--------------------
+
+ * The date parser did not accept timezone designators that lack minutes
+   part and also has a colon between "hh:mm".
+
+ * After fetching from a remote that has very long refname, the reporting
+   output could have corrupted by overrunning a static buffer.
+
+ * "git mergetool" did not use its arguments as pathspec, but as a path to
+   the file that may not even have any conflict.
+
+ * "git name-rev --all" tried to name all _objects_, naturally failing to
+   describe many blobs and trees, instead of showing only commits as
+   advertised in its documentation.
+
+ * "git remote rename $a $b" were not careful to match the remote name
+   against $a (i.e. source side of the remote nickname).
+
+ * "gitweb" used to produce a non-working link while showing the contents
+   of a blob, when JavaScript actions are enabled.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.7.5.txt b/Documentation/RelNotes/1.7.7.5.txt
new file mode 100644 (file)
index 0000000..7b09319
--- /dev/null
@@ -0,0 +1,14 @@
+Git v1.7.7.5 Release Notes
+==========================
+
+Fixes since v1.7.7.4
+--------------------
+
+ * After fetching from a remote that has very long refname, the reporting
+   output could have corrupted by overrunning a static buffer.
+
+ * "git checkout" and "git merge" treated in-tree .gitignore and exclude
+   file in $GIT_DIR/info/ directory inconsistently when deciding which
+   untracked files are ignored and expendable.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.8.1.txt b/Documentation/RelNotes/1.7.8.1.txt
new file mode 100644 (file)
index 0000000..33dc948
--- /dev/null
@@ -0,0 +1,38 @@
+Git v1.7.8.1 Release Notes
+==========================
+
+Fixes since v1.7.8
+------------------
+
+ * In some codepaths (notably, checkout and merge), the ignore patterns
+   recorded in $GIT_DIR/info/exclude were not honored. They now are.
+
+ * "git apply --check" did not error out when given an empty input
+   without any patch.
+
+ * "git archive" mistakenly allowed remote clients to ask for commits
+   that are not at the tip of any ref.
+
+ * "git checkout" and "git merge" treated in-tree .gitignore and exclude
+   file in $GIT_DIR/info/ directory inconsistently when deciding which
+   untracked files are ignored and expendable.
+
+ * LF-to-CRLF streaming filter used when checking out a large-ish blob
+   fell into an infinite loop with a rare input.
+
+ * The function header pattern for files with "diff=cpp" attribute did
+   not consider "type *funcname(type param1,..." as the beginning of a
+   function.
+
+ * The error message from "git diff" and "git status" when they fail
+   to inspect changes in submodules did not report which submodule they
+   had trouble with.
+
+ * After fetching from a remote that has very long refname, the reporting
+   output could have corrupted by overrunning a static buffer.
+
+ * "git pack-objects" avoids creating cyclic dependencies among deltas
+   when seeing a broken packfile that records the same object in both
+   the deflated form and as a delta.
+
+Also contains minor fixes and documentation updates.
index 258ab7a186084e5dc6804e2c679e9a41666acd20..70ff0602315109a665a66d68f5803fda71a92a9d 100644 (file)
@@ -4,11 +4,45 @@ Git v1.7.9 Release Notes (draft)
 Updates since v1.7.8
 --------------------
 
+ * gitk updates accumulated since early 2011.
+
+ * git-gui updated to 0.16.0.
+
+ * git-p4 (in contrib/) updates.
+
+ * i18n effort is going forward and Git uses localized messages if
+   available.
+
  * Porcelain commands like "git reset" did not distinguish deletions
    and type-changes from ordinary modification, and reported them with
    the same 'M' moniker. They now use 'D' (for deletion) and 'T' (for
    type-change) to match "git status -s" and "git diff --name-status".
 
+ * The code to handle username/password for HTTP transaction used in
+   "git push" & "git fetch" learned to talk "credential API" to
+   external programs to cache or store them, to allow integration with
+   platform native keychain mechanisms.
+
+ * "git commit" and "git reset" re-learned the optimization to prime
+   the cache-tree information in the index, which makes it faster to
+   write a tree object out after the index entries are updated.
+
+ * "git add" learned to stream large files directly into a packfile
+   instead of writing them into individual loose object files.
+
+ * "git branch -m <current branch> HEAD" is an obvious no-op and is
+   now allowed.
+
+ * "git checkout -B <current branch> <elsewhere>" is a more intuitive
+   way to spell "git reset --keep <elsewhere>".
+
+ * "git checkout" and "git merge" learned "--no-overwrite-ignore" option
+   to tell Git that untracked and ignored files are not expendable.
+
+ * "git commit --amend" learned "--no-edit" option to say that the
+   user is amending the tree being recorded, without updating the
+   commit log message.
+
  * fsck and prune are relatively lengthy operations that still go
    silent while making the end-user wait. They learned to give progress
    output like other slow operations.
@@ -41,6 +75,10 @@ Updates since v1.7.8
    which serves as a global fallback for setting 'branch.<name>.rebase'
    configuration variable per branch.
 
+ * "git tag" learned "--cleanup" option to control how the whitespaces
+   and empty lines in tag message are cleaned up.
+
+ * "gitweb" learned to show side-by-side diff.
 
 Also contains minor documentation updates and code clean-ups.
 
@@ -48,14 +86,74 @@ Also contains minor documentation updates and code clean-ups.
 Fixes since v1.7.8
 ------------------
 
- * In some codepaths (notably, checkout and merge), the ignore patterns
-   recorded in $GIT_DIR/info/exclude were not honored. They now are.
-   (merge fc001b5 nd/maint-ignore-exclude later to maint).
-
+ * The function header pattern for files with "diff=cpp" attribute did
+   not consider "type *funcname(type param1,..." as the beginning of a
+   function.
+   (merge 37e7793 tr/userdiff-c-returns-pointer later to maint).
+
+ * The replacement implemention for snprintf used on platforms with
+   native snprintf that is broken did not use va_copy correctly.
+   (merge a9bfbc5 jk/maint-snprintf-va-copy later to maint).
+
+ * LF-to-CRLF streaming filter used when checking out a large-ish blob
+   fell into an infinite loop with a rare input.
+   (merge 284e3d2 cn/maint-lf-to-crlf-filter later to maint).
+
+ * git native connection going over TCP (not over SSH) did not set
+   SO_KEEPALIVE option which failed to receive link layer errors.
+   (merge e47a858 ew/keepalive later to maint).
+
+ * "git archive" mistakenly allowed remote clients to ask for commits
+   that are not at the tip of any ref.
+   (merge 7b51c33 jk/maint-upload-archive later to maint).
+
+ * "git apply --check" did not error out when given an empty input
+   without any patch.
+   (merge cc64b31 bc/maint-apply-check-no-patch later to maint).
+
+ * "git checkout -m" did not recreate the conflicted state in a "both
+   sides added, without any common ancestor version" conflict
+   situation.
+   (merge 335c6e4 jc/checkout-m-twoway later to maint).
+
+ * "git cherry-pick $commit" (not a range) created an unnecessary
+   sequencer state and interfered with valid workflow to use the
+   command during a session to cherry-pick multiple commits.
+   (merge d596118 jn/maint-sequencer-fixes later to maint).
+
+ * The error message from "git diff" and "git status" when they fail
+   to inspect changes in submodules did not report which submodule they
+   had trouble with.
+   (merge 6a5ceda jl/submodule-status-failure-report later to maint).
+
+ * "fast-import" did not correctly update an existing notes tree,
+   possibly corrupting the fan-out.
+
+ * "git fetch-pack" accepted unqualified refs that do not begin with
+   refs/ by mistake and compensated it by matching the refspec with
+   tail-match, which was doubly wrong. This broke fetching from a
+   repository with a funny named ref "refs/foo/refs/heads/master" and a
+   'master' branch with "git fetch-pack refs/heads/master", as the
+   command incorrectly considered the former a "match".
+   (merge bab8d28 jk/fetch-no-tail-match-refs later to maint).
+
+ * "git mv" gave suboptimal error/warning messages when it overwrites
+   target files. It also did not pay attention to "-v" option.
+   (merge 534376c jk/maint-mv later to maint).
+
+ * When a "reword" action in "git rebase -i" failed to run "commit --amend",
+   we did not give the control back to the user to resolve the situation, and
+   instead kept the original commit log message.
+   (merge 0becb3e aw/rebase-i-stop-on-failure-to-amend later to maint).
+
+ * Authenticated "git push" over dumb HTTP were broken with a recent
+   change and failed without asking for password when username is
+   given.
+   (merge a4ddbc3 jk/maint-push-over-dav later to maint).
 
 --
 exec >/var/tmp/1
-O=v1.7.8-162-gd2c7807
+O=v1.7.8-351-g2dccad3
 echo O=$(git describe master)
 git log --first-parent --oneline --reverse ^$O master
 echo
index 8a7d2d4cb1c0dd200b1d7fc98fe3b5e78acba755..6e63b5938f60f544cf3db3ce0d9a277964e99115 100644 (file)
@@ -115,35 +115,32 @@ in the appropriate manual page. You will find a description of non-core
 porcelain configuration variables in the respective porcelain documentation.
 
 advice.*::
-       When set to 'true', display the given optional help message.
-       When set to 'false', do not display. The configuration variables
-       are:
+       These variables control various optional help messages designed to
+       aid new users. All 'advice.*' variables default to 'true', and you
+       can tell Git that you do not need help by setting these to 'false':
 +
 --
        pushNonFastForward::
                Advice shown when linkgit:git-push[1] refuses
-               non-fast-forward refs. Default: true.
+               non-fast-forward refs.
        statusHints::
                Directions on how to stage/unstage/add shown in the
                output of linkgit:git-status[1] and the template shown
-               when writing commit messages. Default: true.
+               when writing commit messages.
        commitBeforeMerge::
                Advice shown when linkgit:git-merge[1] refuses to
                merge to avoid overwriting 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.
-
+               domain name.
        detachedHead::
-               Advice shown when you used linkgit::git-checkout[1] to
+               Advice shown when you used linkgit:git-checkout[1] to
                move to the detach HEAD state, to instruct how to create
-               a local branch after the fact.  Default: true.
+               a local branch after the fact.
 --
 
 core.fileMode::
@@ -834,6 +831,29 @@ commit.template::
        "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
        specified user's home directory.
 
+credential.helper::
+       Specify an external helper to be called when a username or
+       password credential is needed; the helper may consult external
+       storage to avoid prompting the user for the credentials. See
+       linkgit:gitcredentials[7] for details.
+
+credential.useHttpPath::
+       When acquiring credentials, consider the "path" component of an http
+       or https URL to be important. Defaults to false. See
+       linkgit:gitcredentials[7] for more information.
+
+credential.username::
+       If no username is set for a network authentication, use this username
+       by default. See credential.<context>.* below, and
+       linkgit:gitcredentials[7].
+
+credential.<url>.*::
+       Any of the credential.* options above can be applied selectively to
+       some credentials. For example "credential.https://example.com.username"
+       would set the default username only for https connections to
+       example.com. See linkgit:gitcredentials[7] for details on how URLs are
+       matched.
+
 include::diff-config.txt[]
 
 difftool.<tool>.path::
diff --git a/Documentation/git-credential-cache--daemon.txt b/Documentation/git-credential-cache--daemon.txt
new file mode 100644 (file)
index 0000000..11edc5a
--- /dev/null
@@ -0,0 +1,26 @@
+git-credential-cache--daemon(1)
+===============================
+
+NAME
+----
+git-credential-cache--daemon - temporarily store user credentials in memory
+
+SYNOPSIS
+--------
+[verse]
+git credential-cache--daemon <socket>
+
+DESCRIPTION
+-----------
+
+NOTE: You probably don't want to invoke this command yourself; it is
+started automatically when you use linkgit:git-credential-cache[1].
+
+This command listens on the Unix domain socket specified by `<socket>`
+for `git-credential-cache` clients. Clients may store and retrieve
+credentials. Each credential is held for a timeout specified by the
+client; once no credentials are held, the daemon exits.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-credential-cache.txt b/Documentation/git-credential-cache.txt
new file mode 100644 (file)
index 0000000..f3d09c5
--- /dev/null
@@ -0,0 +1,77 @@
+git-credential-cache(1)
+=======================
+
+NAME
+----
+git-credential-cache - helper to temporarily store passwords in memory
+
+SYNOPSIS
+--------
+-----------------------------
+git config credential.helper 'cache [options]'
+-----------------------------
+
+DESCRIPTION
+-----------
+
+This command caches credentials in memory for use by future git
+programs. The stored credentials never touch the disk, and are forgotten
+after a configurable timeout.  The cache is accessible over a Unix
+domain socket, restricted to the current user by filesystem permissions.
+
+You probably don't want to invoke this command directly; it is meant to
+be used as a credential helper by other parts of git. See
+linkgit:gitcredentials[7] or `EXAMPLES` below.
+
+OPTIONS
+-------
+
+--timeout <seconds>::
+
+       Number of seconds to cache credentials (default: 900).
+
+--socket <path>::
+
+       Use `<path>` to contact a running cache daemon (or start a new
+       cache daemon if one is not started). Defaults to
+       `~/.git-credential-cache/socket`. If your home directory is on a
+       network-mounted filesystem, you may need to change this to a
+       local filesystem.
+
+CONTROLLING THE DAEMON
+----------------------
+
+If you would like the daemon to exit early, forgetting all cached
+credentials before their timeout, you can issue an `exit` action:
+
+--------------------------------------
+git credential-cache exit
+--------------------------------------
+
+EXAMPLES
+--------
+
+The point of this helper is to reduce the number of times you must type
+your username or password. For example:
+
+------------------------------------
+$ git config credential.helper cache
+$ git push http://example.com/repo.git
+Username: <type your username>
+Password: <type your password>
+
+[work for 5 more minutes]
+$ git push http://example.com/repo.git
+[your credentials are used automatically]
+------------------------------------
+
+You can provide options via the credential.helper configuration
+variable (this example drops the cache time to 5 minutes):
+
+-------------------------------------------------------
+$ git config credential.helper 'cache --timeout=300'
+-------------------------------------------------------
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-credential-store.txt b/Documentation/git-credential-store.txt
new file mode 100644 (file)
index 0000000..3109346
--- /dev/null
@@ -0,0 +1,75 @@
+git-credential-store(1)
+=======================
+
+NAME
+----
+git-credential-store - helper to store credentials on disk
+
+SYNOPSIS
+--------
+-------------------
+git config credential.helper 'store [options]'
+-------------------
+
+DESCRIPTION
+-----------
+
+NOTE: Using this helper will store your passwords unencrypted on disk,
+protected only by filesystem permissions. If this is not an acceptable
+security tradeoff, try linkgit:git-credential-cache[1], or find a helper
+that integrates with secure storage provided by your operating system.
+
+This command stores credentials indefinitely on disk for use by future
+git programs.
+
+You probably don't want to invoke this command directly; it is meant to
+be used as a credential helper by other parts of git. See
+linkgit:gitcredentials[7] or `EXAMPLES` below.
+
+OPTIONS
+-------
+
+--store=<path>::
+
+       Use `<path>` to store credentials. The file will have its
+       filesystem permissions set to prevent other users on the system
+       from reading it, but will not be encrypted or otherwise
+       protected. Defaults to `~/.git-credentials`.
+
+EXAMPLES
+--------
+
+The point of this helper is to reduce the number of times you must type
+your username or password. For example:
+
+------------------------------------------
+$ git config credential.helper store
+$ git push http://example.com/repo.git
+Username: <type your username>
+Password: <type your password>
+
+[several days later]
+$ git push http://example.com/repo.git
+[your credentials are used automatically]
+------------------------------------------
+
+STORAGE FORMAT
+--------------
+
+The `.git-credentials` file is stored in plaintext. Each credential is
+stored on its own line as a URL like:
+
+------------------------------
+https://user:pass@example.com
+------------------------------
+
+When git needs authentication for a particular URL context,
+credential-store will consider that context a pattern to match against
+each entry in the credentials file.  If the protocol, hostname, and
+username (if we already have one) match, then the password is returned
+to git. See the discussion of configuration in linkgit:gitcredentials[7]
+for more information.
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 0a17b4258e2a69a1d2a5e931dd14ac1869876f33..6c47395ad2d22f54ed76dda914efbc28b0dca083 100644 (file)
@@ -81,30 +81,20 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless
        progress status even if the standard error stream is not
        directed to a terminal.
 
-It tests SHA1 and general object sanity, and it does full tracking of
-the resulting reachability and everything else. It prints out any
-corruption it finds (missing or bad objects), and if you use the
-'--unreachable' flag it will also print out objects that exist but
-that aren't reachable from any of the specified head nodes.
-
-So for example
-
-       git fsck --unreachable HEAD \
-               $(git for-each-ref --format="%(objectname)" refs/heads)
+DISCUSSION
+----------
 
-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
-do have a valid tree.
+git-fsck tests SHA1 and general object sanity, and it does full tracking
+of the resulting reachability and everything else. It prints out any
+corruption it finds (missing or bad objects), and if you use the
+'--unreachable' flag it will also print out objects that exist but that
+aren't reachable from any of the specified head nodes (or the default
+set, as mentioned above).
 
 Any corrupt objects you will have to find in backups or other archives
 (i.e., you can just remove them and do an 'rsync' with some other site in
 the hopes that somebody else has the object you have corrupted).
 
-Of course, "valid tree" doesn't mean that it wasn't generated by some
-evil person, and the end result might be crap. git is a revision
-tracking system, not a quality assurance system ;)
-
 Extracted Diagnostics
 ---------------------
 
index b8db3739640491566dee6e381bae319b7e7be8c6..e3c84486141685e2f128f64f46d24c36cc45e97f 100644 (file)
@@ -15,8 +15,8 @@ DESCRIPTION
 -----------
 This script is used to move or rename a file, directory or symlink.
 
- git mv [-f] [-n] <source> <destination>
- git mv [-f] [-n] [-k] <source> ... <destination directory>
+ git mv [-v] [-f] [-n] [-k] <source> <destination>
+ git mv [-v] [-f] [-n] [-k] <source> ... <destination directory>
 
 In the first form, it renames <source>, which must exist and be either
 a file, symlink or directory, to <destination>.
@@ -40,6 +40,10 @@ OPTIONS
 --dry-run::
        Do nothing; only show what would happen
 
+-v::
+--verbose::
+       Report the names of files as they are moved.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index b78f031cd4464b21be145d4ffa79ff39dc8bd2bb..a80d94650d3a6b724dc68aac18fd562ff38d2cf8 100644 (file)
@@ -3,26 +3,83 @@ git-stripspace(1)
 
 NAME
 ----
-git-stripspace - Filter out empty lines
+git-stripspace - Remove unnecessary whitespace
 
 
 SYNOPSIS
 --------
 [verse]
-'git stripspace' [-s | --strip-comments] < <stream>
+'git stripspace' [-s | --strip-comments] < input
 
 DESCRIPTION
 -----------
-Remove multiple empty lines, and empty lines at beginning and end.
+
+Clean the input in the manner used by 'git' for text such as commit
+messages, notes, tags and branch descriptions.
+
+With no arguments, this will:
+
+- remove trailing whitespace from all lines
+- collapse multiple consecutive empty lines into one empty line
+- remove empty lines from the beginning and end of the input
+- add a missing '\n' to the last line if necessary.
+
+In the case where the input consists entirely of whitespace characters, no
+output will be produced.
+
+*NOTE*: This is intended for cleaning metadata, prefer the `--whitespace=fix`
+mode of linkgit:git-apply[1] for correcting whitespace of patches or files in
+the repository.
 
 OPTIONS
 -------
 -s::
 --strip-comments::
-       In addition to empty lines, also strip lines starting with '#'.
+       Skip and remove all lines starting with '#'.
+
+EXAMPLES
+--------
+
+Given the following noisy input with '$' indicating the end of a line:
 
-<stream>::
-       Byte stream to act on.
+--------
+|A brief introduction   $
+|   $
+|$
+|A new paragraph$
+|# with a commented-out line    $
+|explaining lots of stuff.$
+|$
+|# An old paragraph, also commented-out. $
+|      $
+|The end.$
+|  $
+---------
+
+Use 'git stripspace' with no arguments to obtain:
+
+--------
+|A brief introduction$
+|$
+|A new paragraph$
+|# with a commented-out line$
+|explaining lots of stuff.$
+|$
+|# An old paragraph, also commented-out.$
+|$
+|The end.$
+---------
+
+Use 'git stripspace --strip-comments' to obtain:
+
+--------
+|A brief introduction$
+|$
+|A new paragraph$
+|explaining lots of stuff.$
+|$
+|The end.$
+---------
 
 GIT
 ---
index c83cb13de67943813edc99725b87cfe94beba87e..622a019eb034e61f73bf326ed1f000fd90fe9375 100644 (file)
@@ -99,6 +99,13 @@ OPTIONS
        Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
        is given.
 
+--cleanup=<mode>::
+       This option sets how the tag message is cleaned up.
+       The  '<mode>' can be one of 'verbatim', 'whitespace' and 'strip'.  The
+       'strip' mode is default. The 'verbatim' mode does not change message at
+       all, 'whitespace' removes just leading/trailing whitespace lines and
+       'strip' removes both whitespace and commentary.
+
 <tagname>::
        The name of the tag to create, delete, or describe.
        The new tag name must pass all checks defined by
index e869032fc0703aed18d3cb1417eda4341c29e093..da7d48787e7a00e7e14f9d9a0bf236ffe088bd8b 100644 (file)
@@ -49,15 +49,20 @@ Documentation for older releases are available here:
 * release notes for
   link:RelNotes/1.7.8.txt[1.7.8].
 
-* link:v1.7.7.1/git.html[documentation for release 1.7.7.1]
+* link:v1.7.7.5/git.html[documentation for release 1.7.7.5]
 
 * release notes for
+  link:RelNotes/1.7.7.5.txt[1.7.7.5],
+  link:RelNotes/1.7.7.4.txt[1.7.7.4],
+  link:RelNotes/1.7.7.3.txt[1.7.7.3],
+  link:RelNotes/1.7.7.2.txt[1.7.7.2],
   link:RelNotes/1.7.7.1.txt[1.7.7.1],
   link:RelNotes/1.7.7.txt[1.7.7].
 
-* link:v1.7.6.4/git.html[documentation for release 1.7.6.4]
+* link:v1.7.6.5/git.html[documentation for release 1.7.6.5]
 
 * release notes for
+  link:RelNotes/1.7.6.5.txt[1.7.6.5],
   link:RelNotes/1.7.6.4.txt[1.7.6.4],
   link:RelNotes/1.7.6.3.txt[1.7.6.3],
   link:RelNotes/1.7.6.2.txt[1.7.6.2],
diff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt
new file mode 100644 (file)
index 0000000..066f825
--- /dev/null
@@ -0,0 +1,183 @@
+gitcredentials(7)
+=================
+
+NAME
+----
+gitcredentials - providing usernames and passwords to git
+
+SYNOPSIS
+--------
+------------------
+git config credential.https://example.com.username myusername
+git config credential.helper "$helper $options"
+------------------
+
+DESCRIPTION
+-----------
+
+Git will sometimes need credentials from the user in order to perform
+operations; for example, it may need to ask for a username and password
+in order to access a remote repository over HTTP. This manual describes
+the mechanisms git uses to request these credentials, as well as some
+features to avoid inputting these credentials repeatedly.
+
+REQUESTING CREDENTIALS
+----------------------
+
+Without any credential helpers defined, git will try the following
+strategies to ask the user for usernames and passwords:
+
+1. If the `GIT_ASKPASS` environment variable is set, the program
+   specified by the variable is invoked. A suitable prompt is provided
+   to the program on the command line, and the user's input is read
+   from its standard output.
+
+2. Otherwise, if the `core.askpass` configuration variable is set, its
+   value is used as above.
+
+3. Otherwise, if the `SSH_ASKPASS` environment variable is set, its
+   value is used as above.
+
+4. Otherwise, the user is prompted on the terminal.
+
+AVOIDING REPETITION
+-------------------
+
+It can be cumbersome to input the same credentials over and over.  Git
+provides two methods to reduce this annoyance:
+
+1. Static configuration of usernames for a given authentication context.
+
+2. Credential helpers to cache or store passwords, or to interact with
+   a system password wallet or keychain.
+
+The first is simple and appropriate if you do not have secure storage available
+for a password. It is generally configured by adding this to your config:
+
+---------------------------------------
+[credential "https://example.com"]
+       username = me
+---------------------------------------
+
+Credential helpers, on the other hand, are external programs from which git can
+request both usernames and passwords; they typically interface with secure
+storage provided by the OS or other programs.
+
+To use a helper, you must first select one to use. Git currently
+includes the following helpers:
+
+cache::
+
+       Cache credentials in memory for a short period of time. See
+       linkgit:git-credential-cache[1] for details.
+
+store::
+
+       Store credentials indefinitely on disk. See
+       linkgit:git-credential-store[1] for details.
+
+You may also have third-party helpers installed; search for
+`credential-*` in the output of `git help -a`, and consult the
+documentation of individual helpers.  Once you have selected a helper,
+you can tell git to use it by putting its name into the
+credential.helper variable.
+
+1. Find a helper.
++
+-------------------------------------------
+$ git help -a | grep credential-
+credential-foo
+-------------------------------------------
+
+2. Read its description.
++
+-------------------------------------------
+$ git help credential-foo
+-------------------------------------------
+
+3. Tell git to use it.
++
+-------------------------------------------
+$ git config --global credential.helper foo
+-------------------------------------------
+
+If there are multiple instances of the `credential.helper` configuration
+variable, each helper will be tried in turn, and may provide a username,
+password, or nothing. Once git has acquired both a username and a
+password, no more helpers will be tried.
+
+
+CREDENTIAL CONTEXTS
+-------------------
+
+Git considers each credential to have a context defined by a URL. This context
+is used to look up context-specific configuration, and is passed to any
+helpers, which may use it as an index into secure storage.
+
+For instance, imagine we are accessing `https://example.com/foo.git`. When git
+looks into a config file to see if a section matches this context, it will
+consider the two a match if the context is a more-specific subset of the
+pattern in the config file. For example, if you have this in your config file:
+
+--------------------------------------
+[credential "https://example.com"]
+       username = foo
+--------------------------------------
+
+then we will match: both protocols are the same, both hosts are the same, and
+the "pattern" URL does not care about the path component at all. However, this
+context would not match:
+
+--------------------------------------
+[credential "https://kernel.org"]
+       username = foo
+--------------------------------------
+
+because the hostnames differ. Nor would it match `foo.example.com`; git
+compares hostnames exactly, without considering whether two hosts are part of
+the same domain. Likewise, a config entry for `http://example.com` would not
+match: git compares the protocols exactly.
+
+
+CONFIGURATION OPTIONS
+---------------------
+
+Options for a credential context can be configured either in
+`credential.\*` (which applies to all credentials), or
+`credential.<url>.\*`, where <url> matches the context as described
+above.
+
+The following options are available in either location:
+
+helper::
+
+       The name of an external credential helper, and any associated options.
+       If the helper name is not an absolute path, then the string `git
+       credential-` is prepended. The resulting string is executed by the
+       shell (so, for example, setting this to `foo --option=bar` will execute
+       `git credential-foo --option=bar` via the shell. See the manual of
+       specific helpers for examples of their use.
+
+username::
+
+       A default username, if one is not provided in the URL.
+
+useHttpPath::
+
+       By default, git does not consider the "path" component of an http URL
+       to be worth matching via external helpers. This means that a credential
+       stored for `https://example.com/foo.git` will also be used for
+       `https://example.com/bar.git`. If you do want to distinguish these
+       cases, set this option to `true`.
+
+
+CUSTOM HELPERS
+--------------
+
+You can write your own custom helpers to interface with any system in
+which you keep credentials. See the documentation for git's
+link:technical/api-credentials.html[credentials API] for details.
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 561cc9f7d7ef1a2502d608e59e2eae345bffd685..880b6f2e6f946ab6fc0ff4850759908364d50854 100644 (file)
@@ -132,6 +132,10 @@ The placeholders are:
 - '%N': commit notes
 - '%gD': reflog selector, e.g., `refs/stash@\{1\}`
 - '%gd': shortened reflog selector, e.g., `stash@\{1\}`
+- '%gn': reflog identity name
+- '%gN': reflog identity name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
+- '%ge': reflog identity email
+- '%gE': reflog identity email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
 - '%gs': reflog subject
 - '%Cred': switch color to red
 - '%Cgreen': switch color to green
diff --git a/Documentation/technical/api-credentials.txt b/Documentation/technical/api-credentials.txt
new file mode 100644 (file)
index 0000000..21ca6a2
--- /dev/null
@@ -0,0 +1,245 @@
+credentials API
+===============
+
+The credentials API provides an abstracted way of gathering username and
+password credentials from the user (even though credentials in the wider
+world can take many forms, in this document the word "credential" always
+refers to a username and password pair).
+
+Data Structures
+---------------
+
+`struct credential`::
+
+       This struct represents a single username/password combination
+       along with any associated context. All string fields should be
+       heap-allocated (or NULL if they are not known or not applicable).
+       The meaning of the individual context fields is the same as
+       their counterparts in the helper protocol; see the section below
+       for a description of each field.
++
+The `helpers` member of the struct is a `string_list` of helpers.  Each
+string specifies an external helper which will be run, in order, to
+either acquire or store credentials. See the section on credential
+helpers below.
++
+This struct should always be initialized with `CREDENTIAL_INIT` or
+`credential_init`.
+
+
+Functions
+---------
+
+`credential_init`::
+
+       Initialize a credential structure, setting all fields to empty.
+
+`credential_clear`::
+
+       Free any resources associated with the credential structure,
+       returning it to a pristine initialized state.
+
+`credential_fill`::
+
+       Instruct the credential subsystem to fill the username and
+       password fields of the passed credential struct by first
+       consulting helpers, then asking the user. After this function
+       returns, the username and password fields of the credential are
+       guaranteed to be non-NULL. If an error occurs, the function will
+       die().
+
+`credential_reject`::
+
+       Inform the credential subsystem that the provided credentials
+       have been rejected. This will cause the credential subsystem to
+       notify any helpers of the rejection (which allows them, for
+       example, to purge the invalid credentials from storage).  It
+       will also free() the username and password fields of the
+       credential and set them to NULL (readying the credential for
+       another call to `credential_fill`). Any errors from helpers are
+       ignored.
+
+`credential_approve`::
+
+       Inform the credential subsystem that the provided credentials
+       were successfully used for authentication.  This will cause the
+       credential subsystem to notify any helpers of the approval, so
+       that they may store the result to be used again.  Any errors
+       from helpers are ignored.
+
+`credential_from_url`::
+
+       Parse a URL into broken-down credential fields.
+
+Example
+-------
+
+The example below shows how the functions of the credential API could be
+used to login to a fictitious "foo" service on a remote host:
+
+-----------------------------------------------------------------------
+int foo_login(struct foo_connection *f)
+{
+       int status;
+       /*
+        * Create a credential with some context; we don't yet know the
+        * username or password.
+        */
+
+       struct credential c = CREDENTIAL_INIT;
+       c.protocol = xstrdup("foo");
+       c.host = xstrdup(f->hostname);
+
+       /*
+        * Fill in the username and password fields by contacting
+        * helpers and/or asking the user. The function will die if it
+        * fails.
+        */
+       credential_fill(&c);
+
+       /*
+        * Otherwise, we have a username and password. Try to use it.
+        */
+       status = send_foo_login(f, c.username, c.password);
+       switch (status) {
+       case FOO_OK:
+               /* It worked. Store the credential for later use. */
+               credential_accept(&c);
+               break;
+       case FOO_BAD_LOGIN:
+               /* Erase the credential from storage so we don't try it
+                * again. */
+               credential_reject(&c);
+               break;
+       default:
+               /*
+                * Some other error occured. We don't know if the
+                * credential is good or bad, so report nothing to the
+                * credential subsystem.
+                */
+       }
+
+       /* Free any associated resources. */
+       credential_clear(&c);
+
+       return status;
+}
+-----------------------------------------------------------------------
+
+
+Credential Helpers
+------------------
+
+Credential helpers are programs executed by git to fetch or save
+credentials from and to long-term storage (where "long-term" is simply
+longer than a single git process; e.g., credentials may be stored
+in-memory for a few minutes, or indefinitely on disk).
+
+Each helper is specified by a single string. The string is transformed
+by git into a command to be executed using these rules:
+
+  1. If the helper string begins with "!", it is considered a shell
+     snippet, and everything after the "!" becomes the command.
+
+  2. Otherwise, if the helper string begins with an absolute path, the
+     verbatim helper string becomes the command.
+
+  3. Otherwise, the string "git credential-" is prepended to the helper
+     string, and the result becomes the command.
+
+The resulting command then has an "operation" argument appended to it
+(see below for details), and the result is executed by the shell.
+
+Here are some example specifications:
+
+----------------------------------------------------
+# run "git credential-foo"
+foo
+
+# same as above, but pass an argument to the helper
+foo --bar=baz
+
+# the arguments are parsed by the shell, so use shell
+# quoting if necessary
+foo --bar="whitespace arg"
+
+# you can also use an absolute path, which will not use the git wrapper
+/path/to/my/helper --with-arguments
+
+# or you can specify your own shell snippet
+!f() { echo "password=`cat $HOME/.secret`"; }; f
+----------------------------------------------------
+
+Generally speaking, rule (3) above is the simplest for users to specify.
+Authors of credential helpers should make an effort to assist their
+users by naming their program "git-credential-$NAME", and putting it in
+the $PATH or $GIT_EXEC_PATH during installation, which will allow a user
+to enable it with `git config credential.helper $NAME`.
+
+When a helper is executed, it will have one "operation" argument
+appended to its command line, which is one of:
+
+`get`::
+
+       Return a matching credential, if any exists.
+
+`store`::
+
+       Store the credential, if applicable to the helper.
+
+`erase`::
+
+       Remove a matching credential, if any, from the helper's storage.
+
+The details of the credential will be provided on the helper's stdin
+stream. The credential is split into a set of named attributes.
+Attributes are provided to the helper, one per line. Each attribute is
+specified by a key-value pair, separated by an `=` (equals) sign,
+followed by a newline. The key may contain any bytes except `=`,
+newline, or NUL. The value may contain any bytes except newline or NUL.
+In both cases, all bytes are treated as-is (i.e., there is no quoting,
+and one cannot transmit a value with newline or NUL in it). The list of
+attributes is terminated by a blank line or end-of-file.
+
+Git will send the following attributes (but may not send all of
+them for a given credential; for example, a `host` attribute makes no
+sense when dealing with a non-network protocol):
+
+`protocol`::
+
+       The protocol over which the credential will be used (e.g.,
+       `https`).
+
+`host`::
+
+       The remote hostname for a network credential.
+
+`path`::
+
+       The path with which the credential will be used. E.g., for
+       accessing a remote https repository, this will be the
+       repository's path on the server.
+
+`username`::
+
+       The credential's username, if we already have one (e.g., from a
+       URL, from the user, or from a previously run helper).
+
+`password`::
+
+       The credential's password, if we are asking it to be stored.
+
+For a `get` operation, the helper should produce a list of attributes
+on stdout in the same format. A helper is free to produce a subset, or
+even no values at all if it has nothing useful to provide. Any provided
+attributes will overwrite those already known about by git.
+
+For a `store` or `erase` operation, the helper's output is ignored.
+If it fails to perform the requested operation, it may complain to
+stderr to inform the user. If it does not support the requested
+operation (e.g., a read-only store), it should silently ignore the
+request.
+
+If a helper receives any other operation, it should silently ignore the
+request. This leaves room for future operations to be added (older
+helpers will just ignore the new requests).
diff --git a/INSTALL b/INSTALL
index bf0d97ef769adda0066578c7823a124385785e94..8120641a513f1528b7e8f312b1ba8f496b936539 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -106,6 +106,18 @@ Issues of note:
          history graphically, and in git-gui.  If you don't want gitk or
          git-gui, you can use NO_TCLTK.
 
+       - A gettext library is used by default for localizing Git. The
+         primary target is GNU libintl, but the Solaris gettext
+         implementation also works.
+
+         We need a gettext.h on the system for C code, gettext.sh (or
+         Solaris gettext(1)) for shell scripts, and libintl-perl for Perl
+         programs.
+
+         Set NO_GETTEXT to disable localization support and make Git only
+         use English. Under autoconf the configure script will do this
+         automatically if it can't find libintl on the system.
+
  - Some platform specific issues are dealt with Makefile rules,
    but depending on your specific installation, you may not
    have all the libraries/tools needed, or you may have
index ed8232075e017b43519298083a6c678ea9c806d9..a782409306df85985e1f465eab4bd3cd7fa2cc83 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,22 @@ all::
 # Define EXPATDIR=/foo/bar if your expat header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
 #
+# Define NO_GETTEXT if you don't want Git output to be translated.
+# A translated Git requires GNU libintl or another gettext implementation,
+# plus libintl-perl at runtime.
+#
+# Define HAVE_LIBCHARSET_H if you haven't set NO_GETTEXT and you can't
+# trust the langinfo.h's nl_langinfo(CODESET) function to return the
+# current character set. GNU and Solaris have a nl_langinfo(CODESET),
+# FreeBSD can use either, but MinGW and some others need to use
+# libcharset.h's locale_charset() instead.
+#
+# Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't
+# need -lintl when linking.
+#
+# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt
+# doesn't support GNU extensions like --check and --statistics
+#
 # Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
 # it specifies.
 #
@@ -143,6 +159,8 @@ all::
 #
 # Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
 #
+# Define NO_UNIX_SOCKETS if your system does not offer unix sockets.
+#
 # Define NO_SOCKADDR_STORAGE if your platform does not have struct
 # sockaddr_storage.
 #
@@ -227,6 +245,9 @@ all::
 #
 # Define NO_REGEX if you have no or inferior regex support in your C library.
 #
+# Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the
+# user.
+#
 # Define GETTEXT_POISON if you are debugging the choice of strings marked
 # for translation.  In a GETTEXT_POISON build, you can turn all strings marked
 # for translation into gibberish by setting the GIT_GETTEXT_POISON variable
@@ -307,6 +328,7 @@ gitexecdir = libexec/git-core
 mergetoolsdir = $(gitexecdir)/mergetools
 sharedir = $(prefix)/share
 gitwebdir = $(sharedir)/gitweb
+localedir = $(sharedir)/locale
 template_dir = share/git-core/templates
 htmldir = share/doc/git-doc
 ETC_GITCONFIG = $(sysconfdir)/gitconfig
@@ -315,7 +337,7 @@ lib = lib
 # DESTDIR=
 pathsep = :
 
-export prefix bindir sharedir sysconfdir gitwebdir
+export prefix bindir sharedir sysconfdir gitwebdir localedir
 
 CC = gcc
 AR = ar
@@ -328,6 +350,7 @@ RPMBUILD = rpmbuild
 TCL_PATH = tclsh
 TCLTK_PATH = wish
 XGETTEXT = xgettext
+MSGFMT = msgfmt
 PTHREAD_LIBS = -lpthread
 PTHREAD_CFLAGS =
 GCOV = gcov
@@ -427,14 +450,17 @@ PROGRAM_OBJS += show-index.o
 PROGRAM_OBJS += upload-pack.o
 PROGRAM_OBJS += http-backend.o
 PROGRAM_OBJS += sh-i18n--envsubst.o
+PROGRAM_OBJS += credential-store.o
 
 PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
 
 TEST_PROGRAMS_NEED_X += test-chmtime
+TEST_PROGRAMS_NEED_X += test-credential
 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-scrap-cache-tree
 TEST_PROGRAMS_NEED_X += test-genrandom
 TEST_PROGRAMS_NEED_X += test-index-version
 TEST_PROGRAMS_NEED_X += test-line-buffer
@@ -511,6 +537,7 @@ LIB_H += argv-array.h
 LIB_H += attr.h
 LIB_H += blob.h
 LIB_H += builtin.h
+LIB_H += bulk-checkin.h
 LIB_H += cache.h
 LIB_H += cache-tree.h
 LIB_H += color.h
@@ -519,12 +546,14 @@ LIB_H += compat/bswap.h
 LIB_H += compat/cygwin.h
 LIB_H += compat/mingw.h
 LIB_H += compat/obstack.h
+LIB_H += compat/terminal.h
 LIB_H += compat/win32/pthread.h
 LIB_H += compat/win32/syslog.h
 LIB_H += compat/win32/poll.h
 LIB_H += compat/win32/dirent.h
 LIB_H += connected.h
 LIB_H += convert.h
+LIB_H += credential.h
 LIB_H += csum-file.h
 LIB_H += decorate.h
 LIB_H += delta.h
@@ -560,6 +589,7 @@ LIB_H += parse-options.h
 LIB_H += patch-ids.h
 LIB_H += pkt-line.h
 LIB_H += progress.h
+LIB_H += prompt.h
 LIB_H += quote.h
 LIB_H += reflog-walk.h
 LIB_H += refs.h
@@ -600,17 +630,20 @@ LIB_OBJS += base85.o
 LIB_OBJS += bisect.o
 LIB_OBJS += blob.o
 LIB_OBJS += branch.o
+LIB_OBJS += bulk-checkin.o
 LIB_OBJS += bundle.o
 LIB_OBJS += cache-tree.o
 LIB_OBJS += color.o
 LIB_OBJS += combine-diff.o
 LIB_OBJS += commit.o
 LIB_OBJS += compat/obstack.o
+LIB_OBJS += compat/terminal.o
 LIB_OBJS += config.o
 LIB_OBJS += connect.o
 LIB_OBJS += connected.o
 LIB_OBJS += convert.o
 LIB_OBJS += copy.o
+LIB_OBJS += credential.o
 LIB_OBJS += csum-file.o
 LIB_OBJS += ctype.o
 LIB_OBJS += date.o
@@ -631,6 +664,7 @@ LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
 LIB_OBJS += gpg-interface.o
+LIB_OBJS += gettext.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
@@ -666,6 +700,7 @@ LIB_OBJS += pkt-line.o
 LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
 LIB_OBJS += progress.o
+LIB_OBJS += prompt.o
 LIB_OBJS += quote.o
 LIB_OBJS += reachable.o
 LIB_OBJS += read-cache.o
@@ -827,12 +862,15 @@ ifeq ($(uname_S),Linux)
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
+       LIBC_CONTAINS_LIBINTL = YesPlease
+       HAVE_DEV_TTY = YesPlease
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
        DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
+       LIBC_CONTAINS_LIBINTL = YesPlease
 endif
 ifeq ($(uname_S),UnixWare)
        CC = cc
@@ -887,6 +925,7 @@ ifeq ($(uname_S),Darwin)
        endif
        NO_MEMMEM = YesPlease
        USE_ST_TIMESPEC = YesPlease
+       HAVE_DEV_TTY = YesPlease
 endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
@@ -899,6 +938,7 @@ ifeq ($(uname_S),SunOS)
        NO_MKSTEMPS = YesPlease
        NO_REGEX = YesPlease
        NO_FNMATCH_CASEFOLD = YesPlease
+       NO_MSGFMT_EXTENDED_OPTIONS = YesPlease
        ifeq ($(uname_R),5.6)
                SOCKLEN_T = int
                NO_HSTRERROR = YesPlease
@@ -1022,6 +1062,7 @@ ifeq ($(uname_S),GNU)
        NO_STRLCPY=YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
+       LIBC_CONTAINS_LIBINTL = YesPlease
 endif
 ifeq ($(uname_S),IRIX)
        NO_SETENV = YesPlease
@@ -1101,6 +1142,7 @@ ifeq ($(uname_S),Windows)
        NO_SYS_POLL_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
        NO_IPV6 = YesPlease
+       NO_UNIX_SOCKETS = YesPlease
        NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
@@ -1194,6 +1236,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        NO_LIBGEN_H = YesPlease
        NO_SYS_POLL_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
+       NO_UNIX_SOCKETS = YesPlease
        NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
@@ -1238,6 +1281,7 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
        EXTLIBS += /mingw/lib/libz.a
        NO_R_TO_GCC_LINKER = YesPlease
        INTERNAL_QSORT = YesPlease
+       HAVE_LIBCHARSET_H = YesPlease
 else
        NO_CURL = YesPlease
 endif
@@ -1426,6 +1470,11 @@ endif
 ifdef NEEDS_LIBGEN
        EXTLIBS += -lgen
 endif
+ifndef NO_GETTEXT
+ifndef LIBC_CONTAINS_LIBINTL
+       EXTLIBS += -lintl
+endif
+endif
 ifdef NEEDS_SOCKET
        EXTLIBS += -lsocket
 endif
@@ -1468,9 +1517,11 @@ ifdef NO_SYMLINK_HEAD
        BASIC_CFLAGS += -DNO_SYMLINK_HEAD
 endif
 ifdef GETTEXT_POISON
-       LIB_OBJS += gettext.o
        BASIC_CFLAGS += -DGETTEXT_POISON
 endif
+ifdef NO_GETTEXT
+       BASIC_CFLAGS += -DNO_GETTEXT
+endif
 ifdef NO_STRCASESTR
        COMPAT_CFLAGS += -DNO_STRCASESTR
        COMPAT_OBJS += compat/strcasestr.o
@@ -1571,6 +1622,12 @@ ifdef NO_INET_PTON
        LIB_OBJS += compat/inet_pton.o
        BASIC_CFLAGS += -DNO_INET_PTON
 endif
+ifndef NO_UNIX_SOCKETS
+       LIB_OBJS += unix-socket.o
+       LIB_H += unix-socket.h
+       PROGRAM_OBJS += credential-cache.o
+       PROGRAM_OBJS += credential-cache--daemon.o
+endif
 
 ifdef NO_ICONV
        BASIC_CFLAGS += -DNO_ICONV
@@ -1633,6 +1690,14 @@ ifdef HAVE_PATHS_H
        BASIC_CFLAGS += -DHAVE_PATHS_H
 endif
 
+ifdef HAVE_LIBCHARSET_H
+       BASIC_CFLAGS += -DHAVE_LIBCHARSET_H
+endif
+
+ifdef HAVE_DEV_TTY
+       BASIC_CFLAGS += -DHAVE_DEV_TTY
+endif
+
 ifdef DIR_HAS_BSD_GROUP_SEMANTICS
        COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
 endif
@@ -1653,6 +1718,10 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
        export GIT_TEST_CMP_USE_COPIED_CONTEXT
 endif
 
+ifndef NO_MSGFMT_EXTENDED_OPTIONS
+       MSGFMT += --check --statistics
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
 endif
@@ -1683,6 +1752,7 @@ ifndef V
        QUIET_GEN      = @echo '   ' GEN $@;
        QUIET_LNCP     = @echo '   ' LN/CP $@;
        QUIET_XGETTEXT = @echo '   ' XGETTEXT $@;
+       QUIET_MSGFMT   = @echo '   ' MSGFMT $@;
        QUIET_GCOV     = @echo '   ' GCOV $@;
        QUIET_SP       = @echo '   ' SP $<;
        QUIET_SUBDIR0  = +@subdir=
@@ -1709,6 +1779,7 @@ bindir_SQ = $(subst ','\'',$(bindir))
 bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
 mandir_SQ = $(subst ','\'',$(mandir))
 infodir_SQ = $(subst ','\'',$(infodir))
+localedir_SQ = $(subst ','\'',$(localedir))
 gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 template_dir_SQ = $(subst ','\'',$(template_dir))
 htmldir_SQ = $(subst ','\'',$(htmldir))
@@ -1764,7 +1835,7 @@ ifndef NO_TCLTK
        $(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
 endif
 ifndef NO_PERL
-       $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+       $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all
 endif
 ifndef NO_PYTHON
        $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
@@ -1814,6 +1885,7 @@ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
     -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
     -e 's|@@DIFF@@|$(DIFF_SQ)|' \
     -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+    -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \
     -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
     -e $(BROKEN_PATH_FIX) \
     $@.sh >$@+
@@ -2066,6 +2138,9 @@ config.sp config.s config.o: EXTRA_CPPFLAGS = \
 attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \
        -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"'
 
+gettext.sp gettext.s gettext.o: EXTRA_CPPFLAGS = \
+       -DGIT_LOCALE_PATH='"$(localedir_SQ)"'
+
 http.sp http.s http.o: EXTRA_CPPFLAGS = \
        -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"'
 
@@ -2139,17 +2214,37 @@ XGETTEXT_FLAGS = \
 XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \
        --keyword=_ --keyword=N_ --keyword="Q_:1,2"
 XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell
+XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
 LOCALIZED_C := $(C_OBJ:o=c)
 LOCALIZED_SH := $(SCRIPT_SH)
+LOCALIZED_PERL := $(SCRIPT_PERL)
+
+ifdef XGETTEXT_INCLUDE_TESTS
+LOCALIZED_C += t/t0200/test.c
+LOCALIZED_SH += t/t0200/test.sh
+LOCALIZED_PERL += t/t0200/test.perl
+endif
 
 po/git.pot: $(LOCALIZED_C)
        $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ $(XGETTEXT_FLAGS_C) $(LOCALIZED_C)
        $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_SH) \
                $(LOCALIZED_SH)
+       $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_PERL) \
+               $(LOCALIZED_PERL)
        mv $@+ $@
 
 pot: po/git.pot
 
+POFILES := $(wildcard po/*.po)
+MOFILES := $(patsubst po/%.po,po/build/locale/%/LC_MESSAGES/git.mo,$(POFILES))
+
+ifndef NO_GETTEXT
+all:: $(MOFILES)
+endif
+
+po/build/locale/%/LC_MESSAGES/git.mo: po/%.po
+       $(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $<
+
 FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \
                        $(FIND) . \( -name .git -type d -prune \) \
                                -o \( -name '*.[hcS]' -type f -print \) )
@@ -2168,7 +2263,8 @@ cscope:
 
 ### Detect prefix changes
 TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\
-             $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
+             $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\
+             $(localedir_SQ)
 
 GIT-CFLAGS: FORCE
        @FLAGS='$(TRACK_CFLAGS)'; \
@@ -2205,7 +2301,9 @@ endif
 ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
        @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@
 endif
+       @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
        @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
+       @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
@@ -2320,6 +2418,11 @@ install: all
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
        $(INSTALL) -m 644 mergetools/* '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
+ifndef NO_GETTEXT
+       $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(localedir_SQ)'
+       (cd po/build/locale && $(TAR) cf - .) | \
+       (cd '$(DESTDIR_SQ)$(localedir_SQ)' && umask 022 && $(TAR) xof -)
+endif
 ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
        $(MAKE) -C gitweb install
@@ -2456,6 +2559,7 @@ clean:
        $(RM) $(TEST_PROGRAMS)
        $(RM) -r bin-wrappers
        $(RM) -r $(dep_dirs)
+       $(RM) -r po/build/
        $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
        $(RM) -r autom4te.cache
        $(RM) config.log config.mak.autogen config.mak.append config.status config.cache
index 2ae740a71e6d43ee81afdeddcb53f983f10a8fff..164bbd014a82feac48886db6f27ba85d61a059ac 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -247,7 +247,8 @@ static void parse_pathspec_arg(const char **pathspec,
 }
 
 static void parse_treeish_arg(const char **argv,
-               struct archiver_args *ar_args, const char *prefix)
+               struct archiver_args *ar_args, const char *prefix,
+               int remote)
 {
        const char *name = argv[0];
        const unsigned char *commit_sha1;
@@ -256,8 +257,17 @@ static void parse_treeish_arg(const char **argv,
        const struct commit *commit;
        unsigned char sha1[20];
 
-       if (get_sha1(name, sha1))
-               die("Not a valid object name");
+       /* Remotes are only allowed to fetch actual refs */
+       if (remote) {
+               char *ref = NULL;
+               if (!dwim_ref(name, strlen(name), sha1, &ref))
+                       die("no such ref: %s", name);
+               free(ref);
+       }
+       else {
+               if (get_sha1(name, sha1))
+                       die("Not a valid object name");
+       }
 
        commit = lookup_commit_reference_gently(sha1, 1);
        if (commit) {
@@ -414,7 +424,7 @@ int write_archive(int argc, const char **argv, const char *prefix,
                setup_git_directory();
        }
 
-       parse_treeish_arg(argv, &args, prefix);
+       parse_treeish_arg(argv, &args, prefix, remote);
        parse_pathspec_arg(argv + 1, &args);
 
        return ar->write_archive(ar, &args);
index d91a099fdd22b9131a1d2ddaf3778b645c53eca0..9971820a184d9713126c3c9f763dd8f6ec1b1a50 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -3,7 +3,6 @@
 #include "refs.h"
 #include "remote.h"
 #include "commit.h"
-#include "sequencer.h"
 
 struct tracking {
        struct refspec spec;
@@ -182,7 +181,7 @@ int validate_new_branchname(const char *name, struct strbuf *ref,
                const char *head;
                unsigned char sha1[20];
 
-               head = resolve_ref("HEAD", sha1, 0, NULL);
+               head = resolve_ref_unsafe("HEAD", sha1, 0, NULL);
                if (!is_bare_repository() && head && !strcmp(head, ref->buf))
                        die("Cannot force update the current branch.");
        }
@@ -191,7 +190,8 @@ int validate_new_branchname(const char *name, struct strbuf *ref,
 
 void create_branch(const char *head,
                   const char *name, const char *start_name,
-                  int force, int reflog, enum branch_track track)
+                  int force, int reflog, int clobber_head,
+                  enum branch_track track)
 {
        struct ref_lock *lock = NULL;
        struct commit *commit;
@@ -206,7 +206,8 @@ void create_branch(const char *head,
                explicit_tracking = 1;
 
        if (validate_new_branchname(name, &ref, force,
-                                   track == BRANCH_TRACK_OVERRIDE)) {
+                                   track == BRANCH_TRACK_OVERRIDE ||
+                                   clobber_head)) {
                if (!force)
                        dont_change_ref = 1;
                else
@@ -278,5 +279,4 @@ void remove_branch_state(void)
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
        unlink(git_path("SQUASH_MSG"));
-       remove_sequencer_state(0);
 }
index 1493f73722161cc191f0c7fd655781d8b748ed41..b99c5a369e31a85d1fff822460e69a79d8c6102b 100644 (file)
--- a/branch.h
+++ b/branch.h
@@ -13,7 +13,8 @@
  * branch for (if any).
  */
 void create_branch(const char *head, const char *name, const char *start_name,
-                  int force, int reflog, enum branch_track track);
+                  int force, int reflog,
+                  int clobber_head, enum branch_track track);
 
 /*
  * Validates that the requested branch may be created, returning the
index e94a5dc8a5557e44d35286ee5f70c7929a4d3a51..857b9c8aa85fff5764b528485a880cd9dfa95b17 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -139,6 +139,7 @@ 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_archive_writer(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);
index c59b0c98fefefc413c8330715fffcc83142d5b2d..1c42900ff8c55a94ccfd1d214567d0f64d615412 100644 (file)
@@ -13,6 +13,7 @@
 #include "diff.h"
 #include "diffcore.h"
 #include "revision.h"
+#include "bulk-checkin.h"
 
 static const char * const builtin_add_usage[] = {
        "git add [options] [--] <filepattern>...",
@@ -458,11 +459,15 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                free(seen);
        }
 
+       plug_bulk_checkin();
+
        exit_status |= add_files_to_cache(prefix, pathspec, flags);
 
        if (add_new_files)
                exit_status |= add_files(&dir, flags);
 
+       unplug_bulk_checkin();
+
  finish:
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
index b3b59db534ff0763c822ae9da571d5643a8b709a..c24dc546d0cc3f223c40c12aa20dc75eff13d4f9 100644 (file)
@@ -3587,15 +3587,12 @@ static int write_out_one_reject(struct patch *patch)
        return -1;
 }
 
-static int write_out_results(struct patch *list, int skipped_patch)
+static int write_out_results(struct patch *list)
 {
        int phase;
        int errs = 0;
        struct patch *l;
 
-       if (!list && !skipped_patch)
-               return error("No changes");
-
        for (phase = 0; phase < 2; phase++) {
                l = list;
                while (l) {
@@ -3721,6 +3718,9 @@ static int apply_patch(int fd, const char *filename, int options)
                offset += nr;
        }
 
+       if (!list && !skipped_patch)
+               die("unrecognized input");
+
        if (whitespace_error && (ws_error_action == die_on_ws_error))
                apply = 0;
 
@@ -3738,7 +3738,7 @@ static int apply_patch(int fd, const char *filename, int options)
            !apply_with_reject)
                exit(1);
 
-       if (apply && write_out_results(list, skipped_patch))
+       if (apply && write_out_results(list))
                exit(1);
 
        if (fake_ancestor)
index 80febbe420db1c75bbcf7eda6a733e7e66549790..5a67c202f06abeaa90a7547d78b536f7f2b9db24 100644 (file)
@@ -1598,7 +1598,7 @@ static const char *format_time(unsigned long time, const char *tz_str,
        int tz;
 
        if (show_raw_time) {
-               sprintf(time_buf, "%lu %s", time, tz_str);
+               snprintf(time_buf, sizeof(time_buf), "%lu %s", time, tz_str);
        }
        else {
                tz = atoi(tz_str);
index e1e486e4c51194e09df3779be254cb72855d1103..7095718c13b5c4f39186548f5ed12198a3b9e609 100644 (file)
@@ -104,6 +104,7 @@ static int branch_merged(int kind, const char *name,
         */
        struct commit *reference_rev = NULL;
        const char *reference_name = NULL;
+       void *reference_name_to_free = NULL;
        int merged;
 
        if (kind == REF_LOCAL_BRANCH) {
@@ -114,11 +115,9 @@ static int branch_merged(int kind, const char *name,
                    branch->merge &&
                    branch->merge[0] &&
                    branch->merge[0]->dst &&
-                   (reference_name =
-                    resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) {
-                       reference_name = xstrdup(reference_name);
+                   (reference_name = reference_name_to_free =
+                    resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
                        reference_rev = lookup_commit_reference(sha1);
-               }
        }
        if (!reference_rev)
                reference_rev = head_rev;
@@ -143,7 +142,7 @@ static int branch_merged(int kind, const char *name,
                                "         '%s', even though it is merged to HEAD."),
                                name, reference_name);
        }
-       free((char *)reference_name);
+       free(reference_name_to_free);
        return merged;
 }
 
@@ -253,7 +252,7 @@ static char *resolve_symref(const char *src, const char *prefix)
        int flag;
        const char *dst, *cp;
 
-       dst = resolve_ref(src, sha1, 0, &flag);
+       dst = resolve_ref_unsafe(src, sha1, 0, &flag);
        if (!(dst && (flag & REF_ISSYMREF)))
                return NULL;
        if (prefix && (cp = skip_prefix(dst, prefix)))
@@ -570,6 +569,7 @@ static void rename_branch(const char *oldname, const char *newname, int force)
        struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
        struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
        int recovery = 0;
+       int clobber_head_ok;
 
        if (!oldname)
                die(_("cannot rename the current branch while not on any."));
@@ -585,7 +585,13 @@ static void rename_branch(const char *oldname, const char *newname, int force)
                        die(_("Invalid branch name: '%s'"), oldname);
        }
 
-       validate_new_branchname(newname, &newref, force, 0);
+       /*
+        * A command like "git branch -M currentbranch currentbranch" cannot
+        * cause the worktree to become inconsistent with HEAD, so allow it.
+        */
+       clobber_head_ok = !strcmp(oldname, newname);
+
+       validate_new_branchname(newname, &newref, force, clobber_head_ok);
 
        strbuf_addf(&logmsg, "Branch: renamed %s to %s",
                 oldref.buf, newref.buf);
@@ -731,10 +737,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        track = git_branch_track;
 
-       head = resolve_ref("HEAD", head_sha1, 0, NULL);
+       head = resolve_refdup("HEAD", head_sha1, 0, NULL);
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
-       head = xstrdup(head);
        if (!strcmp(head, "HEAD")) {
                detached = 1;
        } else {
@@ -784,7 +789,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                if (kinds != REF_LOCAL_BRANCH)
                        die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
                create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
-                             force_create, reflog, track);
+                             force_create, reflog, 0, track);
        } else
                usage_with_options(builtin_branch_usage, options);
 
index b7c630287dda662630af9883655ed23bd50e7b7e..f1984d9933c526bcd2af66fd745dc64a607ac19b 100644 (file)
@@ -34,6 +34,7 @@ struct checkout_opts {
        int force_detach;
        int writeout_stage;
        int writeout_error;
+       int overwrite_ignore;
 
        /* not set by parse_options */
        int branch_exists;
@@ -114,16 +115,21 @@ static int check_stage(int stage, struct cache_entry *ce, int pos)
                return error(_("path '%s' does not have their version"), ce->name);
 }
 
-static int check_all_stages(struct cache_entry *ce, int pos)
+static int check_stages(unsigned stages, struct cache_entry *ce, int pos)
 {
-       if (ce_stage(ce) != 1 ||
-           active_nr <= pos + 2 ||
-           strcmp(active_cache[pos+1]->name, ce->name) ||
-           ce_stage(active_cache[pos+1]) != 2 ||
-           strcmp(active_cache[pos+2]->name, ce->name) ||
-           ce_stage(active_cache[pos+2]) != 3)
-               return error(_("path '%s' does not have all three versions"),
-                            ce->name);
+       unsigned seen = 0;
+       const char *name = ce->name;
+
+       while (pos < active_nr) {
+               ce = active_cache[pos];
+               if (strcmp(name, ce->name))
+                       break;
+               seen |= (1 << ce_stage(ce));
+               pos++;
+       }
+       if ((stages & seen) != stages)
+               return error(_("path '%s' does not have all necessary versions"),
+                            name);
        return 0;
 }
 
@@ -150,18 +156,27 @@ static int checkout_merged(int pos, struct checkout *state)
        int status;
        unsigned char sha1[20];
        mmbuffer_t result_buf;
+       unsigned char threeway[3][20];
+       unsigned mode = 0;
+
+       memset(threeway, 0, sizeof(threeway));
+       while (pos < active_nr) {
+               int stage;
+               stage = ce_stage(ce);
+               if (!stage || strcmp(path, ce->name))
+                       break;
+               hashcpy(threeway[stage - 1], ce->sha1);
+               if (stage == 2)
+                       mode = create_ce_mode(ce->ce_mode);
+               pos++;
+               ce = active_cache[pos];
+       }
+       if (is_null_sha1(threeway[1]) || is_null_sha1(threeway[2]))
+               return error(_("path '%s' does not have necessary versions"), path);
 
-       if (ce_stage(ce) != 1 ||
-           active_nr <= pos + 2 ||
-           strcmp(active_cache[pos+1]->name, path) ||
-           ce_stage(active_cache[pos+1]) != 2 ||
-           strcmp(active_cache[pos+2]->name, path) ||
-           ce_stage(active_cache[pos+2]) != 3)
-               return error(_("path '%s' does not have all 3 versions"), path);
-
-       read_mmblob(&ancestor, active_cache[pos]->sha1);
-       read_mmblob(&ours, active_cache[pos+1]->sha1);
-       read_mmblob(&theirs, active_cache[pos+2]->sha1);
+       read_mmblob(&ancestor, threeway[0]);
+       read_mmblob(&ours, threeway[1]);
+       read_mmblob(&theirs, threeway[2]);
 
        /*
         * NEEDSWORK: re-create conflicts from merges with
@@ -192,9 +207,7 @@ static int checkout_merged(int pos, struct checkout *state)
        if (write_sha1_file(result_buf.ptr, result_buf.size,
                            blob_type, sha1))
                die(_("Unable to add merge result for '%s'"), path);
-       ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
-                             sha1,
-                             path, 2, 0);
+       ce = make_cache_entry(mode, sha1, path, 2, 0);
        if (!ce)
                die(_("make_cache_entry failed for path '%s'"), path);
        status = checkout_entry(ce, state, NULL);
@@ -252,7 +265,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
                        } else if (stage) {
                                errs |= check_stage(stage, ce, pos);
                        } else if (opts->merge) {
-                               errs |= check_all_stages(ce, pos);
+                               errs |= check_stages((1<<2) | (1<<3), ce, pos);
                        } else {
                                errs = 1;
                                error(_("path '%s' is unmerged"), ce->name);
@@ -409,9 +422,11 @@ static int merge_working_tree(struct checkout_opts *opts,
                topts.gently = opts->merge && old->commit;
                topts.verbose_update = !opts->quiet;
                topts.fn = twoway_merge;
-               topts.dir = xcalloc(1, sizeof(*topts.dir));
-               topts.dir->flags |= DIR_SHOW_IGNORED;
-               setup_standard_excludes(topts.dir);
+               if (opts->overwrite_ignore) {
+                       topts.dir = xcalloc(1, sizeof(*topts.dir));
+                       topts.dir->flags |= DIR_SHOW_IGNORED;
+                       setup_standard_excludes(topts.dir);
+               }
                tree = parse_tree_indirect(old->commit ?
                                           old->commit->object.sha1 :
                                           EMPTY_TREE_SHA1_BIN);
@@ -540,7 +555,9 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                else
                        create_branch(old->name, opts->new_branch, new->name,
                                      opts->new_branch_force ? 1 : 0,
-                                     opts->new_branch_log, opts->track);
+                                     opts->new_branch_log,
+                                     opts->new_branch_force ? 1 : 0,
+                                     opts->track);
                new->name = opts->new_branch;
                setup_branch_path(new);
        }
@@ -565,8 +582,12 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                create_symref("HEAD", new->path, msg.buf);
                if (!opts->quiet) {
                        if (old->path && !strcmp(new->path, old->path)) {
-                               fprintf(stderr, _("Already on '%s'\n"),
-                                       new->name);
+                               if (opts->new_branch_force)
+                                       fprintf(stderr, _("Reset branch '%s'\n"),
+                                               new->name);
+                               else
+                                       fprintf(stderr, _("Already on '%s'\n"),
+                                               new->name);
                        } else if (opts->new_branch) {
                                if (opts->branch_exists)
                                        fprintf(stderr, _("Switched to and reset branch '%s'\n"), new->name);
@@ -696,17 +717,14 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
 {
        int ret = 0;
        struct branch_info old;
+       void *path_to_free;
        unsigned char rev[20];
        int flag;
        memset(&old, 0, sizeof(old));
-       old.path = resolve_ref("HEAD", rev, 0, &flag);
-       if (old.path)
-               old.path = xstrdup(old.path);
+       old.path = path_to_free = resolve_refdup("HEAD", rev, 0, &flag);
        old.commit = lookup_commit_reference_gently(rev, 1);
-       if (!(flag & REF_ISSYMREF)) {
-               free((char *)old.path);
+       if (!(flag & REF_ISSYMREF))
                old.path = NULL;
-       }
 
        if (old.path && !prefixcmp(old.path, "refs/heads/"))
                old.name = old.path + strlen("refs/heads/");
@@ -720,8 +738,10 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
        }
 
        ret = merge_working_tree(opts, &old, new);
-       if (ret)
+       if (ret) {
+               free(path_to_free);
                return ret;
+       }
 
        if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
                orphaned_commit_warning(old.commit);
@@ -729,7 +749,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
        update_refs_for_switch(opts, &old, new);
 
        ret = post_checkout_hook(old.commit, new->commit, 1);
-       free((char *)old.path);
+       free(path_to_free);
        return ret || opts->writeout_error;
 }
 
@@ -928,6 +948,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                            3),
                OPT__FORCE(&opts.force, "force checkout (throw away local modifications)"),
                OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"),
+               OPT_BOOLEAN(0, "overwrite-ignore", &opts.overwrite_ignore, "update ignored files (default)"),
                OPT_STRING(0, "conflict", &conflict_style, "style",
                           "conflict style (merge or diff3)"),
                OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
@@ -939,6 +960,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
        memset(&opts, 0, sizeof(opts));
        memset(&new, 0, sizeof(new));
+       opts.overwrite_ignore = 1;
 
        gitmodules_config();
        git_config(git_checkout_config, &opts);
@@ -1059,7 +1081,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                struct strbuf buf = STRBUF_INIT;
 
                opts.branch_exists = validate_new_branchname(opts.new_branch, &buf,
-                                                            !!opts.new_branch_force, 0);
+                                                            !!opts.new_branch_force,
+                                                            !!opts.new_branch_force);
 
                strbuf_release(&buf);
        }
index efe8b6cce5a9f2ae40c6f69755debecfb6b501ca..86db95473021bc8d0b1cc8850185e1660fd9c776 100644 (file)
@@ -84,8 +84,8 @@ static struct option builtin_clone_options[] = {
                   "directory from which templates will be used"),
        OPT_CALLBACK(0 , "reference", &option_reference, "repo",
                     "reference repository", &opt_parse_reference),
-       OPT_STRING('o', "origin", &option_origin, "branch",
-                  "use <branch> instead of 'origin' to track upstream"),
+       OPT_STRING('o', "origin", &option_origin, "name",
+                  "use <name> instead of 'origin' to track upstream"),
        OPT_STRING('b', "branch", &option_branch, "branch",
                   "checkout <branch> instead of the remote's HEAD"),
        OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
index b9c331225f0d5c960e9e03f2c92d498f8f01f74c..05353c30f292684b44b42f7f744d8eb1b7062f69 100644 (file)
@@ -98,7 +98,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
                        die_errno("git commit-tree: failed to read");
        }
 
-       if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
+       if (commit_tree(&buffer, tree_sha1, parents, commit_sha1, NULL)) {
                strbuf_release(&buffer);
                return 1;
        }
index e36e9adf87d76002784eb26107c25e5d91683229..3069041b8023224be0080f081d852dc001a81437 100644 (file)
@@ -81,7 +81,8 @@ static const char *template_file;
 static const char *author_message, *author_message_buffer;
 static char *edit_message, *use_message;
 static char *fixup_message, *squash_message;
-static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff;
+static int all, also, interactive, patch_interactive, only, amend, signoff;
+static int edit_flag = -1; /* unspecified */
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int no_post_rewrite, allow_empty_message;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
@@ -138,10 +139,10 @@ static struct option builtin_commit_options[] = {
        OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
        OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
        OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
-       OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
+       OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C/-c/--amend)"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
-       OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
+       OPT_BOOL('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 */
@@ -394,6 +395,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
                fd = hold_locked_index(&index_lock, 1);
                add_files_to_cache(also ? prefix : NULL, pathspec, 0);
                refresh_cache_or_die(refresh_flags);
+               update_main_cache_tree(1);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
                        die(_("unable to write new_index file"));
@@ -414,6 +416,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
                fd = hold_locked_index(&index_lock, 1);
                refresh_cache_or_die(refresh_flags);
                if (active_cache_changed) {
+                       update_main_cache_tree(1);
                        if (write_cache(fd, active_cache, active_nr) ||
                            commit_locked_index(&index_lock))
                                die(_("unable to write new_index file"));
@@ -862,10 +865,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
         */
        discard_cache();
        read_cache_from(index_file);
-       if (!active_cache_tree)
-               active_cache_tree = cache_tree();
-       if (cache_tree_update(active_cache_tree,
-                             active_cache, active_nr, 0, 0) < 0) {
+       if (update_main_cache_tree(0)) {
                error(_("Error building trees"));
                return 0;
        }
@@ -1020,8 +1020,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
 
        if (logfile || message.len || use_message || fixup_message)
                use_editor = 0;
-       if (edit_flag)
-               use_editor = 1;
+       if (0 <= edit_flag)
+               use_editor = edit_flag;
        if (!use_editor)
                setenv("GIT_EDITOR", ":", 1);
 
@@ -1304,7 +1304,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
 
-       head = resolve_ref("HEAD", junk_sha1, 0, NULL);
+       head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL);
        printf("[%s%s ",
                !prefixcmp(head, "refs/heads/") ?
                        head + 11 :
@@ -1485,10 +1485,14 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                exit(1);
        }
 
-       if (amend)
+       if (amend) {
                extra = read_commit_extra_headers(current_head);
+       } else {
+               struct commit_extra_header **tail = &extra;
+               append_merge_tag_headers(parents, &tail);
+       }
 
-       if (commit_tree_extended(sb.buf, active_cache_tree->sha1, parents, sha1,
+       if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1,
                                 author_ident.buf, extra)) {
                rollback_index_files();
                die(_("failed to write commit object"));
index c6bc8eb0aa6f5a6bc35c69e7893118a17813db7d..6207ecd2982761a47474b57cc945a2fc66ed84a1 100644 (file)
@@ -556,11 +556,16 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
                        continue;
                }
                else {
-                       int order = path_match(ref->name, nr_match, match);
-                       if (order) {
-                               return_refs[order-1] = ref;
-                               continue; /* we will link it later */
+                       int i;
+                       for (i = 0; i < nr_match; i++) {
+                               if (!strcmp(ref->name, match[i])) {
+                                       match[i][0] = '\0';
+                                       return_refs[i] = ref;
+                                       break;
+                               }
                        }
+                       if (i < nr_match)
+                               continue; /* we will link it later */
                }
                free(ref);
        }
@@ -976,7 +981,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
                                   args.verbose ? CONNECT_VERBOSE : 0);
        }
 
-       get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
+       get_remote_heads(fd[0], &ref, 0, NULL);
 
        ref = fetch_pack(&args, fd, conn, ref, dest,
                nr_heads, heads, pack_lockfile_ptr);
index 494a7f9976f870a28dbbbcb4196954a827f6bfdb..33ad3aad2c99116a237d7e7d11dab22fcf4295b1 100644 (file)
@@ -240,23 +240,23 @@ static int s_update_ref(const char *action,
 
 static int update_local_ref(struct ref *ref,
                            const char *remote,
-                           char *display)
+                           struct strbuf *display)
 {
        struct commit *current = NULL, *updated;
        enum object_type type;
        struct branch *current_branch = branch_get(NULL);
        const char *pretty_ref = prettify_refname(ref->name);
 
-       *display = 0;
        type = sha1_object_info(ref->new_sha1, NULL);
        if (type < 0)
                die(_("object %s not found"), sha1_to_hex(ref->new_sha1));
 
        if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
                if (verbosity > 0)
-                       sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH,
-                               _("[up to date]"), REFCOL_WIDTH, remote,
-                               pretty_ref);
+                       strbuf_addf(display, "= %-*s %-*s -> %s",
+                                   TRANSPORT_SUMMARY_WIDTH,
+                                   _("[up to date]"), REFCOL_WIDTH,
+                                   remote, pretty_ref);
                return 0;
        }
 
@@ -268,9 +268,10 @@ static int update_local_ref(struct ref *ref,
                 * If this is the head, and it's not okay to update
                 * the head, and the old value of the head isn't empty...
                 */
-               sprintf(display, _("! %-*s %-*s -> %s  (can't fetch in current branch)"),
-                       TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote,
-                       pretty_ref);
+               strbuf_addf(display,
+                           _("! %-*s %-*s -> %s  (can't fetch in current branch)"),
+                           TRANSPORT_SUMMARY_WIDTH, _("[rejected]"),
+                           REFCOL_WIDTH, remote, pretty_ref);
                return 1;
        }
 
@@ -278,9 +279,11 @@ static int update_local_ref(struct ref *ref,
            !prefixcmp(ref->name, "refs/tags/")) {
                int r;
                r = s_update_ref("updating tag", ref, 0);
-               sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-',
-                       TRANSPORT_SUMMARY_WIDTH, _("[tag update]"), REFCOL_WIDTH, remote,
-                       pretty_ref, r ? _("  (unable to update local ref)") : "");
+               strbuf_addf(display, "%c %-*s %-*s -> %s%s",
+                           r ? '!' : '-',
+                           TRANSPORT_SUMMARY_WIDTH, _("[tag update]"),
+                           REFCOL_WIDTH, remote, pretty_ref,
+                           r ? _("  (unable to update local ref)") : "");
                return r;
        }
 
@@ -303,9 +306,11 @@ static int update_local_ref(struct ref *ref,
                }
 
                r = s_update_ref(msg, ref, 0);
-               sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*',
-                       TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref,
-                       r ? _("  (unable to update local ref)") : "");
+               strbuf_addf(display, "%c %-*s %-*s -> %s%s",
+                           r ? '!' : '*',
+                           TRANSPORT_SUMMARY_WIDTH, what,
+                           REFCOL_WIDTH, remote, pretty_ref,
+                           r ? _("  (unable to update local ref)") : "");
                return r;
        }
 
@@ -319,9 +324,11 @@ static int update_local_ref(struct ref *ref,
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(ref->new_sha1);
                r = s_update_ref("fast-forward", ref, 1);
-               sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
-                       TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
-                       pretty_ref, r ? _("  (unable to update local ref)") : "");
+               strbuf_addf(display, "%c %-*s %-*s -> %s%s",
+                           r ? '!' : ' ',
+                           TRANSPORT_SUMMARY_WIDTH, quickref,
+                           REFCOL_WIDTH, remote, pretty_ref,
+                           r ? _("  (unable to update local ref)") : "");
                return r;
        } else if (force || ref->force) {
                char quickref[84];
@@ -333,15 +340,17 @@ static int update_local_ref(struct ref *ref,
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(ref->new_sha1);
                r = s_update_ref("forced-update", ref, 1);
-               sprintf(display, "%c %-*s %-*s -> %s  (%s)", r ? '!' : '+',
-                       TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
-                       pretty_ref,
-                       r ? _("unable to update local ref") : _("forced update"));
+               strbuf_addf(display, "%c %-*s %-*s -> %s  (%s)",
+                           r ? '!' : '+',
+                           TRANSPORT_SUMMARY_WIDTH, quickref,
+                           REFCOL_WIDTH, remote, pretty_ref,
+                           r ? _("unable to update local ref") : _("forced update"));
                return r;
        } else {
-               sprintf(display, "! %-*s %-*s -> %s  %s",
-                       TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote,
-                       pretty_ref, _("(non-fast-forward)"));
+               strbuf_addf(display, "! %-*s %-*s -> %s  %s",
+                           TRANSPORT_SUMMARY_WIDTH, _("[rejected]"),
+                           REFCOL_WIDTH, remote, pretty_ref,
+                           _("(non-fast-forward)"));
                return 1;
        }
 }
@@ -363,8 +372,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
 {
        FILE *fp;
        struct commit *commit;
-       int url_len, i, note_len, shown_url = 0, rc = 0;
-       char note[1024];
+       int url_len, i, shown_url = 0, rc = 0;
+       struct strbuf note = STRBUF_INIT;
        const char *what, *kind;
        struct ref *rm;
        char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
@@ -427,18 +436,16 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                if (4 < i && !strncmp(".git", url + i - 3, 4))
                        url_len = i - 3;
 
-               note_len = 0;
+               strbuf_reset(&note);
                if (*what) {
                        if (*kind)
-                               note_len += sprintf(note + note_len, "%s ",
-                                                   kind);
-                       note_len += sprintf(note + note_len, "'%s' of ", what);
+                               strbuf_addf(&note, "%s ", kind);
+                       strbuf_addf(&note, "'%s' of ", what);
                }
-               note[note_len] = '\0';
                fprintf(fp, "%s\t%s\t%s",
                        sha1_to_hex(rm->old_sha1),
                        rm->merge ? "" : "not-for-merge",
-                       note);
+                       note.buf);
                for (i = 0; i < url_len; ++i)
                        if ('\n' == url[i])
                                fputs("\\n", fp);
@@ -446,21 +453,24 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                                fputc(url[i], fp);
                fputc('\n', fp);
 
+               strbuf_reset(&note);
                if (ref) {
-                       rc |= update_local_ref(ref, what, note);
+                       rc |= update_local_ref(ref, what, &note);
                        free(ref);
                } else
-                       sprintf(note, "* %-*s %-*s -> FETCH_HEAD",
-                               TRANSPORT_SUMMARY_WIDTH, *kind ? kind : "branch",
-                                REFCOL_WIDTH, *what ? what : "HEAD");
-               if (*note) {
+                       strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD",
+                                   TRANSPORT_SUMMARY_WIDTH,
+                                   *kind ? kind : "branch",
+                                   REFCOL_WIDTH,
+                                   *what ? what : "HEAD");
+               if (note.len) {
                        if (verbosity >= 0 && !shown_url) {
                                fprintf(stderr, _("From %.*s\n"),
                                                url_len, url);
                                shown_url = 1;
                        }
                        if (verbosity >= 0)
-                               fprintf(stderr, " %s\n", note);
+                               fprintf(stderr, " %s\n", note.buf);
                }
        }
 
@@ -470,6 +480,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                      "branches"), remote_name);
 
  abort:
+       strbuf_release(&note);
        free(url);
        fclose(fp);
        return rc;
index bdfa0ea05d99089937b9e753cf85a8489a5e27cb..c81a7fef2680620d521e118d60e8c59893d59234 100644 (file)
@@ -372,14 +372,15 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
        int i = 0, pos = 0;
        unsigned char head_sha1[20];
        const char *current_branch;
+       void *current_branch_to_free;
 
        /* get current branch */
-       current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
+       current_branch = current_branch_to_free =
+               resolve_refdup("HEAD", head_sha1, 1, NULL);
        if (!current_branch)
                die("No current branch");
        if (!prefixcmp(current_branch, "refs/heads/"))
                current_branch += 11;
-       current_branch = xstrdup(current_branch);
 
        /* get a line */
        while (pos < in->len) {
@@ -421,7 +422,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
        }
 
        strbuf_complete_line(out);
-       free((char *)current_branch);
+       free(current_branch_to_free);
        return 0;
 }
 
index d90e5d2b29f9ac72a104d6308c04497991a03c17..b01d76a24323e86e9c9cbf1cd3adc9c0d1b2c6d8 100644 (file)
@@ -628,11 +628,8 @@ static void populate_value(struct refinfo *ref)
 
        if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
                unsigned char unused1[20];
-               const char *symref;
-               symref = resolve_ref(ref->refname, unused1, 1, NULL);
-               if (symref)
-                       ref->symref = xstrdup(symref);
-               else
+               ref->symref = resolve_refdup(ref->refname, unused1, 1, NULL);
+               if (!ref->symref)
                        ref->symref = "";
        }
 
index 30d0dc82f053a5cbdfabcf474be816484ef3ca2b..8c479a791b792ebda334a7e3816523af3802b5bc 100644 (file)
@@ -563,7 +563,7 @@ static int fsck_head_link(void)
        if (verbose)
                fprintf(stderr, "Checking HEAD link\n");
 
-       head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag);
+       head_points_at = resolve_ref_unsafe("HEAD", head_sha1, 0, &flag);
        if (!head_points_at)
                return error("Invalid HEAD");
        if (!strcmp(head_points_at, "HEAD"))
index d07554c8844a9b7dd3d4ae2b5efe2cbde623e4af..0dacb8b79c57cae2b789eb84d7cfbdb1654ba52f 100644 (file)
@@ -351,7 +351,7 @@ static void separate_git_dir(const char *git_dir)
                else if (S_ISDIR(st.st_mode))
                        src = git_link;
                else
-                       die(_("unable to handle file type %d"), st.st_mode);
+                       die(_("unable to handle file type %d"), (int)st.st_mode);
 
                if (rename(src, git_dir))
                        die_errno(_("unable to move %s to %s"), src, git_dir);
index 4395f3e47168b2b6274026867bddb472dc244f36..7d1f6f88a0e0f76bc8ee35155a532b641c14a845 100644 (file)
@@ -73,8 +73,6 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse
 
 static void cmd_log_init_defaults(struct rev_info *rev)
 {
-       rev->abbrev = DEFAULT_ABBREV;
-       rev->commit_format = CMIT_FMT_DEFAULT;
        if (fmt_pretty)
                get_commit_format(fmt_pretty, rev);
        rev->verbose_header = 1;
@@ -1040,7 +1038,7 @@ static char *find_branch_name(struct rev_info *rev)
        if (positive < 0)
                return NULL;
        strbuf_addf(&buf, "refs/heads/%s", rev->cmdline.rev[positive].name);
-       branch = resolve_ref(buf.buf, branch_sha1, 1, NULL);
+       branch = resolve_ref_unsafe(buf.buf, branch_sha1, 1, NULL);
        if (!branch ||
            prefixcmp(branch, "refs/heads/") ||
            hashcmp(rev->cmdline.rev[positive].item->sha1, branch_sha1))
@@ -1268,7 +1266,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 
                        rev.pending.objects[0].item->flags |= UNINTERESTING;
                        add_head_to_pending(&rev);
-                       ref = resolve_ref("HEAD", sha1, 1, NULL);
+                       ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
                        if (ref && !prefixcmp(ref, "refs/heads/"))
                                branch_name = xstrdup(ref + strlen("refs/heads/"));
                        else
index a1c85344b2b54f957ea69ace864ea99b7d295405..a896165790d0c6cf05858f229cfaf05e62f67a9c 100644 (file)
@@ -49,6 +49,7 @@ static int show_diffstat = 1, shortlog_len = -1, squash;
 static int option_commit = 1, allow_fast_forward = 1;
 static int fast_forward_only, option_edit;
 static int allow_trivial = 1, have_message;
+static int overwrite_ignore = 1;
 static struct strbuf merge_msg;
 static struct commit_list *remoteheads;
 static struct strategy **use_strategies;
@@ -208,6 +209,7 @@ static struct option builtin_merge_options[] = {
        OPT_BOOLEAN(0, "abort", &abort_current_merge,
                "abort the current in-progress merge"),
        OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
+       OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"),
        OPT_END()
 };
 
@@ -766,10 +768,12 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote
        memset(&trees, 0, sizeof(trees));
        memset(&opts, 0, sizeof(opts));
        memset(&t, 0, sizeof(t));
-       memset(&dir, 0, sizeof(dir));
-       dir.flags |= DIR_SHOW_IGNORED;
-       setup_standard_excludes(&dir);
-       opts.dir = &dir;
+       if (overwrite_ignore) {
+               memset(&dir, 0, sizeof(dir));
+               dir.flags |= DIR_SHOW_IGNORED;
+               setup_standard_excludes(&dir);
+               opts.dir = &dir;
+       }
 
        opts.head_idx = 1;
        opts.src_index = &the_index;
@@ -906,7 +910,8 @@ static int merge_trivial(struct commit *head)
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
        prepare_to_commit();
-       commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
+       if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL))
+               die(_("failed to write commit object"));
        finish(head, result_commit, "In-index merge");
        drop_save();
        return 0;
@@ -937,7 +942,8 @@ static int finish_automerge(struct commit *head,
        strbuf_addch(&merge_msg, '\n');
        prepare_to_commit();
        free_commit_list(remoteheads);
-       commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
+       if (commit_tree(&merge_msg, result_tree, parents, result_commit, NULL))
+               die(_("failed to write commit object"));
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
        finish(head, result_commit, buf.buf);
        strbuf_release(&buf);
@@ -1096,6 +1102,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        struct commit_list *common = NULL;
        const char *best_strategy = NULL, *wt_strategy = NULL;
        struct commit_list **remotes = &remoteheads;
+       void *branch_to_free;
 
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_merge_usage, builtin_merge_options);
@@ -1104,12 +1111,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
-       branch = resolve_ref("HEAD", head_sha1, 0, &flag);
-       if (branch) {
-               if (!prefixcmp(branch, "refs/heads/"))
-                       branch += 11;
-               branch = xstrdup(branch);
-       }
+       branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag);
+       if (branch && !prefixcmp(branch, "refs/heads/"))
+               branch += 11;
        if (!branch || is_null_sha1(head_sha1))
                head_commit = NULL;
        else
@@ -1520,6 +1524,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                ret = suggest_conflicts(option_renormalize);
 
 done:
-       free((char *)branch);
+       free(branch_to_free);
        return ret;
 }
index 5efe6c5760c43d0059a940ab9d4610b97092c8bd..2a144b011caa8ecb70f55976bdec60cae89fad9e 100644 (file)
@@ -59,6 +59,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
        int i, newfd;
        int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
        struct option builtin_mv_options[] = {
+               OPT__VERBOSE(&verbose, "be verbose"),
                OPT__DRY_RUN(&show_only, "dry run"),
                OPT__FORCE(&force, "force move/rename even if target exists"),
                OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
@@ -93,7 +94,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                destination = copy_pathspec(dest_path[0], argv, argc, 1);
        } else {
                if (argc != 1)
-                       usage_with_options(builtin_mv_usage, builtin_mv_options);
+                       die("destination '%s' is not a directory", dest_path[0]);
                destination = dest_path;
        }
 
@@ -176,7 +177,8 @@ 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)) {
-                                       warning(_("%s; will overwrite!"), bad);
+                                       if (verbose)
+                                               warning(_("overwriting '%s'"), dst);
                                        bad = NULL;
                                } else
                                        bad = _("Cannot overwrite");
index 10b8bc7ad9c392d9dad9e06cf2d1b3ae3f7fd001..3644d140ece9f2cfc6381e1a60c5b4fc8f645e2a 100644 (file)
@@ -301,12 +301,12 @@ void commit_notes(struct notes_tree *t, const char *msg)
                return; /* don't have to commit an unchanged tree */
 
        /* Prepare commit message and reflog message */
-       strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
        strbuf_addstr(&buf, msg);
        if (buf.buf[buf.len - 1] != '\n')
                strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
 
-       create_notes_commit(t, NULL, buf.buf + 7, commit_sha1);
+       create_notes_commit(t, NULL, &buf, commit_sha1);
+       strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
        update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
 
        strbuf_release(&buf);
@@ -804,6 +804,7 @@ static int merge_commit(struct notes_merge_options *o)
        struct notes_tree *t;
        struct commit *partial;
        struct pretty_print_context pretty_ctx;
+       void *local_ref_to_free;
        int ret;
 
        /*
@@ -826,10 +827,10 @@ static int merge_commit(struct notes_merge_options *o)
        t = xcalloc(1, sizeof(struct notes_tree));
        init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
 
-       o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, NULL);
+       o->local_ref = local_ref_to_free =
+               resolve_refdup("NOTES_MERGE_REF", sha1, 0, NULL);
        if (!o->local_ref)
                die("Failed to resolve NOTES_MERGE_REF");
-       o->local_ref = xstrdup(o->local_ref);
 
        if (notes_merge_commit(o, t, partial, sha1))
                die("Failed to finalize notes merge");
@@ -846,7 +847,7 @@ static int merge_commit(struct notes_merge_options *o)
        free_notes(t);
        strbuf_release(&msg);
        ret = merge_abort(o);
-       free((char *)o->local_ref);
+       free(local_ref_to_free);
        return ret;
 }
 
index b1895aaaa1520ef910504c3beee685f95e72ec6b..96c1680976fc653a1cd7a9dd81a567885a5b0050 100644 (file)
@@ -76,7 +76,7 @@ static struct pack_idx_option pack_idx_opts;
 static const char *base_name;
 static int progress = 1;
 static int window = 10;
-static unsigned long pack_size_limit, pack_size_limit_cfg;
+static unsigned long pack_size_limit;
 static int depth = 50;
 static int delta_search_threads;
 static int pack_to_stdout;
@@ -638,7 +638,6 @@ static void write_pack_file(void)
        uint32_t i = 0, j;
        struct sha1file *f;
        off_t offset;
-       struct pack_header hdr;
        uint32_t nr_remaining = nr_result;
        time_t last_mtime = 0;
        struct object_entry **write_order;
@@ -652,22 +651,14 @@ static void write_pack_file(void)
                unsigned char sha1[20];
                char *pack_tmp_name = NULL;
 
-               if (pack_to_stdout) {
+               if (pack_to_stdout)
                        f = sha1fd_throughput(1, "<stdout>", progress_state);
-               } else {
-                       char tmpname[PATH_MAX];
-                       int fd;
-                       fd = odb_mkstemp(tmpname, sizeof(tmpname),
-                                        "pack/tmp_pack_XXXXXX");
-                       pack_tmp_name = xstrdup(tmpname);
-                       f = sha1fd(fd, pack_tmp_name);
-               }
-
-               hdr.hdr_signature = htonl(PACK_SIGNATURE);
-               hdr.hdr_version = htonl(PACK_VERSION);
-               hdr.hdr_entries = htonl(nr_remaining);
-               sha1write(f, &hdr, sizeof(hdr));
-               offset = sizeof(hdr);
+               else
+                       f = create_tmp_packfile(&pack_tmp_name);
+
+               offset = write_pack_header(f, nr_remaining);
+               if (!offset)
+                       die_errno("unable to write pack header");
                nr_written = 0;
                for (; i < nr_objects; i++) {
                        struct object_entry *e = write_order[i];
@@ -693,20 +684,8 @@ static void write_pack_file(void)
 
                if (!pack_to_stdout) {
                        struct stat st;
-                       const char *idx_tmp_name;
                        char tmpname[PATH_MAX];
 
-                       idx_tmp_name = write_idx_file(NULL, written_list, nr_written,
-                                                     &pack_idx_opts, sha1);
-
-                       snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
-                                base_name, sha1_to_hex(sha1));
-                       free_pack_by_name(tmpname);
-                       if (adjust_shared_perm(pack_tmp_name))
-                               die_errno("unable to make temporary pack file readable");
-                       if (rename(pack_tmp_name, tmpname))
-                               die_errno("unable to rename temporary pack file");
-
                        /*
                         * Packs are runtime accessed in their mtime
                         * order since newer packs are more likely to contain
@@ -714,28 +693,27 @@ static void write_pack_file(void)
                         * packs then we should modify the mtime of later ones
                         * to preserve this property.
                         */
-                       if (stat(tmpname, &st) < 0) {
+                       if (stat(pack_tmp_name, &st) < 0) {
                                warning("failed to stat %s: %s",
-                                       tmpname, strerror(errno));
+                                       pack_tmp_name, strerror(errno));
                        } else if (!last_mtime) {
                                last_mtime = st.st_mtime;
                        } else {
                                struct utimbuf utb;
                                utb.actime = st.st_atime;
                                utb.modtime = --last_mtime;
-                               if (utime(tmpname, &utb) < 0)
+                               if (utime(pack_tmp_name, &utb) < 0)
                                        warning("failed utime() on %s: %s",
                                                tmpname, strerror(errno));
                        }
 
-                       snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
-                                base_name, sha1_to_hex(sha1));
-                       if (adjust_shared_perm(idx_tmp_name))
-                               die_errno("unable to make temporary index file readable");
-                       if (rename(idx_tmp_name, tmpname))
-                               die_errno("unable to rename temporary index file");
-
-                       free((void *) idx_tmp_name);
+                       /* Enough space for "-<sha-1>.pack"? */
+                       if (sizeof(tmpname) <= strlen(base_name) + 50)
+                               die("pack base name '%s' too long", base_name);
+                       snprintf(tmpname, sizeof(tmpname), "%s-", base_name);
+                       finish_tmp_packfile(tmpname, pack_tmp_name,
+                                           written_list, nr_written,
+                                           &pack_idx_opts, sha1);
                        free(pack_tmp_name);
                        puts(sha1_to_hex(sha1));
                }
@@ -2098,10 +2076,6 @@ static int git_pack_config(const char *k, const char *v, void *cb)
                            pack_idx_opts.version);
                return 0;
        }
-       if (!strcmp(k, "pack.packsizelimit")) {
-               pack_size_limit_cfg = git_config_ulong(k, v);
-               return 0;
-       }
        return git_default_config(k, v, cb);
 }
 
index b6d957cb0d2659f2207287598b3566ed37a35ae0..d2dcb7e4af259a43a539f83ab5961472c594c99a 100644 (file)
@@ -37,6 +37,7 @@ static int prefer_ofs_delta = 1;
 static int auto_update_server_info;
 static int auto_gc = 1;
 static const char *head_name;
+static void *head_name_to_free;
 static int sent_capabilities;
 
 static enum deny_action parse_deny_action(const char *var, const char *value)
@@ -571,7 +572,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
        int flag;
 
        strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
-       dst_name = resolve_ref(buf.buf, sha1, 0, &flag);
+       dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag);
        strbuf_release(&buf);
 
        if (!(flag & REF_ISSYMREF))
@@ -695,10 +696,8 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
 
        check_aliased_updates(commands);
 
-       free((char *)head_name);
-       head_name = resolve_ref("HEAD", sha1, 0, NULL);
-       if (head_name)
-               head_name = xstrdup(head_name);
+       free(head_name_to_free);
+       head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL);
 
        for (cmd = commands; cmd; cmd = cmd->next)
                if (!cmd->skip_update)
index 407abfb0f7545374f1d4c0bcdef22effe5ca1bea..583eec90e0b69a4d47a819e70352d250bae3bcef 100644 (file)
@@ -573,7 +573,7 @@ static int read_remote_branches(const char *refname,
        strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
        if (!prefixcmp(refname, buf.buf)) {
                item = string_list_append(rename->remote_branches, xstrdup(refname));
-               symref = resolve_ref(refname, orig_sha1, 1, &flag);
+               symref = resolve_ref_unsafe(refname, orig_sha1, 1, &flag);
                if (flag & REF_ISSYMREF)
                        item->util = xstrdup(symref);
                else
index 811e8e252c1c6a54e65179557203daf2bc42bdb9..8c2c1d52a227334a3d6456bf0989cd561628ffa0 100644 (file)
@@ -43,6 +43,7 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
        int nr = 1;
        int newfd;
        struct tree_desc desc[2];
+       struct tree *tree;
        struct unpack_trees_options opts;
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 
@@ -84,6 +85,12 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
                return error(_("Failed to find tree of %s."), sha1_to_hex(sha1));
        if (unpack_trees(nr, desc, &opts))
                return -1;
+
+       if (reset_type == MIXED || reset_type == HARD) {
+               tree = parse_tree_indirect(sha1);
+               prime_cache_tree(&active_cache_tree, tree);
+       }
+
        if (write_cache(newfd, active_cache, active_nr) ||
            commit_locked_index(lock))
                return error(_("Could not write new index file."));
index 1ea525c10e4c00b66006bf46465ebd490823f25f..fce3f929818d6a7faac5933d585a5217e564f6ba 100644 (file)
@@ -60,13 +60,14 @@ struct replay_opts {
        int allow_rerere_auto;
 
        int mainline;
-       int commit_argc;
-       const char **commit_argv;
 
        /* Merge strategy */
        const char *strategy;
        const char **xopts;
        size_t xopts_nr, xopts_alloc;
+
+       /* Only used by REPLAY_NONE */
+       struct rev_info *revs;
 };
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
@@ -169,9 +170,9 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                        die(_("program error"));
        }
 
-       opts->commit_argc = parse_options(argc, argv, NULL, options, usage_str,
-                                       PARSE_OPT_KEEP_ARGV0 |
-                                       PARSE_OPT_KEEP_UNKNOWN);
+       argc = parse_options(argc, argv, NULL, options, usage_str,
+                       PARSE_OPT_KEEP_ARGV0 |
+                       PARSE_OPT_KEEP_UNKNOWN);
 
        /* Check for incompatible subcommands */
        verify_opt_mutually_compatible(me,
@@ -213,9 +214,6 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                                NULL);
        }
 
-       else if (opts->commit_argc < 2)
-               usage_with_options(usage_str, options);
-
        if (opts->allow_ff)
                verify_opt_compatible(me, "--ff",
                                "--signoff", opts->signoff,
@@ -223,7 +221,20 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                                "-x", opts->record_origin,
                                "--edit", opts->edit,
                                NULL);
-       opts->commit_argv = argv;
+
+       if (opts->subcommand != REPLAY_NONE) {
+               opts->revs = NULL;
+       } else {
+               opts->revs = xmalloc(sizeof(*opts->revs));
+               init_revisions(opts->revs, NULL);
+               opts->revs->no_walk = 1;
+               if (argc < 2)
+                       usage_with_options(usage_str, options);
+               argc = setup_revisions(argc, argv, opts->revs, NULL);
+       }
+
+       if (argc > 1)
+               usage_with_options(usage_str, options);
 }
 
 struct commit_message {
@@ -631,23 +642,15 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
        return res;
 }
 
-static void prepare_revs(struct rev_info *revs, struct replay_opts *opts)
+static void prepare_revs(struct replay_opts *opts)
 {
-       int argc;
-
-       init_revisions(revs, NULL);
-       revs->no_walk = 1;
        if (opts->action != REVERT)
-               revs->reverse = 1;
-
-       argc = setup_revisions(opts->commit_argc, opts->commit_argv, revs, NULL);
-       if (argc > 1)
-               usage(*revert_or_cherry_pick_usage(opts));
+               opts->revs->reverse ^= 1;
 
-       if (prepare_revision_walk(revs))
+       if (prepare_revision_walk(opts->revs))
                die(_("revision walk setup failed"));
 
-       if (!revs->commits)
+       if (!opts->revs->commits)
                die(_("empty commit set passed"));
 }
 
@@ -844,14 +847,13 @@ static void read_populate_opts(struct replay_opts **opts_ptr)
 static void walk_revs_populate_todo(struct commit_list **todo_list,
                                struct replay_opts *opts)
 {
-       struct rev_info revs;
        struct commit *commit;
        struct commit_list **next;
 
-       prepare_revs(&revs, opts);
+       prepare_revs(opts);
 
        next = todo_list;
-       while ((commit = get_revision(&revs)))
+       while ((commit = get_revision(opts->revs)))
                next = commit_list_append(commit, next);
 }
 
@@ -901,7 +903,7 @@ static int rollback_single_pick(void)
        if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
            !file_exists(git_path("REVERT_HEAD")))
                return error(_("no cherry-pick or revert in progress"));
-       if (!resolve_ref("HEAD", head_sha1, 0, NULL))
+       if (read_ref_full("HEAD", head_sha1, 0, NULL))
                return error(_("cannot resolve HEAD"));
        if (is_null_sha1(head_sha1))
                return error(_("cannot abort from a branch yet to be born"));
@@ -942,7 +944,7 @@ static int sequencer_rollback(struct replay_opts *opts)
        }
        if (reset_for_rollback(sha1))
                goto fail;
-       remove_sequencer_state(1);
+       remove_sequencer_state();
        strbuf_release(&buf);
        return 0;
 fail:
@@ -1016,33 +1018,64 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
        for (cur = todo_list; cur; cur = cur->next) {
                save_todo(cur, opts);
                res = do_pick_commit(cur->item, opts);
-               if (res) {
-                       if (!cur->next)
-                               /*
-                                * An error was encountered while
-                                * picking the last commit; the
-                                * sequencer state is useless now --
-                                * the user simply needs to resolve
-                                * the conflict and commit
-                                */
-                               remove_sequencer_state(0);
+               if (res)
                        return res;
-               }
        }
 
        /*
         * Sequence of picks finished successfully; cleanup by
         * removing the .git/sequencer directory
         */
-       remove_sequencer_state(1);
+       remove_sequencer_state();
        return 0;
 }
 
+static int continue_single_pick(void)
+{
+       const char *argv[] = { "commit", NULL };
+
+       if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
+           !file_exists(git_path("REVERT_HEAD")))
+               return error(_("no cherry-pick or revert in progress"));
+       return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int sequencer_continue(struct replay_opts *opts)
+{
+       struct commit_list *todo_list = NULL;
+
+       if (!file_exists(git_path(SEQ_TODO_FILE)))
+               return continue_single_pick();
+       read_populate_opts(&opts);
+       read_populate_todo(&todo_list, opts);
+
+       /* Verify that the conflict has been resolved */
+       if (file_exists(git_path("CHERRY_PICK_HEAD")) ||
+           file_exists(git_path("REVERT_HEAD"))) {
+               int ret = continue_single_pick();
+               if (ret)
+                       return ret;
+       }
+       if (index_differs_from("HEAD", 0))
+               return error_dirty_index(opts);
+       todo_list = todo_list->next;
+       return pick_commits(todo_list, opts);
+}
+
+static int single_pick(struct commit *cmit, struct replay_opts *opts)
+{
+       setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+       return do_pick_commit(cmit, opts);
+}
+
 static int pick_revisions(struct replay_opts *opts)
 {
        struct commit_list *todo_list = NULL;
        unsigned char sha1[20];
 
+       if (opts->subcommand == REPLAY_NONE)
+               assert(opts->revs);
+
        read_and_refresh_cache(opts);
 
        /*
@@ -1051,21 +1084,32 @@ static int pick_revisions(struct replay_opts *opts)
         * one that is being continued
         */
        if (opts->subcommand == REPLAY_REMOVE_STATE) {
-               remove_sequencer_state(1);
+               remove_sequencer_state();
                return 0;
        }
        if (opts->subcommand == REPLAY_ROLLBACK)
                return sequencer_rollback(opts);
-       if (opts->subcommand == REPLAY_CONTINUE) {
-               if (!file_exists(git_path(SEQ_TODO_FILE)))
-                       return error(_("No %s in progress"), action_name(opts));
-               read_populate_opts(&opts);
-               read_populate_todo(&todo_list, opts);
-
-               /* Verify that the conflict has been resolved */
-               if (!index_differs_from("HEAD", 0))
-                       todo_list = todo_list->next;
-               return pick_commits(todo_list, opts);
+       if (opts->subcommand == REPLAY_CONTINUE)
+               return sequencer_continue(opts);
+
+       /*
+        * If we were called as "git cherry-pick <commit>", just
+        * cherry-pick/revert it, set CHERRY_PICK_HEAD /
+        * REVERT_HEAD, and don't touch the sequencer state.
+        * This means it is possible to cherry-pick in the middle
+        * of a cherry-pick sequence.
+        */
+       if (opts->revs->cmdline.nr == 1 &&
+           opts->revs->cmdline.rev->whence == REV_CMD_REV &&
+           opts->revs->no_walk &&
+           !opts->revs->cmdline.rev->flags) {
+               struct commit *cmit;
+               if (prepare_revision_walk(opts->revs))
+                       die(_("revision walk setup failed"));
+               cmit = get_revision(opts->revs);
+               if (!cmit || get_revision(opts->revs))
+                       die("BUG: expected exactly one commit from walk");
+               return single_pick(cmit, opts);
        }
 
        /*
index e0b8030f2b31ee337bc2d2e0925788ec6cd92e0f..cd1115ffc687c642acd4bda09b1ea7976e1e0478 100644 (file)
@@ -494,8 +494,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
 
        memset(&extra_have, 0, sizeof(extra_have));
 
-       get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL,
-                        &extra_have);
+       get_remote_heads(fd[0], &remote_refs, REF_NORMAL, &extra_have);
 
        transport_verify_remote_names(nr_refspecs, refspecs);
 
index 4b480d7c7ca6c6258a5cd82cfc88df62cd0d218f..a59e088cf59215d2d196dda530ad43ef0596b6be 100644 (file)
@@ -726,10 +726,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 
                if (ac == 0) {
                        static const char *fake_av[2];
-                       const char *refname;
 
-                       refname = resolve_ref("HEAD", sha1, 1, NULL);
-                       fake_av[0] = xstrdup(refname);
+                       fake_av[0] = resolve_refdup("HEAD", sha1, 1, NULL);
                        fake_av[1] = NULL;
                        av = fake_av;
                        ac = 1;
@@ -791,7 +789,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                }
        }
 
-       head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
+       head_p = resolve_ref_unsafe("HEAD", head_sha1, 1, NULL);
        if (head_p) {
                head_len = strlen(head_p);
                memcpy(head, head_p, head_len + 1);
index 1288ffcc52530f8ef9561acd2eb2ec5322c9e230..f16986c0ae811f6b998d20be39da1af6095fcfe8 100644 (file)
@@ -75,7 +75,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix)
                                !strcmp(argv[1], "--strip-comments")))
                strip_comments = 1;
        else if (argc > 1)
-               usage("git stripspace [-s | --strip-comments] < <stream>");
+               usage("git stripspace [-s | --strip-comments] < input");
 
        if (strbuf_read(&buf, 0, 1024) < 0)
                die_errno("could not read the input");
index dea849c3c5ec0c0a7ee0ac98e1ae62a6dacf5806..2ef5962386dcc21af94c680be5fd75fc96d4962f 100644 (file)
@@ -12,7 +12,7 @@ static void check_symref(const char *HEAD, int quiet)
 {
        unsigned char sha1[20];
        int flag;
-       const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag);
+       const char *refs_heads_master = resolve_ref_unsafe(HEAD, sha1, 0, &flag);
 
        if (!refs_heads_master)
                die("No such ref: %s", HEAD);
index efb98726955837294190fcef365e45b0dd5f5396..31f02e80f6b8fc22ed7ca7ae142c8d2f99084e8b 100644 (file)
@@ -214,6 +214,15 @@ static const char tag_template[] =
        N_("\n"
        "#\n"
        "# Write a tag message\n"
+       "# Lines starting with '#' will be ignored.\n"
+       "#\n");
+
+static const char tag_template_nocleanup[] =
+       N_("\n"
+       "#\n"
+       "# Write a tag message\n"
+       "# Lines starting with '#' will be kept; you may remove them"
+       " yourself if you want to.\n"
        "#\n");
 
 static int git_tag_config(const char *var, const char *value, void *cb)
@@ -255,8 +264,18 @@ static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
        return 0;
 }
 
+struct create_tag_options {
+       unsigned int message_given:1;
+       unsigned int sign;
+       enum {
+               CLEANUP_NONE,
+               CLEANUP_SPACE,
+               CLEANUP_ALL
+       } cleanup_mode;
+};
+
 static void create_tag(const unsigned char *object, const char *tag,
-                      struct strbuf *buf, int message, int sign,
+                      struct strbuf *buf, struct create_tag_options *opt,
                       unsigned char *prev, unsigned char *result)
 {
        enum object_type type;
@@ -281,7 +300,7 @@ static void create_tag(const unsigned char *object, const char *tag,
        if (header_len > sizeof(header_buf) - 1)
                die(_("tag header too big."));
 
-       if (!message) {
+       if (!opt->message_given) {
                int fd;
 
                /* write the template message before editing: */
@@ -292,8 +311,12 @@ static void create_tag(const unsigned char *object, const char *tag,
 
                if (!is_null_sha1(prev))
                        write_tag_body(fd, prev);
+               else if (opt->cleanup_mode == CLEANUP_ALL)
+                       write_or_die(fd, _(tag_template),
+                                       strlen(_(tag_template)));
                else
-                       write_or_die(fd, _(tag_template), strlen(_(tag_template)));
+                       write_or_die(fd, _(tag_template_nocleanup),
+                                       strlen(_(tag_template_nocleanup)));
                close(fd);
 
                if (launch_editor(path, buf, NULL)) {
@@ -303,14 +326,15 @@ static void create_tag(const unsigned char *object, const char *tag,
                }
        }
 
-       stripspace(buf, 1);
+       if (opt->cleanup_mode != CLEANUP_NONE)
+               stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
 
-       if (!message && !buf->len)
+       if (!opt->message_given && !buf->len)
                die(_("no tag message?"));
 
        strbuf_insert(buf, 0, header_buf, header_len);
 
-       if (build_tag_object(buf, sign, result) < 0) {
+       if (build_tag_object(buf, opt->sign, result) < 0) {
                if (path)
                        fprintf(stderr, _("The tag message has been left in %s\n"),
                                path);
@@ -358,9 +382,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        unsigned char object[20], prev[20];
        const char *object_ref, *tag;
        struct ref_lock *lock;
-
-       int annotate = 0, sign = 0, force = 0, lines = -1,
-               list = 0, delete = 0, verify = 0;
+       struct create_tag_options opt;
+       char *cleanup_arg = NULL;
+       int annotate = 0, force = 0, lines = -1, list = 0,
+               delete = 0, verify = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
        struct commit_list *with_commit = NULL;
@@ -378,7 +403,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_CALLBACK('m', "message", &msg, "message",
                             "tag message", parse_msg_arg),
                OPT_FILENAME('F', "file", &msgfile, "read message from file"),
-               OPT_BOOLEAN('s', "sign", &sign, "annotated and GPG-signed tag"),
+               OPT_BOOLEAN('s', "sign", &opt.sign, "annotated and GPG-signed tag"),
+               OPT_STRING(0, "cleanup", &cleanup_arg, "mode",
+                       "how to strip spaces and #comments from message"),
                OPT_STRING('u', "local-user", &keyid, "key-id",
                                        "use another key to sign the tag"),
                OPT__FORCE(&force, "replace the tag if exists"),
@@ -395,13 +422,15 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
        git_config(git_tag_config, NULL);
 
+       memset(&opt, 0, sizeof(opt));
+
        argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
 
        if (keyid) {
-               sign = 1;
+               opt.sign = 1;
                set_signing_key(keyid);
        }
-       if (sign)
+       if (opt.sign)
                annotate = 1;
        if (argc == 0 && !(delete || verify))
                list = 1;
@@ -459,9 +488,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        else if (!force)
                die(_("tag '%s' already exists"), tag);
 
+       opt.message_given = msg.given || msgfile;
+
+       if (!cleanup_arg || !strcmp(cleanup_arg, "strip"))
+               opt.cleanup_mode = CLEANUP_ALL;
+       else if (!strcmp(cleanup_arg, "verbatim"))
+               opt.cleanup_mode = CLEANUP_NONE;
+       else if (!strcmp(cleanup_arg, "whitespace"))
+               opt.cleanup_mode = CLEANUP_SPACE;
+       else
+               die(_("Invalid cleanup mode %s"), cleanup_arg);
+
        if (annotate)
-               create_tag(object, tag, &buf, msg.given || msgfile,
-                          sign, prev, object);
+               create_tag(object, tag, &buf, &opt, prev, object);
 
        lock = lock_any_ref_for_update(ref.buf, prev, 0);
        if (!lock)
index 2d0b38333eadef0a5c6501e5a3046ebc2afa048e..b928beb8ed51c9af9aa4f640e80443a5e059d505 100644 (file)
@@ -6,6 +6,7 @@
 #include "archive.h"
 #include "pkt-line.h"
 #include "sideband.h"
+#include "run-command.h"
 
 static const char upload_archive_usage[] =
        "git upload-archive <repo>";
@@ -13,12 +14,9 @@ static const char upload_archive_usage[] =
 static const char deadchild[] =
 "git upload-archive: archiver died with error";
 
-static const char lostchild[] =
-"git upload-archive: archiver process was lost";
-
 #define MAX_ARGS (64)
 
-static int run_upload_archive(int argc, const char **argv, const char *prefix)
+int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
 {
        const char *sent_argv[MAX_ARGS];
        const char *arg_cmd = "argument ";
@@ -96,8 +94,8 @@ static ssize_t process_input(int child_fd, int band)
 
 int cmd_upload_archive(int argc, const char **argv, const char *prefix)
 {
-       pid_t writer;
-       int fd1[2], fd2[2];
+       struct child_process writer = { argv };
+
        /*
         * Set up sideband subprocess.
         *
@@ -105,39 +103,24 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
         * multiplexed out to our fd#1.  If the child dies, we tell the other
         * end over channel #3.
         */
-       if (pipe(fd1) < 0 || pipe(fd2) < 0) {
-               int err = errno;
-               packet_write(1, "NACK pipe failed on the remote side\n");
-               die("upload-archive: %s", strerror(err));
-       }
-       writer = fork();
-       if (writer < 0) {
+       argv[0] = "upload-archive--writer";
+       writer.out = writer.err = -1;
+       writer.git_cmd = 1;
+       if (start_command(&writer)) {
                int err = errno;
-               packet_write(1, "NACK fork failed on the remote side\n");
+               packet_write(1, "NACK unable to spawn subprocess\n");
                die("upload-archive: %s", strerror(err));
        }
-       if (!writer) {
-               /* child - connect fd#1 and fd#2 to the pipe */
-               dup2(fd1[1], 1);
-               dup2(fd2[1], 2);
-               close(fd1[1]); close(fd2[1]);
-               close(fd1[0]); close(fd2[0]); /* we do not read from pipe */
-
-               exit(run_upload_archive(argc, argv, prefix));
-       }
 
-       /* parent - read from child, multiplex and send out to fd#1 */
-       close(fd1[1]); close(fd2[1]); /* we do not write to pipe */
        packet_write(1, "ACK\n");
        packet_flush(1);
 
        while (1) {
                struct pollfd pfd[2];
-               int status;
 
-               pfd[0].fd = fd1[0];
+               pfd[0].fd = writer.out;
                pfd[0].events = POLLIN;
-               pfd[1].fd = fd2[0];
+               pfd[1].fd = writer.err;
                pfd[1].events = POLLIN;
                if (poll(pfd, 2, -1) < 0) {
                        if (errno != EINTR) {
@@ -156,9 +139,7 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
                        if (process_input(pfd[0].fd, 1))
                                continue;
 
-               if (waitpid(writer, &status, 0) < 0)
-                       error_clnt("%s", lostchild);
-               else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
+               if (finish_command(&writer))
                        error_clnt("%s", deadchild);
                packet_flush(1);
                break;
diff --git a/bulk-checkin.c b/bulk-checkin.c
new file mode 100644 (file)
index 0000000..6b0b6d4
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#include "bulk-checkin.h"
+#include "csum-file.h"
+#include "pack.h"
+
+static int pack_compression_level = Z_DEFAULT_COMPRESSION;
+
+static struct bulk_checkin_state {
+       unsigned plugged:1;
+
+       char *pack_tmp_name;
+       struct sha1file *f;
+       off_t offset;
+       struct pack_idx_option pack_idx_opts;
+
+       struct pack_idx_entry **written;
+       uint32_t alloc_written;
+       uint32_t nr_written;
+} state;
+
+static void finish_bulk_checkin(struct bulk_checkin_state *state)
+{
+       unsigned char sha1[20];
+       char packname[PATH_MAX];
+       int i;
+
+       if (!state->f)
+               return;
+
+       if (state->nr_written == 0) {
+               close(state->f->fd);
+               unlink(state->pack_tmp_name);
+               goto clear_exit;
+       } else if (state->nr_written == 1) {
+               sha1close(state->f, sha1, CSUM_FSYNC);
+       } else {
+               int fd = sha1close(state->f, sha1, 0);
+               fixup_pack_header_footer(fd, sha1, state->pack_tmp_name,
+                                        state->nr_written, sha1,
+                                        state->offset);
+               close(fd);
+       }
+
+       sprintf(packname, "%s/pack/pack-", get_object_directory());
+       finish_tmp_packfile(packname, state->pack_tmp_name,
+                           state->written, state->nr_written,
+                           &state->pack_idx_opts, sha1);
+       for (i = 0; i < state->nr_written; i++)
+               free(state->written[i]);
+
+clear_exit:
+       free(state->written);
+       memset(state, 0, sizeof(*state));
+
+       /* Make objects we just wrote available to ourselves */
+       reprepare_packed_git();
+}
+
+static int already_written(struct bulk_checkin_state *state, unsigned char sha1[])
+{
+       int i;
+
+       /* The object may already exist in the repository */
+       if (has_sha1_file(sha1))
+               return 1;
+
+       /* Might want to keep the list sorted */
+       for (i = 0; i < state->nr_written; i++)
+               if (!hashcmp(state->written[i]->sha1, sha1))
+                       return 1;
+
+       /* This is a new object we need to keep */
+       return 0;
+}
+
+/*
+ * Read the contents from fd for size bytes, streaming it to the
+ * packfile in state while updating the hash in ctx. Signal a failure
+ * by returning a negative value when the resulting pack would exceed
+ * the pack size limit and this is not the first object in the pack,
+ * so that the caller can discard what we wrote from the current pack
+ * by truncating it and opening a new one. The caller will then call
+ * us again after rewinding the input fd.
+ *
+ * The already_hashed_to pointer is kept untouched by the caller to
+ * make sure we do not hash the same byte when we are called
+ * again. This way, the caller does not have to checkpoint its hash
+ * status before calling us just in case we ask it to call us again
+ * with a new pack.
+ */
+static int stream_to_pack(struct bulk_checkin_state *state,
+                         git_SHA_CTX *ctx, off_t *already_hashed_to,
+                         int fd, size_t size, enum object_type type,
+                         const char *path, unsigned flags)
+{
+       git_zstream s;
+       unsigned char obuf[16384];
+       unsigned hdrlen;
+       int status = Z_OK;
+       int write_object = (flags & HASH_WRITE_OBJECT);
+       off_t offset = 0;
+
+       memset(&s, 0, sizeof(s));
+       git_deflate_init(&s, pack_compression_level);
+
+       hdrlen = encode_in_pack_object_header(type, size, obuf);
+       s.next_out = obuf + hdrlen;
+       s.avail_out = sizeof(obuf) - hdrlen;
+
+       while (status != Z_STREAM_END) {
+               unsigned char ibuf[16384];
+
+               if (size && !s.avail_in) {
+                       ssize_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf);
+                       if (xread(fd, ibuf, rsize) != rsize)
+                               die("failed to read %d bytes from '%s'",
+                                   (int)rsize, path);
+                       offset += rsize;
+                       if (*already_hashed_to < offset) {
+                               size_t hsize = offset - *already_hashed_to;
+                               if (rsize < hsize)
+                                       hsize = rsize;
+                               if (hsize)
+                                       git_SHA1_Update(ctx, ibuf, hsize);
+                               *already_hashed_to = offset;
+                       }
+                       s.next_in = ibuf;
+                       s.avail_in = rsize;
+                       size -= rsize;
+               }
+
+               status = git_deflate(&s, size ? 0 : Z_FINISH);
+
+               if (!s.avail_out || status == Z_STREAM_END) {
+                       if (write_object) {
+                               size_t written = s.next_out - obuf;
+
+                               /* would we bust the size limit? */
+                               if (state->nr_written &&
+                                   pack_size_limit_cfg &&
+                                   pack_size_limit_cfg < state->offset + written) {
+                                       git_deflate_abort(&s);
+                                       return -1;
+                               }
+
+                               sha1write(state->f, obuf, written);
+                               state->offset += written;
+                       }
+                       s.next_out = obuf;
+                       s.avail_out = sizeof(obuf);
+               }
+
+               switch (status) {
+               case Z_OK:
+               case Z_BUF_ERROR:
+               case Z_STREAM_END:
+                       continue;
+               default:
+                       die("unexpected deflate failure: %d", status);
+               }
+       }
+       git_deflate_end(&s);
+       return 0;
+}
+
+/* Lazily create backing packfile for the state */
+static void prepare_to_stream(struct bulk_checkin_state *state,
+                             unsigned flags)
+{
+       if (!(flags & HASH_WRITE_OBJECT) || state->f)
+               return;
+
+       state->f = create_tmp_packfile(&state->pack_tmp_name);
+       reset_pack_idx_option(&state->pack_idx_opts);
+
+       /* Pretend we are going to write only one object */
+       state->offset = write_pack_header(state->f, 1);
+       if (!state->offset)
+               die_errno("unable to write pack header");
+}
+
+static int deflate_to_pack(struct bulk_checkin_state *state,
+                          unsigned char result_sha1[],
+                          int fd, size_t size,
+                          enum object_type type, const char *path,
+                          unsigned flags)
+{
+       off_t seekback, already_hashed_to;
+       git_SHA_CTX ctx;
+       unsigned char obuf[16384];
+       unsigned header_len;
+       struct sha1file_checkpoint checkpoint;
+       struct pack_idx_entry *idx = NULL;
+
+       seekback = lseek(fd, 0, SEEK_CUR);
+       if (seekback == (off_t) -1)
+               return error("cannot find the current offset");
+
+       header_len = sprintf((char *)obuf, "%s %" PRIuMAX,
+                            typename(type), (uintmax_t)size) + 1;
+       git_SHA1_Init(&ctx);
+       git_SHA1_Update(&ctx, obuf, header_len);
+
+       /* Note: idx is non-NULL when we are writing */
+       if ((flags & HASH_WRITE_OBJECT) != 0)
+               idx = xcalloc(1, sizeof(*idx));
+
+       already_hashed_to = 0;
+
+       while (1) {
+               prepare_to_stream(state, flags);
+               if (idx) {
+                       sha1file_checkpoint(state->f, &checkpoint);
+                       idx->offset = state->offset;
+                       crc32_begin(state->f);
+               }
+               if (!stream_to_pack(state, &ctx, &already_hashed_to,
+                                   fd, size, type, path, flags))
+                       break;
+               /*
+                * Writing this object to the current pack will make
+                * it too big; we need to truncate it, start a new
+                * pack, and write into it.
+                */
+               if (!idx)
+                       die("BUG: should not happen");
+               sha1file_truncate(state->f, &checkpoint);
+               state->offset = checkpoint.offset;
+               finish_bulk_checkin(state);
+               if (lseek(fd, seekback, SEEK_SET) == (off_t) -1)
+                       return error("cannot seek back");
+       }
+       git_SHA1_Final(result_sha1, &ctx);
+       if (!idx)
+               return 0;
+
+       idx->crc32 = crc32_end(state->f);
+       if (already_written(state, result_sha1)) {
+               sha1file_truncate(state->f, &checkpoint);
+               state->offset = checkpoint.offset;
+               free(idx);
+       } else {
+               hashcpy(idx->sha1, result_sha1);
+               ALLOC_GROW(state->written,
+                          state->nr_written + 1,
+                          state->alloc_written);
+               state->written[state->nr_written++] = idx;
+       }
+       return 0;
+}
+
+int index_bulk_checkin(unsigned char *sha1,
+                      int fd, size_t size, enum object_type type,
+                      const char *path, unsigned flags)
+{
+       int status = deflate_to_pack(&state, sha1, fd, size, type,
+                                    path, flags);
+       if (!state.plugged)
+               finish_bulk_checkin(&state);
+       return status;
+}
+
+void plug_bulk_checkin(void)
+{
+       state.plugged = 1;
+}
+
+void unplug_bulk_checkin(void)
+{
+       state.plugged = 0;
+       if (state.f)
+               finish_bulk_checkin(&state);
+}
diff --git a/bulk-checkin.h b/bulk-checkin.h
new file mode 100644 (file)
index 0000000..4f599f8
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2011, Google Inc.
+ */
+#ifndef BULK_CHECKIN_H
+#define BULK_CHECKIN_H
+
+#include "cache.h"
+
+extern int index_bulk_checkin(unsigned char sha1[],
+                             int fd, size_t size, enum object_type type,
+                             const char *path, unsigned flags);
+
+extern void plug_bulk_checkin(void);
+extern void unplug_bulk_checkin(void);
+
+#endif
index f755590da827234830d8b4359720cfbfd87a3dea..8de39590d57e14d08ee4d04b74965191aa905b29 100644 (file)
@@ -150,7 +150,7 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
 }
 
 static int verify_cache(struct cache_entry **cache,
-                       int entries)
+                       int entries, int silent)
 {
        int i, funny;
 
@@ -159,6 +159,8 @@ static int verify_cache(struct cache_entry **cache,
        for (i = 0; i < entries; i++) {
                struct cache_entry *ce = cache[i];
                if (ce_stage(ce) || (ce->ce_flags & CE_INTENT_TO_ADD)) {
+                       if (silent)
+                               return -1;
                        if (10 < ++funny) {
                                fprintf(stderr, "...\n");
                                break;
@@ -370,10 +372,11 @@ int cache_tree_update(struct cache_tree *it,
                      struct cache_entry **cache,
                      int entries,
                      int missing_ok,
-                     int dryrun)
+                     int dryrun,
+                     int silent)
 {
        int i;
-       i = verify_cache(cache, entries);
+       i = verify_cache(cache, entries, silent);
        if (i)
                return i;
        i = update_one(it, cache, entries, "", 0, missing_ok, dryrun);
@@ -573,7 +576,7 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
 
                if (cache_tree_update(active_cache_tree,
                                      active_cache, active_nr,
-                                     missing_ok, 0) < 0)
+                                     missing_ok, 0, 0) < 0)
                        return WRITE_TREE_UNMERGED_INDEX;
                if (0 <= newfd) {
                        if (!write_cache(newfd, active_cache, active_nr) &&
@@ -668,3 +671,11 @@ int cache_tree_matches_traversal(struct cache_tree *root,
                return it->entry_count;
        return 0;
 }
+
+int update_main_cache_tree (int silent)
+{
+       if (!the_index.cache_tree)
+               the_index.cache_tree = cache_tree();
+       return cache_tree_update(the_index.cache_tree,
+                                the_index.cache, the_index.cache_nr, 0, 0, silent);
+}
index 3df641f59311f43aa951a2cdfa9f110b97b13a45..0ec0b2a159dfd352ca621322a9ce3715328ab2d0 100644 (file)
@@ -29,7 +29,9 @@ void cache_tree_write(struct strbuf *, struct cache_tree *root);
 struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
 
 int cache_tree_fully_valid(struct cache_tree *);
-int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int);
+int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int, int);
+
+int update_main_cache_tree(int);
 
 /* bitmasks to write_cache_as_tree flags */
 #define WRITE_TREE_MISSING_OK 1
diff --git a/cache.h b/cache.h
index 8c98d056674c9a426215bf93398fe922a1a90679..10afd71d435920a3cc2152208a06058791ea7a04 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -35,6 +35,7 @@ int git_inflate(git_zstream *, int flush);
 void git_deflate_init(git_zstream *, int level);
 void git_deflate_init_gzip(git_zstream *, int level);
 void git_deflate_end(git_zstream *);
+int git_deflate_abort(git_zstream *);
 int git_deflate_end_gently(git_zstream *);
 int git_deflate(git_zstream *, int flush);
 unsigned long git_deflate_bound(git_zstream *, unsigned long);
@@ -597,6 +598,7 @@ extern size_t packed_git_window_size;
 extern size_t packed_git_limit;
 extern size_t delta_base_cache_limit;
 extern unsigned long big_file_threshold;
+extern unsigned long pack_size_limit_cfg;
 extern int read_replace_refs;
 extern int fsync_object_files;
 extern int core_preload_index;
@@ -831,9 +833,9 @@ static inline int get_sha1_with_context(const char *str, unsigned char *sha1, st
 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_full(const char *filename, unsigned char *sha1,
+extern int read_ref_full(const char *refname, unsigned char *sha1,
                         int reading, int *flags);
-extern int read_ref(const char *filename, unsigned char *sha1);
+extern int read_ref(const char *refname, unsigned char *sha1);
 
 /*
  * Resolve a reference, recursively following symbolic refererences.
@@ -865,7 +867,8 @@ extern int read_ref(const char *filename, unsigned char *sha1);
  *
  * errno is sometimes set on errors, but not always.
  */
-extern const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag);
+extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag);
+extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag);
 
 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);
@@ -1025,16 +1028,14 @@ struct ref {
 extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
 
 #define CONNECT_VERBOSE       (1u << 0)
-extern char *git_getpass(const char *prompt);
 extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
 extern int finish_connect(struct child_process *conn);
 extern int git_connection_is_socket(struct child_process *conn);
-extern int path_match(const char *path, int nr, char **match);
 struct extra_have_objects {
        int nr, alloc;
        unsigned char (*array)[20];
 };
-extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags, struct extra_have_objects *);
+extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *);
 extern int server_supports(const char *feature);
 
 extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
index b78127403be65f5c26e35e53a59b91860396194a..44bc96d44d391209d3a60e9e8c1b98ed209e6b4a 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -973,7 +973,7 @@ void free_commit_extra_headers(struct commit_extra_header *extra)
        }
 }
 
-int commit_tree(const char *msg, unsigned char *tree,
+int commit_tree(const struct strbuf *msg, unsigned char *tree,
                struct commit_list *parents, unsigned char *ret,
                const char *author)
 {
@@ -991,7 +991,7 @@ static const char commit_utf8_warn[] =
 "You may want to amend it after fixing the message, or set the config\n"
 "variable i18n.commitencoding to the encoding your project uses.\n";
 
-int commit_tree_extended(const char *msg, unsigned char *tree,
+int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
                         struct commit_list *parents, unsigned char *ret,
                         const char *author, struct commit_extra_header *extra)
 {
@@ -1001,6 +1001,9 @@ int commit_tree_extended(const char *msg, unsigned char *tree,
 
        assert_sha1_type(tree, OBJ_TREE);
 
+       if (memchr(msg->buf, '\0', msg->len))
+               return error("a NUL byte in commit log message not allowed.");
+
        /* Not having i18n.commitencoding is the same as having utf-8 */
        encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
 
@@ -1037,7 +1040,7 @@ int commit_tree_extended(const char *msg, unsigned char *tree,
        strbuf_addch(&buffer, '\n');
 
        /* And add the comment */
-       strbuf_addstr(&buffer, msg);
+       strbuf_addbuf(&buffer, msg);
 
        /* And check the encoding */
        if (encoding_is_utf8 && !is_utf8(buffer.buf))
index 3745f120994cb6876e1ccc1bfc5e07c6cc3fc5c9..4df397865abe7161a9f16e3177e3b486bf8434ca 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -191,11 +191,11 @@ struct commit_extra_header {
 extern void append_merge_tag_headers(struct commit_list *parents,
                                     struct commit_extra_header ***tail);
 
-extern int commit_tree(const char *msg, unsigned char *tree,
+extern int commit_tree(const struct strbuf *msg, unsigned char *tree,
                       struct commit_list *parents, unsigned char *ret,
                       const char *author);
 
-extern int commit_tree_extended(const char *msg, unsigned char *tree,
+extern int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
                                struct commit_list *parents, unsigned char *ret,
                                const char *author,
                                struct commit_extra_header *);
index 3a22ea7b751efb768d72afa2f97fd963e10eec7e..fc1439a6439393ff5224e98ab126607ceaa4994d 100644 (file)
@@ -6,7 +6,10 @@ int gitsetenv(const char *name, const char *value, int replace)
        size_t namelen, valuelen;
        char *envstr;
 
-       if (!name || !value) return -1;
+       if (!name || strchr(name, '=') || !value) {
+               errno = EINVAL;
+               return -1;
+       }
        if (!replace) {
                char *oldval = NULL;
                oldval = getenv(name);
@@ -16,7 +19,10 @@ int gitsetenv(const char *name, const char *value, int replace)
        namelen = strlen(name);
        valuelen = strlen(value);
        envstr = malloc((namelen + valuelen + 2));
-       if (!envstr) return -1;
+       if (!envstr) {
+               errno = ENOMEM;
+               return -1;
+       }
 
        memcpy(envstr, name, namelen);
        envstr[namelen] = '=';
index e1e0e7543d9414726122c121b7909bf73809a81a..42ea1ac110813bbd16e77cfbc36f16e6a5e9ddb2 100644 (file)
 #undef vsnprintf
 int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap)
 {
+       va_list cp;
        char *s;
        int ret = -1;
 
        if (maxsize > 0) {
-               ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, ap);
+               va_copy(cp, ap);
+               ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, cp);
+               va_end(cp);
                if (ret == maxsize-1)
                        ret = -1;
                /* Windows does not NUL-terminate if result fills buffer */
@@ -42,7 +45,9 @@ int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap)
                if (! str)
                        break;
                s = str;
-               ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, ap);
+               va_copy(cp, ap);
+               ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, cp);
+               va_end(cp);
                if (ret == maxsize-1)
                        ret = -1;
        }
diff --git a/compat/terminal.c b/compat/terminal.c
new file mode 100644 (file)
index 0000000..6d16c8f
--- /dev/null
@@ -0,0 +1,81 @@
+#include "git-compat-util.h"
+#include "compat/terminal.h"
+#include "sigchain.h"
+#include "strbuf.h"
+
+#ifdef HAVE_DEV_TTY
+
+static int term_fd = -1;
+static struct termios old_term;
+
+static void restore_term(void)
+{
+       if (term_fd < 0)
+               return;
+
+       tcsetattr(term_fd, TCSAFLUSH, &old_term);
+       term_fd = -1;
+}
+
+static void restore_term_on_signal(int sig)
+{
+       restore_term();
+       sigchain_pop(sig);
+       raise(sig);
+}
+
+char *git_terminal_prompt(const char *prompt, int echo)
+{
+       static struct strbuf buf = STRBUF_INIT;
+       int r;
+       FILE *fh;
+
+       fh = fopen("/dev/tty", "w+");
+       if (!fh)
+               return NULL;
+
+       if (!echo) {
+               struct termios t;
+
+               if (tcgetattr(fileno(fh), &t) < 0) {
+                       fclose(fh);
+                       return NULL;
+               }
+
+               old_term = t;
+               term_fd = fileno(fh);
+               sigchain_push_common(restore_term_on_signal);
+
+               t.c_lflag &= ~ECHO;
+               if (tcsetattr(fileno(fh), TCSAFLUSH, &t) < 0) {
+                       term_fd = -1;
+                       fclose(fh);
+                       return NULL;
+               }
+       }
+
+       fputs(prompt, fh);
+       fflush(fh);
+
+       r = strbuf_getline(&buf, fh, '\n');
+       if (!echo) {
+               putc('\n', fh);
+               fflush(fh);
+       }
+
+       restore_term();
+       fclose(fh);
+
+       if (r == EOF)
+               return NULL;
+       return buf.buf;
+}
+
+#else
+
+char *git_terminal_prompt(const char *prompt, int echo)
+{
+       return getpass(prompt);
+}
+
+#endif
diff --git a/compat/terminal.h b/compat/terminal.h
new file mode 100644 (file)
index 0000000..97db7cd
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef COMPAT_TERMINAL_H
+#define COMPAT_TERMINAL_H
+
+char *git_terminal_prompt(const char *prompt, int echo);
+
+#endif /* COMPAT_TERMINAL_H */
index 5ea101fb251d27eadac20c665a7f01fb210c20d1..40f9c6d10317ed47f7786e5c328df3ab6f167e7c 100644 (file)
--- a/config.c
+++ b/config.c
@@ -818,6 +818,10 @@ int git_default_config(const char *var, const char *value, void *dummy)
                return 0;
        }
 
+       if (!strcmp(var, "pack.packsizelimit")) {
+               pack_size_limit_cfg = git_config_ulong(var, value);
+               return 0;
+       }
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
 }
index ab371012a22f39bf31f512e276e12f55b6d1b8b7..10698c8292e639fc5adc4492a4bcac0c82c403bc 100644 (file)
@@ -35,6 +35,9 @@ NO_CURL=@NO_CURL@
 NO_EXPAT=@NO_EXPAT@
 NO_LIBGEN_H=@NO_LIBGEN_H@
 HAVE_PATHS_H=@HAVE_PATHS_H@
+HAVE_LIBCHARSET_H=@HAVE_LIBCHARSET_H@
+NO_GETTEXT=@NO_GETTEXT@
+LIBC_CONTAINS_LIBINTL=@LIBC_CONTAINS_LIBINTL@
 NEEDS_LIBICONV=@NEEDS_LIBICONV@
 NEEDS_SOCKET=@NEEDS_SOCKET@
 NEEDS_RESOLV=@NEEDS_RESOLV@
index 048a1d4972769184ff857fb7681bc67b2ebdfb99..630dbdd19d74fc6ffab529d8d5854043a8f18fd2 100644 (file)
@@ -636,6 +636,12 @@ AC_CHECK_LIB([c], [basename],
 AC_SUBST(NEEDS_LIBGEN)
 test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen"
 
+AC_CHECK_LIB([c], [gettext],
+[LIBC_CONTAINS_LIBINTL=YesPlease],
+[LIBC_CONTAINS_LIBINTL=])
+AC_SUBST(LIBC_CONTAINS_LIBINTL)
+test -n "$LIBC_CONTAINS_LIBINTL" || LIBS="$LIBS -lintl"
+
 ## Checks for header files.
 AC_MSG_NOTICE([CHECKS for header files])
 #
@@ -818,6 +824,19 @@ AC_CHECK_HEADER([paths.h],
 [HAVE_PATHS_H=])
 AC_SUBST(HAVE_PATHS_H)
 #
+# Define NO_GETTEXT if you don't want Git output to be translated.
+# A translated Git requires GNU libintl or another gettext implementation
+AC_CHECK_HEADER([libintl.h],
+[NO_GETTEXT=],
+[NO_GETTEXT=YesPlease])
+AC_SUBST(NO_GETTEXT)
+#
+# Define HAVE_LIBCHARSET_H if have libcharset.h
+AC_CHECK_HEADER([libcharset.h],
+[HAVE_LIBCHARSET_H=YesPlease],
+[HAVE_LIBCHARSET_H=])
+AC_SUBST(HAVE_LIBCHARSET_H)
+#
 # Define NO_STRCASESTR if you don't have strcasestr.
 GIT_CHECK_FUNC(strcasestr,
 [NO_STRCASESTR=],
index 51990fa0cb300a95b125b0727f10133961d0167b..2ea5c3c0fbedb89e4189d32a455f289398ce106b 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -53,7 +53,6 @@ static void add_extra_have(struct extra_have_objects *extra, unsigned char *sha1
  * Read all the refs from the other end
  */
 struct ref **get_remote_heads(int in, struct ref **list,
-                             int nr_match, char **match,
                              unsigned int flags,
                              struct extra_have_objects *extra_have)
 {
@@ -92,8 +91,6 @@ struct ref **get_remote_heads(int in, struct ref **list,
 
                if (!check_ref(name, name_len, flags))
                        continue;
-               if (nr_match && !path_match(name, nr_match, match))
-                       continue;
                ref = alloc_ref(buffer + 41);
                hashcpy(ref->old_sha1, old_sha1);
                *list = ref;
@@ -108,27 +105,6 @@ int server_supports(const char *feature)
                strstr(server_capabilities, feature) != NULL;
 }
 
-int path_match(const char *path, int nr, char **match)
-{
-       int i;
-       int pathlen = strlen(path);
-
-       for (i = 0; i < nr; i++) {
-               char *s = match[i];
-               int len = strlen(s);
-
-               if (!len || len > pathlen)
-                       continue;
-               if (memcmp(path + pathlen - len, s, len))
-                       continue;
-               if (pathlen > len && path[pathlen - len - 1] != '/')
-                       continue;
-               *s = 0;
-               return (i + 1);
-       }
-       return 0;
-}
-
 enum protocol {
        PROTO_LOCAL = 1,
        PROTO_SSH,
@@ -175,6 +151,15 @@ static void get_host_and_port(char **host, const char **port)
        }
 }
 
+static void enable_keepalive(int sockfd)
+{
+       int ka = 1;
+
+       if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka)) < 0)
+               fprintf(stderr, "unable to set SO_KEEPALIVE on socket: %s\n",
+                       strerror(errno));
+}
+
 #ifndef NO_IPV6
 
 static const char *ai_name(const struct addrinfo *ai)
@@ -239,6 +224,8 @@ static int git_tcp_connect_sock(char *host, int flags)
        if (sockfd < 0)
                die("unable to connect to %s:\n%s", host, error_message.buf);
 
+       enable_keepalive(sockfd);
+
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\n");
 
@@ -312,6 +299,8 @@ static int git_tcp_connect_sock(char *host, int flags)
        if (sockfd < 0)
                die("unable to connect to %s:\n%s", host, error_message.buf);
 
+       enable_keepalive(sockfd);
+
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "done.\n");
 
@@ -619,47 +608,3 @@ int finish_connect(struct child_process *conn)
        free(conn);
        return code;
 }
-
-char *git_getpass(const char *prompt)
-{
-       const char *askpass;
-       struct child_process pass;
-       const char *args[3];
-       static struct strbuf buffer = STRBUF_INIT;
-
-       askpass = getenv("GIT_ASKPASS");
-       if (!askpass)
-               askpass = askpass_program;
-       if (!askpass)
-               askpass = getenv("SSH_ASKPASS");
-       if (!askpass || !(*askpass)) {
-               char *result = getpass(prompt);
-               if (!result)
-                       die_errno("Could not read password");
-               return result;
-       }
-
-       args[0] = askpass;
-       args[1] = prompt;
-       args[2] = NULL;
-
-       memset(&pass, 0, sizeof(pass));
-       pass.argv = args;
-       pass.out = -1;
-
-       if (start_command(&pass))
-               exit(1);
-
-       strbuf_reset(&buffer);
-       if (strbuf_read(&buffer, pass.out, 20) < 0)
-               die("failed to read password from %s\n", askpass);
-
-       close(pass.out);
-
-       if (finish_command(&pass))
-               exit(1);
-
-       strbuf_setlen(&buffer, strcspn(buffer.buf, "\r\n"));
-
-       return buffer.buf;
-}
diff --git a/contrib/credential/osxkeychain/.gitignore b/contrib/credential/osxkeychain/.gitignore
new file mode 100644 (file)
index 0000000..6c5b702
--- /dev/null
@@ -0,0 +1 @@
+git-credential-osxkeychain
diff --git a/contrib/credential/osxkeychain/Makefile b/contrib/credential/osxkeychain/Makefile
new file mode 100644 (file)
index 0000000..75c07f8
--- /dev/null
@@ -0,0 +1,14 @@
+all:: git-credential-osxkeychain
+
+CC = gcc
+RM = rm -f
+CFLAGS = -g -Wall
+
+git-credential-osxkeychain: git-credential-osxkeychain.o
+       $(CC) -o $@ $< -Wl,-framework -Wl,Security
+
+git-credential-osxkeychain.o: git-credential-osxkeychain.c
+       $(CC) -c $(CFLAGS) $<
+
+clean:
+       $(RM) git-credential-osxkeychain git-credential-osxkeychain.o
diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
new file mode 100644 (file)
index 0000000..6beed12
--- /dev/null
@@ -0,0 +1,173 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <Security/Security.h>
+
+static SecProtocolType protocol;
+static char *host;
+static char *path;
+static char *username;
+static char *password;
+static UInt16 port;
+
+static void die(const char *err, ...)
+{
+       char msg[4096];
+       va_list params;
+       va_start(params, err);
+       vsnprintf(msg, sizeof(msg), err, params);
+       fprintf(stderr, "%s\n", msg);
+       va_end(params);
+       exit(1);
+}
+
+static void *xstrdup(const char *s1)
+{
+       void *ret = strdup(s1);
+       if (!ret)
+               die("Out of memory");
+       return ret;
+}
+
+#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
+#define KEYCHAIN_ARGS \
+       NULL, /* default keychain */ \
+       KEYCHAIN_ITEM(host), \
+       0, NULL, /* account domain */ \
+       KEYCHAIN_ITEM(username), \
+       KEYCHAIN_ITEM(path), \
+       port, \
+       protocol, \
+       kSecAuthenticationTypeDefault
+
+static void write_item(const char *what, const char *buf, int len)
+{
+       printf("%s=", what);
+       fwrite(buf, 1, len, stdout);
+       putchar('\n');
+}
+
+static void find_username_in_item(SecKeychainItemRef item)
+{
+       SecKeychainAttributeList list;
+       SecKeychainAttribute attr;
+
+       list.count = 1;
+       list.attr = &attr;
+       attr.tag = kSecAccountItemAttr;
+
+       if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
+               return;
+
+       write_item("username", attr.data, attr.length);
+       SecKeychainItemFreeContent(&list, NULL);
+}
+
+static void find_internet_password(void)
+{
+       void *buf;
+       UInt32 len;
+       SecKeychainItemRef item;
+
+       if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
+               return;
+
+       write_item("password", buf, len);
+       if (!username)
+               find_username_in_item(item);
+
+       SecKeychainItemFreeContent(NULL, buf);
+}
+
+static void delete_internet_password(void)
+{
+       SecKeychainItemRef item;
+
+       /*
+        * Require at least a protocol and host for removal, which is what git
+        * will give us; if you want to do something more fancy, use the
+        * Keychain manager.
+        */
+       if (!protocol || !host)
+               return;
+
+       if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
+               return;
+
+       SecKeychainItemDelete(item);
+}
+
+static void add_internet_password(void)
+{
+       /* Only store complete credentials */
+       if (!protocol || !host || !username || !password)
+               return;
+
+       if (SecKeychainAddInternetPassword(
+             KEYCHAIN_ARGS,
+             KEYCHAIN_ITEM(password),
+             NULL))
+               return;
+}
+
+static void read_credential(void)
+{
+       char buf[1024];
+
+       while (fgets(buf, sizeof(buf), stdin)) {
+               char *v;
+
+               if (!strcmp(buf, "\n"))
+                       break;
+               buf[strlen(buf)-1] = '\0';
+
+               v = strchr(buf, '=');
+               if (!v)
+                       die("bad input: %s", buf);
+               *v++ = '\0';
+
+               if (!strcmp(buf, "protocol")) {
+                       if (!strcmp(v, "https"))
+                               protocol = kSecProtocolTypeHTTPS;
+                       else if (!strcmp(v, "http"))
+                               protocol = kSecProtocolTypeHTTP;
+                       else /* we don't yet handle other protocols */
+                               exit(0);
+               }
+               else if (!strcmp(buf, "host")) {
+                       char *colon = strchr(v, ':');
+                       if (colon) {
+                               *colon++ = '\0';
+                               port = atoi(colon);
+                       }
+                       host = xstrdup(v);
+               }
+               else if (!strcmp(buf, "path"))
+                       path = xstrdup(v);
+               else if (!strcmp(buf, "username"))
+                       username = xstrdup(v);
+               else if (!strcmp(buf, "password"))
+                       password = xstrdup(v);
+       }
+}
+
+int main(int argc, const char **argv)
+{
+       const char *usage =
+               "Usage: git credential-osxkeychain <get|store|erase>";
+
+       if (!argv[1])
+               die(usage);
+
+       read_credential();
+
+       if (!strcmp(argv[1], "get"))
+               find_internet_password();
+       else if (!strcmp(argv[1], "store"))
+               add_internet_password();
+       else if (!strcmp(argv[1], "erase"))
+               delete_internet_password();
+       /* otherwise, ignore unknown action */
+
+       return 0;
+}
index 7fd8bf031e1eb10f6dda2342f32c86454ef7efb3..594980302b634d13029c740d582246eb9ec75a74 100755 (executable)
@@ -53,9 +53,10 @@ def p4_build_cmd(cmd):
 
 def chdir(dir):
     # P4 uses the PWD environment variable rather than getcwd(). Since we're
-    # not using the shell, we have to set it ourselves.
-    os.environ['PWD']=dir
+    # not using the shell, we have to set it ourselves.  This path could
+    # be relative, so go there first, then figure out where we ended up.
     os.chdir(dir)
+    os.environ['PWD'] = os.getcwd()
 
 def die(msg):
     if verbose:
@@ -871,13 +872,16 @@ class P4Submit(Command, P4UserMap):
         if gitConfig("git-p4.skipSubmitEditCheck") == "true":
             return True
 
-        if os.stat(template_file).st_mtime <= mtime:
-            while True:
-                response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
-                if response == 'y':
-                    return True
-                if response == 'n':
-                    return False
+        # modification time updated means user saved the file
+        if os.stat(template_file).st_mtime > mtime:
+            return True
+
+        while True:
+            response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
+            if response == 'y':
+                return True
+            if response == 'n':
+                return False
 
     def applyCommit(self, id):
         print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
@@ -1061,6 +1065,7 @@ class P4Submit(Command, P4UserMap):
                         self.modifyChangelistUser(changelist, p4User)
             else:
                 # skip this patch
+                print "Submission cancelled, undoing p4 changes."
                 for f in editedFiles:
                     p4_revert(f)
                 for f in filesToAdd:
@@ -1116,6 +1121,10 @@ class P4Submit(Command, P4UserMap):
         print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
         self.oldWorkingDirectory = os.getcwd()
 
+        # ensure the clientPath exists
+        if not os.path.exists(self.clientPath):
+            os.makedirs(self.clientPath)
+
         chdir(self.clientPath)
         print "Synchronizing p4 checkout..."
         p4_sync("...")
index 86e9c29ec05139844ff46868e2b52dfbc274cc39..c9ab54ffd2f84b0c501a6582c3837371efd34464 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -876,24 +876,42 @@ int is_null_stream_filter(struct stream_filter *filter)
 /*
  * LF-to-CRLF filter
  */
+
+struct lf_to_crlf_filter {
+       struct stream_filter filter;
+       unsigned want_lf:1;
+};
+
 static int lf_to_crlf_filter_fn(struct stream_filter *filter,
                                const char *input, size_t *isize_p,
                                char *output, size_t *osize_p)
 {
-       size_t count;
+       size_t count, o = 0;
+       struct lf_to_crlf_filter *lf_to_crlf = (struct lf_to_crlf_filter *)filter;
+
+       /* Output a pending LF if we need to */
+       if (lf_to_crlf->want_lf) {
+               output[o++] = '\n';
+               lf_to_crlf->want_lf = 0;
+       }
+
+       /* We are told to drain */
+       if (!input) {
+               *osize_p -= o;
+               return 0;
+       }
 
-       if (!input)
-               return 0; /* we do not keep any states */
        count = *isize_p;
        if (count) {
-               size_t i, o;
-               for (i = o = 0; o < *osize_p && i < count; i++) {
+               size_t i;
+               for (i = 0; o < *osize_p && i < count; i++) {
                        char ch = input[i];
                        if (ch == '\n') {
-                               if (o + 1 < *osize_p)
-                                       output[o++] = '\r';
-                               else
-                                       break;
+                               output[o++] = '\r';
+                               if (o >= *osize_p) {
+                                       lf_to_crlf->want_lf = 1;
+                                       continue; /* We need to increase i */
+                               }
                        }
                        output[o++] = ch;
                }
@@ -904,15 +922,23 @@ static int lf_to_crlf_filter_fn(struct stream_filter *filter,
        return 0;
 }
 
+static void lf_to_crlf_free_fn(struct stream_filter *filter)
+{
+       free(filter);
+}
+
 static struct stream_filter_vtbl lf_to_crlf_vtbl = {
        lf_to_crlf_filter_fn,
-       null_free_fn,
+       lf_to_crlf_free_fn,
 };
 
-static struct stream_filter lf_to_crlf_filter_singleton = {
-       &lf_to_crlf_vtbl,
-};
+static struct stream_filter *lf_to_crlf_filter(void)
+{
+       struct lf_to_crlf_filter *lf_to_crlf = xcalloc(1, sizeof(*lf_to_crlf));
 
+       lf_to_crlf->filter.vtbl = &lf_to_crlf_vtbl;
+       return (struct stream_filter *)lf_to_crlf;
+}
 
 /*
  * Cascade filter
@@ -1194,7 +1220,7 @@ struct stream_filter *get_stream_filter(const char *path, const unsigned char *s
 
        else if (output_eol(crlf_action) == EOL_CRLF &&
                 !(crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS))
-               filter = cascade_filter(filter, &lf_to_crlf_filter_singleton);
+               filter = cascade_filter(filter, lf_to_crlf_filter());
 
        return filter;
 }
diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c
new file mode 100644 (file)
index 0000000..390f194
--- /dev/null
@@ -0,0 +1,269 @@
+#include "cache.h"
+#include "credential.h"
+#include "unix-socket.h"
+#include "sigchain.h"
+
+static const char *socket_path;
+
+static void cleanup_socket(void)
+{
+       if (socket_path)
+               unlink(socket_path);
+}
+
+static void cleanup_socket_on_signal(int sig)
+{
+       cleanup_socket();
+       sigchain_pop(sig);
+       raise(sig);
+}
+
+struct credential_cache_entry {
+       struct credential item;
+       unsigned long expiration;
+};
+static struct credential_cache_entry *entries;
+static int entries_nr;
+static int entries_alloc;
+
+static void cache_credential(struct credential *c, int timeout)
+{
+       struct credential_cache_entry *e;
+
+       ALLOC_GROW(entries, entries_nr + 1, entries_alloc);
+       e = &entries[entries_nr++];
+
+       /* take ownership of pointers */
+       memcpy(&e->item, c, sizeof(*c));
+       memset(c, 0, sizeof(*c));
+       e->expiration = time(NULL) + timeout;
+}
+
+static struct credential_cache_entry *lookup_credential(const struct credential *c)
+{
+       int i;
+       for (i = 0; i < entries_nr; i++) {
+               struct credential *e = &entries[i].item;
+               if (credential_match(c, e))
+                       return &entries[i];
+       }
+       return NULL;
+}
+
+static void remove_credential(const struct credential *c)
+{
+       struct credential_cache_entry *e;
+
+       e = lookup_credential(c);
+       if (e)
+               e->expiration = 0;
+}
+
+static int check_expirations(void)
+{
+       static unsigned long wait_for_entry_until;
+       int i = 0;
+       unsigned long now = time(NULL);
+       unsigned long next = (unsigned long)-1;
+
+       /*
+        * Initially give the client 30 seconds to actually contact us
+        * and store a credential before we decide there's no point in
+        * keeping the daemon around.
+        */
+       if (!wait_for_entry_until)
+               wait_for_entry_until = now + 30;
+
+       while (i < entries_nr) {
+               if (entries[i].expiration <= now) {
+                       entries_nr--;
+                       credential_clear(&entries[i].item);
+                       if (i != entries_nr)
+                               memcpy(&entries[i], &entries[entries_nr], sizeof(*entries));
+                       /*
+                        * Stick around 30 seconds in case a new credential
+                        * shows up (e.g., because we just removed a failed
+                        * one, and we will soon get the correct one).
+                        */
+                       wait_for_entry_until = now + 30;
+               }
+               else {
+                       if (entries[i].expiration < next)
+                               next = entries[i].expiration;
+                       i++;
+               }
+       }
+
+       if (!entries_nr) {
+               if (wait_for_entry_until <= now)
+                       return 0;
+               next = wait_for_entry_until;
+       }
+
+       return next - now;
+}
+
+static int read_request(FILE *fh, struct credential *c,
+                       struct strbuf *action, int *timeout) {
+       static struct strbuf item = STRBUF_INIT;
+       const char *p;
+
+       strbuf_getline(&item, fh, '\n');
+       p = skip_prefix(item.buf, "action=");
+       if (!p)
+               return error("client sent bogus action line: %s", item.buf);
+       strbuf_addstr(action, p);
+
+       strbuf_getline(&item, fh, '\n');
+       p = skip_prefix(item.buf, "timeout=");
+       if (!p)
+               return error("client sent bogus timeout line: %s", item.buf);
+       *timeout = atoi(p);
+
+       if (credential_read(c, fh) < 0)
+               return -1;
+       return 0;
+}
+
+static void serve_one_client(FILE *in, FILE *out)
+{
+       struct credential c = CREDENTIAL_INIT;
+       struct strbuf action = STRBUF_INIT;
+       int timeout = -1;
+
+       if (read_request(in, &c, &action, &timeout) < 0)
+               /* ignore error */ ;
+       else if (!strcmp(action.buf, "get")) {
+               struct credential_cache_entry *e = lookup_credential(&c);
+               if (e) {
+                       fprintf(out, "username=%s\n", e->item.username);
+                       fprintf(out, "password=%s\n", e->item.password);
+               }
+       }
+       else if (!strcmp(action.buf, "exit"))
+               exit(0);
+       else if (!strcmp(action.buf, "erase"))
+               remove_credential(&c);
+       else if (!strcmp(action.buf, "store")) {
+               if (timeout < 0)
+                       warning("cache client didn't specify a timeout");
+               else if (!c.username || !c.password)
+                       warning("cache client gave us a partial credential");
+               else {
+                       remove_credential(&c);
+                       cache_credential(&c, timeout);
+               }
+       }
+       else
+               warning("cache client sent unknown action: %s", action.buf);
+
+       credential_clear(&c);
+       strbuf_release(&action);
+}
+
+static int serve_cache_loop(int fd)
+{
+       struct pollfd pfd;
+       unsigned long wakeup;
+
+       wakeup = check_expirations();
+       if (!wakeup)
+               return 0;
+
+       pfd.fd = fd;
+       pfd.events = POLLIN;
+       if (poll(&pfd, 1, 1000 * wakeup) < 0) {
+               if (errno != EINTR)
+                       die_errno("poll failed");
+               return 1;
+       }
+
+       if (pfd.revents & POLLIN) {
+               int client, client2;
+               FILE *in, *out;
+
+               client = accept(fd, NULL, NULL);
+               if (client < 0) {
+                       warning("accept failed: %s", strerror(errno));
+                       return 1;
+               }
+               client2 = dup(client);
+               if (client2 < 0) {
+                       warning("dup failed: %s", strerror(errno));
+                       close(client);
+                       return 1;
+               }
+
+               in = xfdopen(client, "r");
+               out = xfdopen(client2, "w");
+               serve_one_client(in, out);
+               fclose(in);
+               fclose(out);
+       }
+       return 1;
+}
+
+static void serve_cache(const char *socket_path)
+{
+       int fd;
+
+       fd = unix_stream_listen(socket_path);
+       if (fd < 0)
+               die_errno("unable to bind to '%s'", socket_path);
+
+       printf("ok\n");
+       fclose(stdout);
+
+       while (serve_cache_loop(fd))
+               ; /* nothing */
+
+       close(fd);
+       unlink(socket_path);
+}
+
+static const char permissions_advice[] =
+"The permissions on your socket directory are too loose; other\n"
+"users may be able to read your cached credentials. Consider running:\n"
+"\n"
+"      chmod 0700 %s";
+static void check_socket_directory(const char *path)
+{
+       struct stat st;
+       char *path_copy = xstrdup(path);
+       char *dir = dirname(path_copy);
+
+       if (!stat(dir, &st)) {
+               if (st.st_mode & 077)
+                       die(permissions_advice, dir);
+               free(path_copy);
+               return;
+       }
+
+       /*
+        * We must be sure to create the directory with the correct mode,
+        * not just chmod it after the fact; otherwise, there is a race
+        * condition in which somebody can chdir to it, sleep, then try to open
+        * our protected socket.
+        */
+       if (safe_create_leading_directories_const(dir) < 0)
+               die_errno("unable to create directories for '%s'", dir);
+       if (mkdir(dir, 0700) < 0)
+               die_errno("unable to mkdir '%s'", dir);
+       free(path_copy);
+}
+
+int main(int argc, const char **argv)
+{
+       socket_path = argv[1];
+
+       if (!socket_path)
+               die("usage: git-credential-cache--daemon <socket_path>");
+       check_socket_directory(socket_path);
+
+       atexit(cleanup_socket);
+       sigchain_push_common(cleanup_socket_on_signal);
+
+       serve_cache(socket_path);
+
+       return 0;
+}
diff --git a/credential-cache.c b/credential-cache.c
new file mode 100644 (file)
index 0000000..dc98372
--- /dev/null
@@ -0,0 +1,120 @@
+#include "cache.h"
+#include "credential.h"
+#include "string-list.h"
+#include "parse-options.h"
+#include "unix-socket.h"
+#include "run-command.h"
+
+#define FLAG_SPAWN 0x1
+#define FLAG_RELAY 0x2
+
+static int send_request(const char *socket, const struct strbuf *out)
+{
+       int got_data = 0;
+       int fd = unix_stream_connect(socket);
+
+       if (fd < 0)
+               return -1;
+
+       if (write_in_full(fd, out->buf, out->len) < 0)
+               die_errno("unable to write to cache daemon");
+       shutdown(fd, SHUT_WR);
+
+       while (1) {
+               char in[1024];
+               int r;
+
+               r = read_in_full(fd, in, sizeof(in));
+               if (r == 0)
+                       break;
+               if (r < 0)
+                       die_errno("read error from cache daemon");
+               write_or_die(1, in, r);
+               got_data = 1;
+       }
+       return got_data;
+}
+
+static void spawn_daemon(const char *socket)
+{
+       struct child_process daemon;
+       const char *argv[] = { NULL, NULL, NULL };
+       char buf[128];
+       int r;
+
+       memset(&daemon, 0, sizeof(daemon));
+       argv[0] = "git-credential-cache--daemon";
+       argv[1] = socket;
+       daemon.argv = argv;
+       daemon.no_stdin = 1;
+       daemon.out = -1;
+
+       if (start_command(&daemon))
+               die_errno("unable to start cache daemon");
+       r = read_in_full(daemon.out, buf, sizeof(buf));
+       if (r < 0)
+               die_errno("unable to read result code from cache daemon");
+       if (r != 3 || memcmp(buf, "ok\n", 3))
+               die("cache daemon did not start: %.*s", r, buf);
+       close(daemon.out);
+}
+
+static void do_cache(const char *socket, const char *action, int timeout,
+                    int flags)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       strbuf_addf(&buf, "action=%s\n", action);
+       strbuf_addf(&buf, "timeout=%d\n", timeout);
+       if (flags & FLAG_RELAY) {
+               if (strbuf_read(&buf, 0, 0) < 0)
+                       die_errno("unable to relay credential");
+       }
+
+       if (!send_request(socket, &buf))
+               return;
+       if (flags & FLAG_SPAWN) {
+               spawn_daemon(socket);
+               send_request(socket, &buf);
+       }
+       strbuf_release(&buf);
+}
+
+int main(int argc, const char **argv)
+{
+       char *socket_path = NULL;
+       int timeout = 900;
+       const char *op;
+       const char * const usage[] = {
+               "git credential-cache [options] <action>",
+               NULL
+       };
+       struct option options[] = {
+               OPT_INTEGER(0, "timeout", &timeout,
+                           "number of seconds to cache credentials"),
+               OPT_STRING(0, "socket", &socket_path, "path",
+                          "path of cache-daemon socket"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, NULL, options, usage, 0);
+       if (!argc)
+               usage_with_options(usage, options);
+       op = argv[0];
+
+       if (!socket_path)
+               socket_path = expand_user_path("~/.git-credential-cache/socket");
+       if (!socket_path)
+               die("unable to find a suitable socket path; use --socket");
+
+       if (!strcmp(op, "exit"))
+               do_cache(socket_path, op, timeout, 0);
+       else if (!strcmp(op, "get") || !strcmp(op, "erase"))
+               do_cache(socket_path, op, timeout, FLAG_RELAY);
+       else if (!strcmp(op, "store"))
+               do_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN);
+       else
+               ; /* ignore unknown operation */
+
+       return 0;
+}
diff --git a/credential-store.c b/credential-store.c
new file mode 100644 (file)
index 0000000..26f7589
--- /dev/null
@@ -0,0 +1,157 @@
+#include "cache.h"
+#include "credential.h"
+#include "string-list.h"
+#include "parse-options.h"
+
+static struct lock_file credential_lock;
+
+static void parse_credential_file(const char *fn,
+                                 struct credential *c,
+                                 void (*match_cb)(struct credential *),
+                                 void (*other_cb)(struct strbuf *))
+{
+       FILE *fh;
+       struct strbuf line = STRBUF_INIT;
+       struct credential entry = CREDENTIAL_INIT;
+
+       fh = fopen(fn, "r");
+       if (!fh) {
+               if (errno != ENOENT)
+                       die_errno("unable to open %s", fn);
+               return;
+       }
+
+       while (strbuf_getline(&line, fh, '\n') != EOF) {
+               credential_from_url(&entry, line.buf);
+               if (entry.username && entry.password &&
+                   credential_match(c, &entry)) {
+                       if (match_cb) {
+                               match_cb(&entry);
+                               break;
+                       }
+               }
+               else if (other_cb)
+                       other_cb(&line);
+       }
+
+       credential_clear(&entry);
+       strbuf_release(&line);
+       fclose(fh);
+}
+
+static void print_entry(struct credential *c)
+{
+       printf("username=%s\n", c->username);
+       printf("password=%s\n", c->password);
+}
+
+static void print_line(struct strbuf *buf)
+{
+       strbuf_addch(buf, '\n');
+       write_or_die(credential_lock.fd, buf->buf, buf->len);
+}
+
+static void rewrite_credential_file(const char *fn, struct credential *c,
+                                   struct strbuf *extra)
+{
+       if (hold_lock_file_for_update(&credential_lock, fn, 0) < 0)
+               die_errno("unable to get credential storage lock");
+       if (extra)
+               print_line(extra);
+       parse_credential_file(fn, c, NULL, print_line);
+       if (commit_lock_file(&credential_lock) < 0)
+               die_errno("unable to commit credential store");
+}
+
+static void store_credential(const char *fn, struct credential *c)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       /*
+        * Sanity check that what we are storing is actually sensible.
+        * In particular, we can't make a URL without a protocol field.
+        * Without either a host or pathname (depending on the scheme),
+        * we have no primary key. And without a username and password,
+        * we are not actually storing a credential.
+        */
+       if (!c->protocol || !(c->host || c->path) ||
+           !c->username || !c->password)
+               return;
+
+       strbuf_addf(&buf, "%s://", c->protocol);
+       strbuf_addstr_urlencode(&buf, c->username, 1);
+       strbuf_addch(&buf, ':');
+       strbuf_addstr_urlencode(&buf, c->password, 1);
+       strbuf_addch(&buf, '@');
+       if (c->host)
+               strbuf_addstr_urlencode(&buf, c->host, 1);
+       if (c->path) {
+               strbuf_addch(&buf, '/');
+               strbuf_addstr_urlencode(&buf, c->path, 0);
+       }
+
+       rewrite_credential_file(fn, c, &buf);
+       strbuf_release(&buf);
+}
+
+static void remove_credential(const char *fn, struct credential *c)
+{
+       /*
+        * Sanity check that we actually have something to match
+        * against. The input we get is a restrictive pattern,
+        * so technically a blank credential means "erase everything".
+        * But it is too easy to accidentally send this, since it is equivalent
+        * to empty input. So explicitly disallow it, and require that the
+        * pattern have some actual content to match.
+        */
+       if (c->protocol || c->host || c->path || c->username)
+               rewrite_credential_file(fn, c, NULL);
+}
+
+static int lookup_credential(const char *fn, struct credential *c)
+{
+       parse_credential_file(fn, c, print_entry, NULL);
+       return c->username && c->password;
+}
+
+int main(int argc, const char **argv)
+{
+       const char * const usage[] = {
+               "git credential-store [options] <action>",
+               NULL
+       };
+       const char *op;
+       struct credential c = CREDENTIAL_INIT;
+       char *file = NULL;
+       struct option options[] = {
+               OPT_STRING(0, "file", &file, "path",
+                          "fetch and store credentials in <path>"),
+               OPT_END()
+       };
+
+       umask(077);
+
+       argc = parse_options(argc, argv, NULL, options, usage, 0);
+       if (argc != 1)
+               usage_with_options(usage, options);
+       op = argv[0];
+
+       if (!file)
+               file = expand_user_path("~/.git-credentials");
+       if (!file)
+               die("unable to set up default path; use --file");
+
+       if (credential_read(&c, stdin) < 0)
+               die("unable to read credential");
+
+       if (!strcmp(op, "get"))
+               lookup_credential(file, &c);
+       else if (!strcmp(op, "erase"))
+               remove_credential(file, &c);
+       else if (!strcmp(op, "store"))
+               store_credential(file, &c);
+       else
+               ; /* Ignore unknown operation. */
+
+       return 0;
+}
diff --git a/credential.c b/credential.c
new file mode 100644 (file)
index 0000000..62d1c56
--- /dev/null
@@ -0,0 +1,365 @@
+#include "cache.h"
+#include "credential.h"
+#include "string-list.h"
+#include "run-command.h"
+#include "url.h"
+#include "prompt.h"
+
+void credential_init(struct credential *c)
+{
+       memset(c, 0, sizeof(*c));
+       c->helpers.strdup_strings = 1;
+}
+
+void credential_clear(struct credential *c)
+{
+       free(c->protocol);
+       free(c->host);
+       free(c->path);
+       free(c->username);
+       free(c->password);
+       string_list_clear(&c->helpers, 0);
+
+       credential_init(c);
+}
+
+int credential_match(const struct credential *want,
+                    const struct credential *have)
+{
+#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x)))
+       return CHECK(protocol) &&
+              CHECK(host) &&
+              CHECK(path) &&
+              CHECK(username);
+#undef CHECK
+}
+
+static int credential_config_callback(const char *var, const char *value,
+                                     void *data)
+{
+       struct credential *c = data;
+       const char *key, *dot;
+
+       key = skip_prefix(var, "credential.");
+       if (!key)
+               return 0;
+
+       if (!value)
+               return config_error_nonbool(var);
+
+       dot = strrchr(key, '.');
+       if (dot) {
+               struct credential want = CREDENTIAL_INIT;
+               char *url = xmemdupz(key, dot - key);
+               int matched;
+
+               credential_from_url(&want, url);
+               matched = credential_match(&want, c);
+
+               credential_clear(&want);
+               free(url);
+
+               if (!matched)
+                       return 0;
+               key = dot + 1;
+       }
+
+       if (!strcmp(key, "helper"))
+               string_list_append(&c->helpers, value);
+       else if (!strcmp(key, "username")) {
+               if (!c->username)
+                       c->username = xstrdup(value);
+       }
+       else if (!strcmp(key, "usehttppath"))
+               c->use_http_path = git_config_bool(var, value);
+
+       return 0;
+}
+
+static int proto_is_http(const char *s)
+{
+       if (!s)
+               return 0;
+       return !strcmp(s, "https") || !strcmp(s, "http");
+}
+
+static void credential_apply_config(struct credential *c)
+{
+       if (c->configured)
+               return;
+       git_config(credential_config_callback, c);
+       c->configured = 1;
+
+       if (!c->use_http_path && proto_is_http(c->protocol)) {
+               free(c->path);
+               c->path = NULL;
+       }
+}
+
+static void credential_describe(struct credential *c, struct strbuf *out)
+{
+       if (!c->protocol)
+               return;
+       strbuf_addf(out, "%s://", c->protocol);
+       if (c->username && *c->username)
+               strbuf_addf(out, "%s@", c->username);
+       if (c->host)
+               strbuf_addstr(out, c->host);
+       if (c->path)
+               strbuf_addf(out, "/%s", c->path);
+}
+
+static char *credential_ask_one(const char *what, struct credential *c,
+                               int flags)
+{
+       struct strbuf desc = STRBUF_INIT;
+       struct strbuf prompt = STRBUF_INIT;
+       char *r;
+
+       credential_describe(c, &desc);
+       if (desc.len)
+               strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
+       else
+               strbuf_addf(&prompt, "%s: ", what);
+
+       r = git_prompt(prompt.buf, flags);
+
+       strbuf_release(&desc);
+       strbuf_release(&prompt);
+       return xstrdup(r);
+}
+
+static void credential_getpass(struct credential *c)
+{
+       if (!c->username)
+               c->username = credential_ask_one("Username", c,
+                                                PROMPT_ASKPASS|PROMPT_ECHO);
+       if (!c->password)
+               c->password = credential_ask_one("Password", c,
+                                                PROMPT_ASKPASS);
+}
+
+int credential_read(struct credential *c, FILE *fp)
+{
+       struct strbuf line = STRBUF_INIT;
+
+       while (strbuf_getline(&line, fp, '\n') != EOF) {
+               char *key = line.buf;
+               char *value = strchr(key, '=');
+
+               if (!line.len)
+                       break;
+
+               if (!value) {
+                       warning("invalid credential line: %s", key);
+                       strbuf_release(&line);
+                       return -1;
+               }
+               *value++ = '\0';
+
+               if (!strcmp(key, "username")) {
+                       free(c->username);
+                       c->username = xstrdup(value);
+               } else if (!strcmp(key, "password")) {
+                       free(c->password);
+                       c->password = xstrdup(value);
+               } else if (!strcmp(key, "protocol")) {
+                       free(c->protocol);
+                       c->protocol = xstrdup(value);
+               } else if (!strcmp(key, "host")) {
+                       free(c->host);
+                       c->host = xstrdup(value);
+               } else if (!strcmp(key, "path")) {
+                       free(c->path);
+                       c->path = xstrdup(value);
+               }
+               /*
+                * Ignore other lines; we don't know what they mean, but
+                * this future-proofs us when later versions of git do
+                * learn new lines, and the helpers are updated to match.
+                */
+       }
+
+       strbuf_release(&line);
+       return 0;
+}
+
+static void credential_write_item(FILE *fp, const char *key, const char *value)
+{
+       if (!value)
+               return;
+       fprintf(fp, "%s=%s\n", key, value);
+}
+
+static void credential_write(const struct credential *c, FILE *fp)
+{
+       credential_write_item(fp, "protocol", c->protocol);
+       credential_write_item(fp, "host", c->host);
+       credential_write_item(fp, "path", c->path);
+       credential_write_item(fp, "username", c->username);
+       credential_write_item(fp, "password", c->password);
+}
+
+static int run_credential_helper(struct credential *c,
+                                const char *cmd,
+                                int want_output)
+{
+       struct child_process helper;
+       const char *argv[] = { NULL, NULL };
+       FILE *fp;
+
+       memset(&helper, 0, sizeof(helper));
+       argv[0] = cmd;
+       helper.argv = argv;
+       helper.use_shell = 1;
+       helper.in = -1;
+       if (want_output)
+               helper.out = -1;
+       else
+               helper.no_stdout = 1;
+
+       if (start_command(&helper) < 0)
+               return -1;
+
+       fp = xfdopen(helper.in, "w");
+       credential_write(c, fp);
+       fclose(fp);
+
+       if (want_output) {
+               int r;
+               fp = xfdopen(helper.out, "r");
+               r = credential_read(c, fp);
+               fclose(fp);
+               if (r < 0) {
+                       finish_command(&helper);
+                       return -1;
+               }
+       }
+
+       if (finish_command(&helper))
+               return -1;
+       return 0;
+}
+
+static int credential_do(struct credential *c, const char *helper,
+                        const char *operation)
+{
+       struct strbuf cmd = STRBUF_INIT;
+       int r;
+
+       if (helper[0] == '!')
+               strbuf_addstr(&cmd, helper + 1);
+       else if (is_absolute_path(helper))
+               strbuf_addstr(&cmd, helper);
+       else
+               strbuf_addf(&cmd, "git credential-%s", helper);
+
+       strbuf_addf(&cmd, " %s", operation);
+       r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
+
+       strbuf_release(&cmd);
+       return r;
+}
+
+void credential_fill(struct credential *c)
+{
+       int i;
+
+       if (c->username && c->password)
+               return;
+
+       credential_apply_config(c);
+
+       for (i = 0; i < c->helpers.nr; i++) {
+               credential_do(c, c->helpers.items[i].string, "get");
+               if (c->username && c->password)
+                       return;
+       }
+
+       credential_getpass(c);
+       if (!c->username && !c->password)
+               die("unable to get password from user");
+}
+
+void credential_approve(struct credential *c)
+{
+       int i;
+
+       if (c->approved)
+               return;
+       if (!c->username || !c->password)
+               return;
+
+       credential_apply_config(c);
+
+       for (i = 0; i < c->helpers.nr; i++)
+               credential_do(c, c->helpers.items[i].string, "store");
+       c->approved = 1;
+}
+
+void credential_reject(struct credential *c)
+{
+       int i;
+
+       credential_apply_config(c);
+
+       for (i = 0; i < c->helpers.nr; i++)
+               credential_do(c, c->helpers.items[i].string, "erase");
+
+       free(c->username);
+       c->username = NULL;
+       free(c->password);
+       c->password = NULL;
+       c->approved = 0;
+}
+
+void credential_from_url(struct credential *c, const char *url)
+{
+       const char *at, *colon, *cp, *slash, *host, *proto_end;
+
+       credential_clear(c);
+
+       /*
+        * Match one of:
+        *   (1) proto://<host>/...
+        *   (2) proto://<user>@<host>/...
+        *   (3) proto://<user>:<pass>@<host>/...
+        */
+       proto_end = strstr(url, "://");
+       if (!proto_end)
+               return;
+       cp = proto_end + 3;
+       at = strchr(cp, '@');
+       colon = strchr(cp, ':');
+       slash = strchrnul(cp, '/');
+
+       if (!at || slash <= at) {
+               /* Case (1) */
+               host = cp;
+       }
+       else if (!colon || at <= colon) {
+               /* Case (2) */
+               c->username = url_decode_mem(cp, at - cp);
+               host = at + 1;
+       } else {
+               /* Case (3) */
+               c->username = url_decode_mem(cp, colon - cp);
+               c->password = url_decode_mem(colon + 1, at - (colon + 1));
+               host = at + 1;
+       }
+
+       if (proto_end - url > 0)
+               c->protocol = xmemdupz(url, proto_end - url);
+       if (slash - host > 0)
+               c->host = url_decode_mem(host, slash - host);
+       /* Trim leading and trailing slashes from path */
+       while (*slash == '/')
+               slash++;
+       if (*slash) {
+               char *p;
+               c->path = url_decode(slash);
+               p = c->path + strlen(c->path) - 1;
+               while (p > c->path && *p == '/')
+                       *p-- = '\0';
+       }
+}
diff --git a/credential.h b/credential.h
new file mode 100644 (file)
index 0000000..96ea41b
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef CREDENTIAL_H
+#define CREDENTIAL_H
+
+#include "string-list.h"
+
+struct credential {
+       struct string_list helpers;
+       unsigned approved:1,
+                configured:1,
+                use_http_path:1;
+
+       char *username;
+       char *password;
+       char *protocol;
+       char *host;
+       char *path;
+};
+
+#define CREDENTIAL_INIT { STRING_LIST_INIT_DUP }
+
+void credential_init(struct credential *);
+void credential_clear(struct credential *);
+
+void credential_fill(struct credential *);
+void credential_approve(struct credential *);
+void credential_reject(struct credential *);
+
+int credential_read(struct credential *, FILE *);
+void credential_from_url(struct credential *, const char *url);
+int credential_match(const struct credential *have,
+                    const struct credential *want);
+
+#endif /* CREDENTIAL_H */
index fc97d6e04528b5c5b55fc211a462f3cb828f3d49..53f5375b6ca3368de6647cf5edcd7fb4dec79657 100644 (file)
@@ -158,6 +158,26 @@ struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp
        return f;
 }
 
+void sha1file_checkpoint(struct sha1file *f, struct sha1file_checkpoint *checkpoint)
+{
+       sha1flush(f);
+       checkpoint->offset = f->total;
+       checkpoint->ctx = f->ctx;
+}
+
+int sha1file_truncate(struct sha1file *f, struct sha1file_checkpoint *checkpoint)
+{
+       off_t offset = checkpoint->offset;
+
+       if (ftruncate(f->fd, offset) ||
+           lseek(f->fd, offset, SEEK_SET) != offset)
+               return -1;
+       f->total = offset;
+       f->ctx = checkpoint->ctx;
+       f->offset = 0; /* sha1flush() was called in checkpoint */
+       return 0;
+}
+
 void crc32_begin(struct sha1file *f)
 {
        f->crc32 = crc32(0, NULL, 0);
index 6a7967c6bf604076c7d68ce139f65f34df3bc30e..3b540bdc21d2edf8d3d6fc2b5e52cc840aa19395 100644 (file)
@@ -17,6 +17,15 @@ struct sha1file {
        unsigned char buffer[8192];
 };
 
+/* Checkpoint */
+struct sha1file_checkpoint {
+       off_t offset;
+       git_SHA_CTX ctx;
+};
+
+extern void sha1file_checkpoint(struct sha1file *, struct sha1file_checkpoint *);
+extern int sha1file_truncate(struct sha1file *, struct sha1file_checkpoint *);
+
 /* sha1close flags */
 #define CSUM_CLOSE     1
 #define CSUM_FSYNC     2
index fa283003ea8809a2defc63f3b36cebe135028ddb..15ce918a21e1cce6cb603caa8638e4d854eee9dd 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -1099,6 +1099,8 @@ int main(int argc, char **argv)
        struct credentials *cred = NULL;
        int i;
 
+       git_setup_gettext();
+
        git_extract_argv0_path(argv[0]);
 
        for (i = 1; i < argc; i++) {
index 2c41d7d6cb66832197d69d49065a3d5b3bb2e5da..c93b8f44df0171a0f923546813d00e8b8e837af1 100644 (file)
@@ -62,6 +62,7 @@ int grafts_replace_parents = 1;
 int core_apply_sparse_checkout;
 int merge_log_config = -1;
 struct startup_info *startup_info;
+unsigned long pack_size_limit_cfg;
 
 /* Parallel index stat data preload? */
 int core_preload_index = 0;
index 8d8ea3c45c0be5481c7b452c27ee0d163d69fb00..4b9c4b73a020e9eefa4caf0bd6ec833df424c9a0 100644 (file)
@@ -1143,17 +1143,11 @@ static int store_object(
        return 0;
 }
 
-static void truncate_pack(off_t to, git_SHA_CTX *ctx)
+static void truncate_pack(struct sha1file_checkpoint *checkpoint)
 {
-       if (ftruncate(pack_data->pack_fd, to)
-        || lseek(pack_data->pack_fd, to, SEEK_SET) != to)
+       if (sha1file_truncate(pack_file, checkpoint))
                die_errno("cannot truncate pack to skip duplicate");
-       pack_size = to;
-
-       /* yes this is a layering violation */
-       pack_file->total = to;
-       pack_file->offset = 0;
-       pack_file->ctx = *ctx;
+       pack_size = checkpoint->offset;
 }
 
 static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
@@ -1166,8 +1160,8 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
        unsigned long hdrlen;
        off_t offset;
        git_SHA_CTX c;
-       git_SHA_CTX pack_file_ctx;
        git_zstream s;
+       struct sha1file_checkpoint checkpoint;
        int status = Z_OK;
 
        /* Determine if we should auto-checkpoint. */
@@ -1175,11 +1169,8 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
                || (pack_size + 60 + len) < pack_size)
                cycle_packfile();
 
-       offset = pack_size;
-
-       /* preserve the pack_file SHA1 ctx in case we have to truncate later */
-       sha1flush(pack_file);
-       pack_file_ctx = pack_file->ctx;
+       sha1file_checkpoint(pack_file, &checkpoint);
+       offset = checkpoint.offset;
 
        hdrlen = snprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1;
        if (out_sz <= hdrlen)
@@ -1245,14 +1236,14 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
 
        if (e->idx.offset) {
                duplicate_count_by_type[OBJ_BLOB]++;
-               truncate_pack(offset, &pack_file_ctx);
+               truncate_pack(&checkpoint);
 
        } else if (find_sha1_pack(sha1, packed_git)) {
                e->type = OBJ_BLOB;
                e->pack_id = MAX_PACK_ID;
                e->idx.offset = 1; /* just not zero! */
                duplicate_count_by_type[OBJ_BLOB]++;
-               truncate_pack(offset, &pack_file_ctx);
+               truncate_pack(&checkpoint);
 
        } else {
                e->depth = 0;
@@ -2173,6 +2164,11 @@ static uintmax_t do_change_note_fanout(
 
                if (tmp_hex_sha1_len == 40 && !get_sha1_hex(hex_sha1, sha1)) {
                        /* This is a note entry */
+                       if (fanout == 0xff) {
+                               /* Counting mode, no rename */
+                               num_notes++;
+                               continue;
+                       }
                        construct_path_with_fanout(hex_sha1, fanout, realpath);
                        if (!strcmp(fullpath, realpath)) {
                                /* Note entry is in correct location */
@@ -2379,7 +2375,7 @@ static void file_change_cr(struct branch *b, int rename)
                leaf.tree);
 }
 
-static void note_change_n(struct branch *b, unsigned char old_fanout)
+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;
@@ -2390,6 +2386,23 @@ static void note_change_n(struct branch *b, unsigned char old_fanout)
        uint16_t inline_data = 0;
        unsigned char new_fanout;
 
+       /*
+        * When loading a branch, we don't traverse its tree to count the real
+        * number of notes (too expensive to do this for all non-note refs).
+        * This means that recently loaded notes refs might incorrectly have
+        * b->num_notes == 0, and consequently, old_fanout might be wrong.
+        *
+        * Fix this by traversing the tree and counting the number of notes
+        * when b->num_notes == 0. If the notes tree is truly empty, the
+        * calculation should not take long.
+        */
+       if (b->num_notes == 0 && *old_fanout == 0) {
+               /* Invoke change_note_fanout() in "counting mode". */
+               b->num_notes = change_note_fanout(&b->branch_tree, 0xff);
+               *old_fanout = convert_num_notes_to_fanout(b->num_notes);
+       }
+
+       /* Now parse the notemodify command. */
        /* <dataref> or 'inline' */
        if (*p == ':') {
                char *x;
@@ -2450,7 +2463,7 @@ static void note_change_n(struct branch *b, unsigned char old_fanout)
                            typename(type), command_buf.buf);
        }
 
-       construct_path_with_fanout(sha1_to_hex(commit_sha1), old_fanout, path);
+       construct_path_with_fanout(sha1_to_hex(commit_sha1), *old_fanout, path);
        if (tree_content_remove(&b->branch_tree, path, NULL))
                b->num_notes--;
 
@@ -2637,7 +2650,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, prev_fanout);
+                       note_change_n(b, &prev_fanout);
                else if (!strcmp("deleteall", command_buf.buf))
                        file_change_deleteall(b);
                else if (!prefixcmp(command_buf.buf, "ls "))
@@ -3292,6 +3305,8 @@ int main(int argc, const char **argv)
 
        git_extract_argv0_path(argv[0]);
 
+       git_setup_gettext();
+
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage(fast_import_usage);
 
index ae5394a49672b6c7fa6c5c69766aa4e80c187c5f..f75bca7f56b7b27c135d92acba816c40211fdece 100644 (file)
--- a/gettext.c
+++ b/gettext.c
@@ -5,6 +5,18 @@
 #include "git-compat-util.h"
 #include "gettext.h"
 
+#ifndef NO_GETTEXT
+#      include <locale.h>
+#      include <libintl.h>
+#      ifdef HAVE_LIBCHARSET_H
+#              include <libcharset.h>
+#      else
+#              include <langinfo.h>
+#              define locale_charset() nl_langinfo(CODESET)
+#      endif
+#endif
+
+#ifdef GETTEXT_POISON
 int use_gettext_poison(void)
 {
        static int poison_requested = -1;
@@ -12,3 +24,108 @@ int use_gettext_poison(void)
                poison_requested = getenv("GIT_GETTEXT_POISON") ? 1 : 0;
        return poison_requested;
 }
+#endif
+
+#ifndef NO_GETTEXT
+static void init_gettext_charset(const char *domain)
+{
+       const char *charset;
+
+       /*
+          This trick arranges for messages to be emitted in the user's
+          requested encoding, but avoids setting LC_CTYPE from the
+          environment for the whole program.
+
+          This primarily done to avoid a bug in vsnprintf in the GNU C
+          Library [1]. which triggered a "your vsnprintf is broken" error
+          on Git's own repository when inspecting v0.99.6~1 under a UTF-8
+          locale.
+
+          That commit contains a ISO-8859-1 encoded author name, which
+          the locale aware vsnprintf(3) won't interpolate in the format
+          argument, due to mismatch between the data encoding and the
+          locale.
+
+          Even if it wasn't for that bug we wouldn't want to use LC_CTYPE at
+          this point, because it'd require auditing all the code that uses C
+          functions whose semantics are modified by LC_CTYPE.
+
+          But only setting LC_MESSAGES as we do creates a problem, since
+          we declare the encoding of our PO files[2] the gettext
+          implementation will try to recode it to the user's locale, but
+          without LC_CTYPE it'll emit something like this on 'git init'
+          under the Icelandic locale:
+
+              Bj? til t?ma Git lind ? /hlagh/.git/
+
+          Gettext knows about the encoding of our PO file, but we haven't
+          told it about the user's encoding, so all the non-US-ASCII
+          characters get encoded to question marks.
+
+          But we're in luck! We can set LC_CTYPE from the environment
+          only while we call nl_langinfo and
+          bind_textdomain_codeset. That suffices to tell gettext what
+          encoding it should emit in, so it'll now say:
+
+              Bjó til tóma Git lind í /hlagh/.git/
+
+          And the equivalent ISO-8859-1 string will be emitted under a
+          ISO-8859-1 locale.
+
+          With this change way we get the advantages of setting LC_CTYPE
+          (talk to the user in his language/encoding), without the major
+          drawbacks (changed semantics for C functions we rely on).
+
+          However foreign functions using other message catalogs that
+          aren't using our neat trick will still have a problem, e.g. if
+          we have to call perror(3):
+
+          #include <stdio.h>
+          #include <locale.h>
+          #include <errno.h>
+
+          int main(void)
+          {
+                  setlocale(LC_MESSAGES, "");
+                  setlocale(LC_CTYPE, "C");
+                  errno = ENODEV;
+                  perror("test");
+                  return 0;
+          }
+
+          Running that will give you a message with question marks:
+
+          $ LANGUAGE= LANG=de_DE.utf8 ./test
+          test: Kein passendes Ger?t gefunden
+
+          In the long term we should probably see about getting that
+          vsnprintf bug in glibc fixed, and audit our code so it won't
+          fall apart under a non-C locale.
+
+          Then we could simply set LC_CTYPE from the environment, which would
+          make things like the external perror(3) messages work.
+
+          See t/t0203-gettext-setlocale-sanity.sh's "gettext.c" tests for
+          regression tests.
+
+          1. http://sourceware.org/bugzilla/show_bug.cgi?id=6530
+          2. E.g. "Content-Type: text/plain; charset=UTF-8\n" in po/is.po
+       */
+       setlocale(LC_CTYPE, "");
+       charset = locale_charset();
+       bind_textdomain_codeset(domain, charset);
+       setlocale(LC_CTYPE, "C");
+}
+
+void git_setup_gettext(void)
+{
+       const char *podir = getenv("GIT_TEXTDOMAINDIR");
+
+       if (!podir)
+               podir = GIT_LOCALE_PATH;
+       bindtextdomain("git", podir);
+       setlocale(LC_MESSAGES, "");
+       init_gettext_charset("git");
+       textdomain("git");
+}
+#endif
index 24d91824e5a810cb3f2cbc4ca0514ec68725597d..57ba8bb02e39d59752a5b2fbf016bc6fe49d27c1 100644 (file)
--- a/gettext.h
+++ b/gettext.h
 #error "namespace conflict: '_' or 'Q_' is pre-defined?"
 #endif
 
+#ifndef NO_GETTEXT
+#      include <libintl.h>
+#else
+#      ifdef gettext
+#              undef gettext
+#      endif
+#      define gettext(s) (s)
+#      ifdef ngettext
+#              undef ngettext
+#      endif
+#      define ngettext(s, p, n) ((n == 1) ? (s) : (p))
+#endif
+
 #define FORMAT_PRESERVING(n) __attribute__((format_arg(n)))
 
+#ifndef NO_GETTEXT
+extern void git_setup_gettext(void);
+#else
+static inline void git_setup_gettext(void)
+{
+}
+#endif
+
 #ifdef GETTEXT_POISON
 extern int use_gettext_poison(void);
 #else
@@ -23,7 +44,7 @@ extern int use_gettext_poison(void);
 
 static inline FORMAT_PRESERVING(1) const char *_(const char *msgid)
 {
-       return use_gettext_poison() ? "# GETTEXT POISON #" : msgid;
+       return use_gettext_poison() ? "# GETTEXT POISON #" : gettext(msgid);
 }
 
 static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2)
@@ -31,7 +52,7 @@ const char *Q_(const char *msgid, const char *plu, unsigned long n)
 {
        if (use_gettext_poison())
                return "# GETTEXT POISON #";
-       return n == 1 ? msgid : plu;
+       return ngettext(msgid, plu, n);
 }
 
 /* Mark msgid for translation but do not translate it. */
index 77062ed2a66e1efda60ead8ce99abedc8d04ce71..8f3972cd3295665b8b1f69b5db7aff67c8c61613 100644 (file)
 #include <arpa/inet.h>
 #include <netdb.h>
 #include <pwd.h>
+#include <sys/un.h>
 #ifndef NO_INTTYPES_H
 #include <inttypes.h>
 #else
index f96112d47f743289bf3893981bc89dcaa9caae19..33d07c06bd90833ce56bc64c13bdc08c1997c3fb 100644 (file)
@@ -1,3 +1,4 @@
+*           whitespace=indent-with-non-tab,trailing-space,space-before-tab,tabwidth=4
 *           encoding=US-ASCII
 git-gui.sh  encoding=UTF-8
 /po/*.po    encoding=UTF-8
index 1fb4d9b4b7393d2c803457365127692fb093338f..65709437ff06a2b371fe70fd846922c73ebc23b0 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=0.13.GITGUI
+DEF_VER=0.16.GITGUI
 
 LF='
 '
index f8971603f77a495b253b5470f5546d51dd6efd14..ba4e5c1330c84f54a6809991eec22aa2e8e25ddd 100755 (executable)
@@ -299,7 +299,9 @@ proc is_config_true {name} {
        global repo_config
        if {[catch {set v $repo_config($name)}]} {
                return 0
-       } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
+       }
+       set v [string tolower $v]
+       if {$v eq {} || $v eq {true} || $v eq {1} || $v eq {yes} || $v eq {on}} {
                return 1
        } else {
                return 0
@@ -310,7 +312,9 @@ proc is_config_false {name} {
        global repo_config
        if {[catch {set v $repo_config($name)}]} {
                return 0
-       } elseif {$v eq {false} || $v eq {0} || $v eq {no}} {
+       }
+       set v [string tolower $v]
+       if {$v eq {false} || $v eq {0} || $v eq {no} || $v eq {off}} {
                return 1
        } else {
                return 0
@@ -460,6 +464,35 @@ proc _which {what args} {
        return {}
 }
 
+# Test a file for a hashbang to identify executable scripts on Windows.
+proc is_shellscript {filename} {
+       if {![file exists $filename]} {return 0}
+       set f [open $filename r]
+       fconfigure $f -encoding binary
+       set magic [read $f 2]
+       close $f
+       return [expr {$magic eq "#!"}]
+}
+
+# Run a command connected via pipes on stdout.
+# This is for use with textconv filters and uses sh -c "..." to allow it to
+# contain a command with arguments. On windows we must check for shell
+# scripts specifically otherwise just call the filter command.
+proc open_cmd_pipe {cmd path} {
+       global env
+       if {![file executable [shellpath]]} {
+               set exe [auto_execok [lindex $cmd 0]]
+               if {[is_shellscript [lindex $exe 0]]} {
+                       set run [linsert [auto_execok sh] end -c "$cmd \"\$0\"" $path]
+               } else {
+                       set run [concat $exe [lrange $cmd 1 end] $path]
+               }
+       } else {
+               set run [list [shellpath] -c "$cmd \"\$0\"" $path]
+       }
+       return [open |$run r]
+}
+
 proc _lappend_nice {cmd_var} {
        global _nice
        upvar $cmd_var cmd
@@ -725,7 +758,10 @@ if {[is_Windows]} {
                gitlogo put gray26  -to  5 15 11 16
                gitlogo redither
 
-               wm iconphoto . -default gitlogo
+               image create photo gitlogo32 -width 32 -height 32
+               gitlogo32 copy gitlogo -zoom 2 2
+
+               wm iconphoto . -default gitlogo gitlogo32
        }
 }
 
@@ -846,6 +882,7 @@ set default_config(gui.fastcopyblame) false
 set default_config(gui.copyblamethreshold) 40
 set default_config(gui.blamehistoryctx) 7
 set default_config(gui.diffcontext) 5
+set default_config(gui.diffopts) {}
 set default_config(gui.commitmsgwidth) 75
 set default_config(gui.newbranchtemplate) {}
 set default_config(gui.spellingdictionary) {}
@@ -859,6 +896,7 @@ set font_descs {
        {fontui   font_ui   {mc "Main Font"}}
        {fontdiff font_diff {mc "Diff/Console Font"}}
 }
+set default_config(gui.stageuntracked) ask
 
 ######################################################################
 ##
@@ -1060,6 +1098,10 @@ git-version proc _parse_config {arr_name args} {
                                } else {
                                        set arr($name) $value
                                }
+                       } elseif {[regexp {^([^\n]+)$} $line line name]} {
+                               # no value given, but interpreting them as
+                               # boolean will be handled as true
+                               set arr($name) {}
                        }
                }
        }
@@ -1075,6 +1117,10 @@ git-version proc _parse_config {arr_name args} {
                                        } else {
                                                set arr($name) $value
                                        }
+                               } elseif {[regexp {^([^=]+)$} $line line name]} {
+                                       # no value given, but interpreting them as
+                                       # boolean will be handled as true
+                                       set arr($name) {}
                                }
                        }
                        close $fd_rc
@@ -2474,6 +2520,7 @@ proc toggle_or_diff {w x y} {
                                [concat $after [list ui_ready]]
                }
        } else {
+               set selected_paths($path) 1
                show_diff $path $w $lno
        }
 }
@@ -3362,6 +3409,7 @@ foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 gr
        $ui_diff tag configure clri3$n -background $c
 }
 $ui_diff tag configure clr1 -font font_diffbold
+$ui_diff tag configure clr4 -underline 1
 
 $ui_diff tag conf d_info -foreground blue -font font_diffbold
 
@@ -3878,7 +3926,7 @@ after 1 {
                $ui_comm configure -state disabled -background gray
        }
 }
-if {[is_enabled multicommit]} {
+if {[is_enabled multicommit] && ![is_config_false gui.gcwarning]} {
        after 1000 hint_gc
 }
 if {[is_enabled retcode]} {
index 691941e95948e7d332d6984aa6e2cc0956147550..324f7744c495dc2eb9a1d4acde09352d7948efe8 100644 (file)
@@ -219,7 +219,8 @@ constructor new {i_commit i_path i_jump} {
        eval grid $w_columns $w.file_pane.out.sby -sticky nsew
        grid conf \
                $w.file_pane.out.sbx \
-               -column [expr {[llength $w_columns] - 1}] \
+               -column 0 \
+               -columnspan [expr {[llength $w_columns] + 1}] \
                -sticky we
        grid columnconfigure \
                $w.file_pane.out \
@@ -229,12 +230,14 @@ constructor new {i_commit i_path i_jump} {
 
        set finder [::searchbar::new \
                $w.file_pane.out.ff $w_file \
-               -column [expr {[llength $w_columns] - 1}] \
+               -column 0 \
+               -columnspan [expr {[llength $w_columns] + 1}] \
                ]
 
        set gotoline [::linebar::new \
                $w.file_pane.out.lf $w_file \
-               -column [expr {[llength $w_columns] - 1}] \
+               -column 0 \
+               -columnspan [expr {[llength $w_columns] + 1}] \
                ]
 
        set w_cviewer $w.file_pane.cm.t
@@ -473,14 +476,7 @@ method _load {jump} {
        }
        if {$commit eq {}} {
                if {$do_textconv ne 0} {
-                       # Run textconv with sh -c "..." to allow it to
-                       # contain command + arguments. On windows, just
-                       # call the filter command.
-                       if {![file executable [shellpath]]} {
-                               set fd [open |[linsert $textconv end $path] r]
-                       } else {
-                               set fd [open |[list [shellpath] -c "$textconv \"\$0\"" $path] r]
-                       }
+                       set fd [open_cmd_pipe $textconv $path]
                } else {
                        set fd [open $path r]
                }
@@ -572,7 +568,11 @@ method _read_file {fd jump} {
        foreach i $w_columns {$i conf -state disabled}
 
        if {[eof $fd]} {
-               close $fd
+               fconfigure $fd -blocking 1; # enable error reporting on close
+               if {[catch {close $fd} err]} {
+                       tk_messageBox -icon error -title [mc Error] \
+                               -message $err
+               }
 
                # If we don't force Tk to update the widgets *right now*
                # none of our jump commands will cause a change in the UI.
@@ -1062,7 +1062,7 @@ method _gitkcommit {} {
                set radius [get_config gui.blamehistoryctx]
                set cmdline [list --select-commit=$cmit]
 
-                if {$radius > 0} {
+               if {$radius > 0} {
                        set author_time {}
                        set committer_time {}
 
@@ -1170,7 +1170,7 @@ method _read_diff_load_commit {fd cparent new_path tline} {
        }
 
        if {[eof $fd]} {
-               close $fd;
+               close $fd
                set current_fd {}
 
                _load_new_commit $this  \
@@ -1201,6 +1201,7 @@ method _open_tooltip {cur_w} {
                _hide_tooltip $this
 
                set tooltip_wm [toplevel $cur_w.tooltip -borderwidth 1]
+               catch {wm attributes $tooltip_wm -type tooltip}
                wm overrideredirect $tooltip_wm 1
                wm transient $tooltip_wm [winfo toplevel $cur_w]
                set tooltip_t $tooltip_wm.label
index a8c622351167be937b9f02df149a1a6ec2a16e98..0328338fda22c90c674630e0351982ff1bb2fbc4 100644 (file)
@@ -26,8 +26,14 @@ constructor new {commit {path {}}} {
        wm withdraw $top
        wm title $top [append "[appname] ([reponame]): " [mc "File Browser"]]
 
+       if {$path ne {}} {
+               if {[string index $path end] ne {/}} {
+                       append path /
+               }
+       }
+
        set browser_commit $commit
-       set browser_path $browser_commit:$path
+       set browser_path "$browser_commit:[escape_path $path]"
 
        ${NS}::label $w.path \
                -textvariable @browser_path \
index 54c7957a66aa5784d47708edc05a1c95178ed7a2..6dae7937d589c174132e9f8b9bd77133e189590f 100644 (file)
@@ -497,6 +497,7 @@ method _open_tooltip {} {
 
        if {$tooltip_wm eq {}} {
                set tooltip_wm [toplevel $w_list.tooltip -borderwidth 1]
+               catch {wm attributes $tooltip_wm -type tooltip}
                wm overrideredirect $tooltip_wm 1
                wm transient $tooltip_wm [winfo toplevel $w_list]
                set tooltip_t $tooltip_wm.label
index c27b71476ac35dbad02b727f28aba5d7c0777e75..f08506f3834a1ec821390190b920146d83078997 100644 (file)
@@ -138,6 +138,7 @@ proc make_dialog {t w args} {
        upvar $t top $w pfx this this
        global use_ttk
        uplevel [linsert $args 0 make_toplevel $t $w]
+       catch {wm attributes $top -type dialog}
        pave_toplevel $pfx
 }
 
index 372bed9948390483d66036231fce2fe8964d7bb6..0d81432af53ba873d89421fa4767243818ab1f0a 100644 (file)
@@ -263,7 +263,9 @@ proc commit_commitmsg {curHEAD msg_p} {
        global is_detached repo_config
        global pch_error
 
-       if {$is_detached && $repo_config(gui.warndetachedcommit)} {
+       if {$is_detached
+           && ![file exists [gitdir rebase-merge head-name]]
+           &&  [is_config_true gui.warndetachedcommit]} {
                set msg [mc "You are about to commit on a detached head.\
 This is a potentially dangerous thing to do because if you switch\
 to another branch you will loose your changes and it can be difficult\
index cf8a95ec346a9a8afa60a3812d59a8caa4bb4c3f..ec4405567a9b86a9bfd8bf50815b3c9c14e2df40 100644 (file)
@@ -309,6 +309,7 @@ proc start_show_diff {cont_info {add_opts {}}} {
 
        lappend cmd -p
        lappend cmd --color
+       set cmd [concat $cmd $repo_config(gui.diffopts)]
        if {$repo_config(gui.diffcontext) >= 1} {
                lappend cmd "-U$repo_config(gui.diffcontext)"
        }
@@ -502,9 +503,9 @@ proc read_diff {fd conflict_size cont_info} {
 
                foreach {posbegin colbegin posend colend} $markup {
                        set prefix clr
-                       foreach style [split $colbegin ";"] {
+                       foreach style [lsort -integer [split $colbegin ";"]] {
                                if {$style eq "7"} {append prefix i; continue}
-                               if {$style < 30 || $style > 47} {continue}
+                               if {$style != 4 && ($style < 30 || $style > 47)} {continue}
                                set a "$mark linestart + $posbegin chars"
                                set b "$mark linestart + $posend chars"
                                catch {$ui_diff tag add $prefix$style $a $b}
index e38b647b71ea335d6771121cfd079de919ced55b..8efbbdde21123dd65412cd5fe6dbb506966b0af9 100644 (file)
@@ -356,21 +356,33 @@ proc do_add_all {} {
        global file_states
 
        set paths [list]
-       set unknown_paths [list]
+       set untracked_paths [list]
        foreach path [array names file_states] {
                switch -glob -- [lindex $file_states($path) 0] {
                U? {continue}
                ?M -
                ?T -
                ?D {lappend paths $path}
-               ?O {lappend unknown_paths $path}
+               ?O {lappend untracked_paths $path}
                }
        }
-       if {[llength $unknown_paths]} {
-               set reply [ask_popup [mc "There are unknown files do you also want
-to stage those?"]]
+       if {[llength $untracked_paths]} {
+               set reply 0
+               switch -- [get_config gui.stageuntracked] {
+               no {
+                       set reply 0
+               }
+               yes {
+                       set reply 1
+               }
+               ask -
+               default {
+                       set reply [ask_popup [mc "Stage %d untracked files?" \
+                                                                         [llength $untracked_paths]]]
+               }
+               }
                if {$reply} {
-                       set paths [concat $paths $unknown_paths]
+                       set paths [concat $paths $untracked_paths]
                }
        }
        add_helper {Adding all changed files} $paths
index c160012de6e4d19c02810d57f0b3268c1037523c..a026de954c3d9cbfd03d4dec9a73a74647bf74ba 100644 (file)
@@ -15,7 +15,7 @@ constructor new {i_w i_text args} {
 
        ${NS}::frame  $w
        ${NS}::label  $w.l       -text [mc "Goto Line:"]
-       entry  $w.ent \
+       tentry  $w.ent \
                -textvariable ${__this}::linenum \
                -background lightgreen \
                -validate key \
index 3807c8d28324a277204db9191e99ddb856041c22..0cf1da1d7ebe75fe91322d9b404dbf4bd8a3b00f 100644 (file)
@@ -153,9 +153,12 @@ proc do_options {} {
                {i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}}
                {i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}}
                {i-1..99 gui.diffcontext {mc "Number of Diff Context Lines"}}
+               {t gui.diffopts {mc "Additional Diff Parameters"}}
                {i-0..99 gui.commitmsgwidth {mc "Commit Message Text Width"}}
                {t gui.newbranchtemplate {mc "New Branch Name Template"}}
                {c gui.encoding {mc "Default File Contents Encoding"}}
+               {b gui.warndetachedcommit {mc "Warn before committing to a detached head"}}
+               {s gui.stageuntracked {mc "Staging of untracked files"} {list "yes" "no" "ask"}}
                } {
                set type [lindex $option 0]
                set name [lindex $option 1]
@@ -208,6 +211,23 @@ proc do_options {} {
                                }
                                pack $w.$f.$optid -side top -anchor w -fill x
                        }
+                       s {
+                               set opts [eval [lindex $option 3]]
+                               ${NS}::frame $w.$f.$optid
+                               ${NS}::label $w.$f.$optid.l -text "$text:"
+                               if {$use_ttk} {
+                                       ttk::combobox $w.$f.$optid.v \
+                                               -textvariable ${f}_config_new($name) \
+                                               -values $opts -state readonly
+                               } else {
+                                       eval tk_optionMenu $w.$f.$optid.v \
+                                               ${f}_config_new($name) \
+                                               $opts
+                               }
+                               pack $w.$f.$optid.l -side left -anchor w -fill x
+                               pack $w.$f.$optid.v -side right -anchor e -padx 5
+                               pack $w.$f.$optid -side top -anchor w -fill x
+                       }
                        }
                }
        }
index ef3486f083c74f7f5e5fe9af861818ed0d64e89c..ef1e55521d7cea10e280f720ad700a4cd4b71d65 100644 (file)
@@ -7,9 +7,16 @@ field w
 field ctext
 
 field searchstring   {}
-field casesensitive  1
+field regexpsearch
+field default_regexpsearch
+field casesensitive
+field default_casesensitive
+field smartcase
 field searchdirn     -forwards
 
+field history
+field history_index
+
 field smarktop
 field smarkbot
 
@@ -18,15 +25,37 @@ constructor new {i_w i_text args} {
        set w      $i_w
        set ctext  $i_text
 
+       set default_regexpsearch [is_config_true gui.search.regexp]
+       switch -- [get_config gui.search.case] {
+       no {
+               set default_casesensitive 0
+               set smartcase 0
+       }
+       smart {
+               set default_casesensitive 0
+               set smartcase 1
+       }
+       yes -
+       default {
+               set default_casesensitive 1
+               set smartcase 0
+       }
+       }
+
+       set history [list]
+
        ${NS}::frame  $w
        ${NS}::label  $w.l       -text [mc Find:]
-       entry  $w.ent -textvariable ${__this}::searchstring -background lightgreen
+       tentry  $w.ent -textvariable ${__this}::searchstring -background lightgreen
        ${NS}::button $w.bn      -text [mc Next] -command [cb find_next]
        ${NS}::button $w.bp      -text [mc Prev] -command [cb find_prev]
-       ${NS}::checkbutton $w.cs -text [mc Case-Sensitive] \
+       ${NS}::checkbutton $w.re -text [mc RegExp] \
+               -variable ${__this}::regexpsearch -command [cb _incrsearch]
+       ${NS}::checkbutton $w.cs -text [mc Case] \
                -variable ${__this}::casesensitive -command [cb _incrsearch]
        pack   $w.l   -side left
        pack   $w.cs  -side right
+       pack   $w.re  -side right
        pack   $w.bp  -side right
        pack   $w.bn  -side right
        pack   $w.ent -side left -expand 1 -fill x
@@ -37,6 +66,8 @@ constructor new {i_w i_text args} {
        trace add variable searchstring write [cb _incrsearch_cb]
        bind $w.ent <Return> [cb find_next]
        bind $w.ent <Shift-Return> [cb find_prev]
+       bind $w.ent <Key-Up>   [cb _prev_search]
+       bind $w.ent <Key-Down> [cb _next_search]
        
        bind $w <Destroy> [list delete_this $this]
        return $this
@@ -45,6 +76,10 @@ constructor new {i_w i_text args} {
 method show {} {
        if {![visible $this]} {
                grid $w
+               $w.ent delete 0 end
+               set regexpsearch  $default_regexpsearch
+               set casesensitive $default_casesensitive
+               set history_index [llength $history]
        }
        focus -force $w.ent
 }
@@ -53,6 +88,7 @@ method hide {} {
        if {[visible $this]} {
                focus $ctext
                grid remove $w
+               _save_search $this
        }
 }
 
@@ -98,6 +134,9 @@ method _do_search {start {mlenvar {}} {dir {}} {endbound {}}} {
                upvar $mlenvar mlen
                lappend cmd -count mlen
        }
+       if {$regexpsearch} {
+               lappend cmd -regexp
+       }
        if {!$casesensitive} {
                lappend cmd -nocase
        }
@@ -105,14 +144,16 @@ method _do_search {start {mlenvar {}} {dir {}} {endbound {}}} {
                set dir $searchdirn
        }
        lappend cmd $dir -- $searchstring
-       if {$endbound ne {}} {
-               set here [eval $cmd [list $start] [list $endbound]]
-       } else {
-               set here [eval $cmd [list $start]]
-               if {$here eq {}} {
-                       set here [eval $cmd [_get_wrap_anchor $this $dir]]
+       if {[catch {
+               if {$endbound ne {}} {
+                       set here [eval $cmd [list $start] [list $endbound]]
+               } else {
+                       set here [eval $cmd [list $start]]
+                       if {$here eq {}} {
+                               set here [eval $cmd [_get_wrap_anchor $this $dir]]
+                       }
                }
-       }
+       } err]} { set here {} }
        return $here
 }
 
@@ -126,17 +167,74 @@ method _incrsearch {} {
                $ctext mark set anchor [_get_new_anchor $this]
        }
        if {$searchstring ne {}} {
+               if {$smartcase && [regexp {[[:upper:]]} $searchstring]} {
+                       set casesensitive 1
+               }
                set here [_do_search $this anchor mlen]
                if {$here ne {}} {
                        $ctext see $here
                        $ctext tag remove sel 1.0 end
                        $ctext tag add sel $here "$here + $mlen c"
-                       $w.ent configure -background lightgreen
+                       #$w.ent configure -background lightgreen
+                       $w.ent state !pressed
                        _set_marks $this 1
                } else {
-                       $w.ent configure -background lightpink
+                       #$w.ent configure -background lightpink
+                       $w.ent state pressed
                }
+       } elseif {$smartcase} {
+               # clearing the field resets the smart case detection
+               set casesensitive 0
+       }
+}
+
+method _save_search {} {
+       if {$searchstring eq {}} {
+               return
+       }
+       if {[llength $history] > 0} {
+               foreach {s_regexp s_case s_expr} [lindex $history end] break
+       } else {
+               set s_regexp $regexpsearch
+               set s_case   $casesensitive
+               set s_expr   ""
+       }
+       if {$searchstring eq $s_expr} {
+               # update modes
+               set history [lreplace $history end end \
+                               [list $regexpsearch $casesensitive $searchstring]]
+       } else {
+               lappend history [list $regexpsearch $casesensitive $searchstring]
+       }
+       set history_index [llength $history]
+}
+
+method _prev_search {} {
+       if {$history_index > 0} {
+               incr history_index -1
+               foreach {s_regexp s_case s_expr} [lindex $history $history_index] break
+               $w.ent delete 0 end
+               $w.ent insert 0 $s_expr
+               set regexpsearch $s_regexp
+               set casesensitive $s_case
+       }
+}
+
+method _next_search {} {
+       if {$history_index < [llength $history]} {
+               incr history_index
+       }
+       if {$history_index < [llength $history]} {
+               foreach {s_regexp s_case s_expr} [lindex $history $history_index] break
+       } else {
+               set s_regexp $default_regexpsearch
+               set s_case   $default_casesensitive
+               set s_expr   ""
        }
+       $w.ent delete 0 end
+       $w.ent insert 0 $s_expr
+       set regexpsearch $s_regexp
+       set casesensitive $s_case
 }
 
 method find_prev {} {
@@ -149,6 +247,7 @@ method find_next {{dir -forwards}} {
        set searchdirn $dir
        $ctext mark unset anchor
        if {$searchstring ne {}} {
+               _save_search $this
                set start [_get_new_anchor $this]
                if {$dir eq "-forwards"} {
                        set start "$start + 1c"
index 5f75bc96b3504bdd67ae3500cfdd30f7d4fcb32a..aa6457bbb5f1b0d64d6e04f27394912483250117 100644 (file)
@@ -117,7 +117,7 @@ proc read_sshkey_output {fd w} {
        } else {
                set finfo [find_ssh_key]
                if {$finfo eq {}} {
-                       set sshkey_title [mc "Generation succeded, but no keys found."]
+                       set sshkey_title [mc "Generation succeeded, but no keys found."]
                        $w.contents insert end $sshkey_output
                } else {
                        set sshkey_title [mc "Your key is in: %s" [lindex $finfo 0]]
index 1da458673b557fe84ac8e430e97b7ad53c69a07a..8b88d3678b7ddd802260f47bcfeff8a895d93f3f 100644 (file)
@@ -23,10 +23,59 @@ proc InitTheme {} {
        ttk::style configure Gold.TFrame -background gold -relief flat
        # listboxes should have a theme border so embed in ttk::frame
        ttk::style layout SListbox.TFrame {
-        SListbox.Frame.Entry.field -sticky news -border true -children {
-            SListbox.Frame.padding -sticky news
-        }
-    }
+               SListbox.Frame.Entry.field -sticky news -border true -children {
+                       SListbox.Frame.padding -sticky news
+               }
+       }
+
+       # Handle either current Tk or older versions of 8.5
+       if {[catch {set theme [ttk::style theme use]}]} {
+               set theme  $::ttk::currentTheme
+       }
+
+       if {[lsearch -exact {default alt classic clam} $theme] != -1} {
+               # Simple override of standard ttk::entry to change the field
+               # packground according to a state flag. We should use 'user1'
+               # but not all versions of 8.5 support that so make use of 'pressed'
+               # which is not normally in use for entry widgets.
+               ttk::style layout Edged.Entry [ttk::style layout TEntry]
+               ttk::style map Edged.Entry {*}[ttk::style map TEntry]
+               ttk::style configure Edged.Entry {*}[ttk::style configure TEntry] \
+                       -fieldbackground lightgreen
+               ttk::style map Edged.Entry -fieldbackground {
+                       {pressed !disabled} lightpink
+               }
+       } else {
+               # For fancier themes, in particular the Windows ones, the field
+               # element may not support changing the background color. So instead
+               # override the fill using the default fill element. If we overrode
+               # the vista theme field element we would loose the themed border
+               # of the widget.
+               catch {
+                       ttk::style element create color.fill from default
+               }
+
+               ttk::style layout Edged.Entry {
+                       Edged.Entry.field -sticky nswe -border 0 -children {
+                               Edged.Entry.border -sticky nswe -border 1 -children {
+                                       Edged.Entry.padding -sticky nswe -children {
+                                               Edged.Entry.color.fill -sticky nswe -children {
+                                                       Edged.Entry.textarea -sticky nswe
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               ttk::style configure Edged.Entry {*}[ttk::style configure TEntry] \
+                       -background lightgreen -padding 0 -borderwidth 0
+               ttk::style map Edged.Entry {*}[ttk::style map TEntry] \
+                       -background {{pressed !disabled} lightpink}
+       }
+
+       if {[lsearch [bind . <<ThemeChanged>>] InitTheme] == -1} {
+               bind . <<ThemeChanged>> +[namespace code [list InitTheme]]
+       }
 }
 
 proc gold_frame {w args} {
@@ -74,6 +123,7 @@ proc paddedlabel {w args} {
 # place a themed frame over the surface.
 proc Dialog {w args} {
        eval [linsert $args 0 toplevel $w -class Dialog]
+       catch {wm attributes $w -type dialog}   
        pave_toplevel $w
        return $w
 }
@@ -143,6 +193,47 @@ proc tspinbox {w args} {
        }
 }
 
+proc tentry {w args} {
+       global use_ttk
+       if {$use_ttk} {
+               InitTheme
+               ttk::entry $w -style Edged.Entry
+       } else {
+               entry $w
+       }
+
+       rename $w _$w
+       interp alias {} $w {} tentry_widgetproc $w
+       eval [linsert $args 0 tentry_widgetproc $w configure]
+       return $w
+}
+proc tentry_widgetproc {w cmd args} {
+       global use_ttk
+       switch -- $cmd {
+               state {
+                       if {$use_ttk} {
+                               return [uplevel 1 [list _$w $cmd] $args]
+                       } else {
+                               if {[lsearch -exact $args pressed] != -1} {
+                                       _$w configure -background lightpink
+                               } else {
+                                       _$w configure -background lightgreen
+                               }
+                       }
+               }
+               configure {
+                       if {$use_ttk} {
+                               if {[set n [lsearch -exact $args -background]] != -1} {
+                                       set args [lreplace $args $n [incr n]]
+                                       if {[llength $args] == 0} {return}
+                               }
+                       }
+                       return [uplevel 1 [list _$w $cmd] $args]
+               }
+               default { return [uplevel 1 [list _$w $cmd] $args] }
+       }
+}
+
 # Tk 8.6 provides a standard font selection dialog. This uses the native
 # dialogs on Windows and MacOSX or a standard Tk dialog on X11.
 proc tchoosefont {w title familyvar sizevar} {
index 95e6e5553ea86482d0fe9be77b07e805e01e3393..6ec94113db7d8c75d78e5300469bb8154e37c940 100644 (file)
@@ -87,8 +87,14 @@ proc tools_exec {fullname} {
                        return
                }
        } elseif {[is_config_true "guitool.$fullname.confirm"]} {
-               if {[ask_popup [mc "Are you sure you want to run %s?" $fullname]] ne {yes}} {
-                       return
+               if {[is_config_true "guitool.$fullname.needsfile"]} {
+                       if {[ask_popup [mc "Are you sure you want to run %1\$s on file \"%2\$s\"?" $fullname $current_diff_path]] ne {yes}} {
+                               return
+                       }
+               } else {
+                       if {[ask_popup [mc "Are you sure you want to run %s?" $fullname]] ne {yes}} {
+                               return
+                       }
                }
        }
 
index 7fad9b7d91a6d6a0671ca7bac714dcc4fd022403..e5d211edea05cbfb856e81a9d683a5fb0f4f4b20 100644 (file)
@@ -124,6 +124,7 @@ proc do_push_anywhere {} {
 
        set w .push_setup
        toplevel $w
+       catch {wm attributes $w -type dialog}
        wm withdraw $w
        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
        pave_toplevel $w
index 804001bb4e29873ef08d950a40cf1f1745e2aa27..5812222eb9afa2b2903040d7cf32ab0fb33c3508 100644 (file)
@@ -143,6 +143,21 @@ die_with_patch () {
        die "$2"
 }
 
+exit_with_patch () {
+       echo "$1" > "$state_dir"/stopped-sha
+       make_patch $1
+       git rev-parse --verify HEAD > "$amend"
+       warn "You can amend the commit now, with"
+       warn
+       warn "  git commit --amend"
+       warn
+       warn "Once you are satisfied with your changes, run"
+       warn
+       warn "  git rebase --continue"
+       warn
+       exit $2
+}
+
 die_abort () {
        rm -rf "$state_dir"
        die "$1"
@@ -408,7 +423,13 @@ do_next () {
                mark_action_done
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
-               git commit --amend --no-post-rewrite
+               git commit --amend --no-post-rewrite || {
+                       warn "Could not amend commit after successfully picking $sha1... $rest"
+                       warn "This is most likely due to an empty commit message, or the pre-commit hook"
+                       warn "failed. If the pre-commit hook failed, you may need to resolve the issue before"
+                       warn "you are able to reword the commit."
+                       exit_with_patch $sha1 1
+               }
                record_in_rewritten $sha1
                ;;
        edit|e)
@@ -417,19 +438,8 @@ do_next () {
                mark_action_done
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
-               echo "$sha1" > "$state_dir"/stopped-sha
-               make_patch $sha1
-               git rev-parse --verify HEAD > "$amend"
                warn "Stopped at $sha1... $rest"
-               warn "You can amend the commit now, with"
-               warn
-               warn "  git commit --amend"
-               warn
-               warn "Once you are satisfied with your changes, run"
-               warn
-               warn "  git rebase --continue"
-               warn
-               exit 0
+               exit_with_patch $sha1 0
                ;;
        squash|s|fixup|f)
                case "$command" in
index c6a5b7a6b38adaafdebb4b0991cc5ee8c14c3796..d7ba1178ae75019aba44f78d6cabd775a84ad526 100755 (executable)
@@ -57,12 +57,38 @@ headrev=$(git rev-parse --verify "$head"^0) || exit
 merge_base=$(git merge-base $baserev $headrev) ||
 die "fatal: No commits in common between $base and $head"
 
-find_matching_branch="/^$headrev       "'refs\/heads\//{
-       s/^.*   refs\/heads\///
-       p
-       q
-}'
-branch=$(git ls-remote "$url" | sed -n -e "$find_matching_branch")
+# $head is the token given from the command line. If a ref with that
+# name exists at the remote and their values match, we should use it.
+# Otherwise find a ref that matches $headrev.
+find_matching_ref='
+       sub abbr {
+               my $ref = shift;
+               if ($ref =~ s|refs/heads/|| || $ref =~ s|refs/tags/||) {
+                       return $ref;
+               } else {
+                       return $ref;
+               }
+       }
+
+       my ($exact, $found);
+       while (<STDIN>) {
+               my ($sha1, $ref, $deref) = /^(\S+)\s+(\S+?)(\^\{\})?$/;
+               next unless ($sha1 eq $ARGV[1]);
+               $found = abbr($ref);
+               if ($ref =~ m|/\Q$ARGV[0]\E$|) {
+                       $exact = $found;
+                       last;
+               }
+       }
+       if ($exact) {
+               print "$exact\n";
+       } elsif ($found) {
+               print "$found\n";
+       }
+'
+
+ref=$(git ls-remote "$url" | perl -e "$find_matching_ref" "$head" "$headrev")
+
 url=$(git ls-remote --get-url "$url")
 
 git show -s --format='The following changes since commit %H:
@@ -71,7 +97,7 @@ git show -s --format='The following changes since commit %H:
 
 are available in the git repository at:
 ' $baserev &&
-echo "  $url${branch+ $branch}" &&
+echo "  $url${ref+ $ref}" &&
 git show -s --format='
 for you to fetch changes up to %H:
 
@@ -81,7 +107,7 @@ for you to fetch changes up to %H:
 
 if test -n "$branch_name"
 then
-       echo "(from the branch description for $branch local branch)"
+       echo "(from the branch description for $branch_name local branch)"
        echo
        git config "branch.$branch_name.description"
 fi &&
@@ -101,7 +127,7 @@ fi &&
 git shortlog ^$baserev $headrev &&
 git diff -M --stat --summary $patch $merge_base..$headrev || status=1
 
-if test -z "$branch"
+if test -z "$ref"
 then
        echo "warn: No branch of $url is at:" >&2
        git show -s --format='warn:   %h: %s' $headrev >&2
index e672366f0c3db3b547233af146cd1bba275c0042..b4575fb3a109a2973d519c9f03187ca8fe516679 100644 (file)
@@ -2,47 +2,91 @@
 #
 # Copyright (c) 2010 Ævar Arnfjörð Bjarmason
 #
-# This is a skeleton no-op implementation of gettext for Git. It'll be
-# replaced by something that uses gettext.sh in a future patch series.
+# This is Git's interface to gettext.sh. See po/README for usage
+# instructions.
+
+# Export the TEXTDOMAIN* data that we need for Git
+TEXTDOMAIN=git
+export TEXTDOMAIN
+if test -z "$GIT_TEXTDOMAINDIR"
+then
+       TEXTDOMAINDIR="@@LOCALEDIR@@"
+else
+       TEXTDOMAINDIR="$GIT_TEXTDOMAINDIR"
+fi
+export TEXTDOMAINDIR
 
 if test -z "$GIT_GETTEXT_POISON"
 then
-       gettext () {
-               printf "%s" "$1"
-       }
+       if test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && type gettext.sh >/dev/null 2>&1
+       then
+               # This is GNU libintl's gettext.sh, we don't need to do anything
+               # else than setting up the environment and loading gettext.sh
+               GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu
+               export GIT_INTERNAL_GETTEXT_SH_SCHEME
 
-       gettextln() {
-               printf "%s\n" "$1"
-       }
+               # Try to use libintl's gettext.sh, or fall back to English if we
+               # can't.
+               . gettext.sh
 
-       eval_gettext () {
-               printf "%s" "$1" | (
-                       export PATH $(git sh-i18n--envsubst --variables "$1");
-                       git sh-i18n--envsubst "$1"
-               )
-       }
+       elif test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && test "$(gettext -h 2>&1)" = "-h"
+       then
+               # We don't have gettext.sh, but there's a gettext binary in our
+               # path. This is probably Solaris or something like it which has a
+               # gettext implementation that isn't GNU libintl.
+               GIT_INTERNAL_GETTEXT_SH_SCHEME=solaris
+               export GIT_INTERNAL_GETTEXT_SH_SCHEME
 
-       eval_gettextln () {
-               printf "%s\n" "$1" | (
-                       export PATH $(git sh-i18n--envsubst --variables "$1");
-                       git sh-i18n--envsubst "$1"
-               )
-       }
+               # Solaris has a gettext(1) but no eval_gettext(1)
+               eval_gettext () {
+                       gettext "$1" | (
+                               export PATH $(git sh-i18n--envsubst --variables "$1");
+                               git sh-i18n--envsubst "$1"
+                       )
+               }
+
+       else
+               # Since gettext.sh isn't available we'll have to define our own
+               # dummy pass-through functions.
+
+               # Tell our tests that we don't have the real gettext.sh
+               GIT_INTERNAL_GETTEXT_SH_SCHEME=fallthrough
+               export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
+               gettext () {
+                       printf "%s" "$1"
+               }
+
+               eval_gettext () {
+                       printf "%s" "$1" | (
+                               export PATH $(git sh-i18n--envsubst --variables "$1");
+                               git sh-i18n--envsubst "$1"
+                       )
+               }
+       fi
 else
+       # Emit garbage under GETTEXT_POISON=YesPlease. Unlike the C tests
+       # this relies on an environment variable
+
+       GIT_INTERNAL_GETTEXT_SH_SCHEME=poison
+       export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
        gettext () {
                printf "%s" "# GETTEXT POISON #"
        }
 
-       gettextln () {
-               printf "%s\n" "# GETTEXT POISON #"
-       }
-
        eval_gettext () {
                printf "%s" "# GETTEXT POISON #"
        }
-
-       eval_gettextln () {
-               printf "%s\n" "# GETTEXT POISON #"
-       }
 fi
 
+# Git-specific wrapper functions
+gettextln () {
+       gettext "$1"
+       echo
+}
+
+eval_gettextln () {
+       eval_gettext "$1"
+       echo
+}
diff --git a/git.c b/git.c
index 8e34903a65c8775b19b993d3e9ddf47c23d5254e..fb9029cbf17bdad1d52ea6eae0c8f4ddf6a13979 100644 (file)
--- a/git.c
+++ b/git.c
@@ -434,6 +434,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "update-ref", cmd_update_ref, RUN_SETUP },
                { "update-server-info", cmd_update_server_info, RUN_SETUP },
                { "upload-archive", cmd_upload_archive },
+               { "upload-archive--writer", cmd_upload_archive_writer },
                { "var", cmd_var, RUN_SETUP_GENTLY },
                { "verify-pack", cmd_verify_pack },
                { "verify-tag", cmd_verify_tag, RUN_SETUP },
@@ -537,6 +538,8 @@ int main(int argc, const char **argv)
        if (!cmd)
                cmd = "git-help";
 
+       git_setup_gettext();
+
        /*
         * "git-xxxx" is the same as "git xxxx", but we obviously:
         *
index 4cde0c493b8ad425c09c63173c692a0ffa4ed632..64ef3c401367c96236d81de3d88957a6459867cd 100755 (executable)
@@ -2,20 +2,16 @@
 # Tcl ignores the next line -*- tcl -*- \
 exec wish "$0" -- "$@"
 
-# Copyright © 2005-2009 Paul Mackerras.  All rights reserved.
+# Copyright © 2005-2011 Paul Mackerras.  All rights reserved.
 # This program is free software; it may be used, copied, modified
 # and distributed under the terms of the GNU General Public Licence,
 # either version 2, or (at your option) any later version.
 
 package require Tk
 
-proc gitdir {} {
-    global env
-    if {[info exists env(GIT_DIR)]} {
-       return $env(GIT_DIR)
-    } else {
-       return [exec git rev-parse --git-dir]
-    }
+proc hasworktree {} {
+    return [expr {[exec git rev-parse --is-bare-repository] == "false" &&
+                 [exec git rev-parse --is-inside-git-dir] == "false"}]
 }
 
 # A simple scheduler for compute-intensive stuff.
@@ -468,11 +464,11 @@ proc updatecommits {} {
     global viewactive viewcomplete tclencoding
     global startmsecs showneartags showlocalchanges
     global mainheadid viewmainheadid viewmainheadid_orig pending_select
-    global isworktree
+    global hasworktree
     global varcid vposids vnegids vflags vrevs
     global show_notes
 
-    set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
+    set hasworktree [hasworktree]
     rereadrefs
     set view $curview
     if {$mainheadid ne $viewmainheadid_orig($view)} {
@@ -659,7 +655,7 @@ proc newvarc {view id} {
        if {![info exists commitinfo($id)]} {
            parsecommit $id $commitdata($id) 1
        }
-       set cdate [lindex $commitinfo($id) 4]
+       set cdate [lindex [lindex $commitinfo($id) 4] 0]
        if {![string is integer -strict $cdate]} {
            set cdate 0
        }
@@ -1621,7 +1617,7 @@ proc readcommit {id} {
 }
 
 proc parsecommit {id contents listed} {
-    global commitinfo cdate
+    global commitinfo
 
     set inhdr 1
     set comment {}
@@ -1641,10 +1637,10 @@ proc parsecommit {id contents listed} {
        set line [split $line " "]
        set tag [lindex $line 0]
        if {$tag == "author"} {
-           set audate [lindex $line end-1]
+           set audate [lrange $line end-1 end]
            set auname [join [lrange $line 1 end-2] " "]
        } elseif {$tag == "committer"} {
-           set comdate [lindex $line end-1]
+           set comdate [lrange $line end-1 end]
            set comname [join [lrange $line 1 end-2] " "]
        }
     }
@@ -1671,11 +1667,9 @@ proc parsecommit {id contents listed} {
        }
        set comment $newcomment
     }
-    if {$comdate != {}} {
-       set cdate($id) $comdate
-    }
+    set hasnote [string first "\nNotes:\n" $contents]
     set commitinfo($id) [list $headline $auname $audate \
-                            $comname $comdate $comment]
+                            $comname $comdate $comment $hasnote]
 }
 
 proc getcommit {id} {
@@ -2437,9 +2431,9 @@ proc makewindow {} {
     bindkey n "selnextline 1"
     bindkey z "goback"
     bindkey x "goforw"
-    bindkey i "selnextline -1"
-    bindkey k "selnextline 1"
-    bindkey j "goback"
+    bindkey k "selnextline -1"
+    bindkey j "selnextline 1"
+    bindkey h "goback"
     bindkey l "goforw"
     bindkey b prevfile
     bindkey d "$ctext yview scroll 18 units"
@@ -2815,7 +2809,7 @@ proc about {} {
     message $w.m -text [mc "
 Gitk - a commit viewer for git
 
-Copyright \u00a9 2005-2010 Paul Mackerras
+Copyright \u00a9 2005-2011 Paul Mackerras
 
 Use and redistribute under the terms of the GNU General Public License"] \
            -justify center -aspect 400 -border 2 -bg white -relief groove
@@ -2850,9 +2844,9 @@ proc keys {} {
 [mc "<%s-W>            Close window" $M1T]
 [mc "<Home>            Move to first commit"]
 [mc "<End>             Move to last commit"]
-[mc "<Up>, p, i        Move up one commit"]
-[mc "<Down>, n, k      Move down one commit"]
-[mc "<Left>, z, j      Go back in history list"]
+[mc "<Up>, p, k        Move up one commit"]
+[mc "<Down>, n, j      Move down one commit"]
+[mc "<Left>, z, h      Go back in history list"]
 [mc "<Right>, x, l     Go forward in history list"]
 [mc "<PageUp>  Move up one page in commit list"]
 [mc "<PageDown>        Move down one page in commit list"]
@@ -3333,8 +3327,7 @@ proc gitknewtmpdir {} {
     global diffnum gitktmpdir gitdir
 
     if {![info exists gitktmpdir]} {
-       set gitktmpdir [file join [file dirname $gitdir] \
-                           [format ".gitk-tmp.%s" [pid]]]
+       set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]]
        if {[catch {file mkdir $gitktmpdir} err]} {
            error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
            unset gitktmpdir
@@ -3366,10 +3359,10 @@ proc save_file_from_commit {filename output what} {
 
 proc external_diff_get_one_file {diffid filename diffdir} {
     global nullid nullid2 nullfile
-    global gitdir
+    global worktree
 
     if {$diffid == $nullid} {
-        set difffile [file join [file dirname $gitdir] $filename]
+        set difffile [file join $worktree $filename]
        if {[file exists $difffile]} {
            return $difffile
        }
@@ -3559,7 +3552,7 @@ proc make_relative {f} {
 }
 
 proc external_blame {parent_idx {line {}}} {
-    global flist_menu_file gitdir
+    global flist_menu_file cdup
     global nullid nullid2
     global parentlist selectedline currentid
 
@@ -3578,7 +3571,7 @@ proc external_blame {parent_idx {line {}}} {
     if {$line ne {} && $line > 1} {
        lappend cmdline "--line=$line"
     }
-    set f [file join [file dirname $gitdir] $flist_menu_file]
+    set f [file join $cdup $flist_menu_file]
     # Unfortunately it seems git gui blame doesn't like
     # being given an absolute path...
     set f [make_relative $f]
@@ -3591,7 +3584,7 @@ proc external_blame {parent_idx {line {}}} {
 proc show_line_source {} {
     global cmitmode currentid parents curview blamestuff blameinst
     global diff_menu_line diff_menu_filebase flist_menu_file
-    global nullid nullid2 gitdir
+    global nullid nullid2 gitdir cdup
 
     set from_index {}
     if {$cmitmode eq "tree"} {
@@ -3644,7 +3637,7 @@ proc show_line_source {} {
     } else {
        lappend blameargs $id
     }
-    lappend blameargs -- [file join [file dirname $gitdir] $flist_menu_file]
+    lappend blameargs -- [file join $cdup $flist_menu_file]
     if {[catch {
        set f [open $blameargs r]
     } err]} {
@@ -4529,12 +4522,22 @@ proc makepatterns {l} {
 
 proc do_file_hl {serial} {
     global highlight_files filehighlight highlight_paths gdttype fhl_list
+    global cdup findtype
 
     if {$gdttype eq [mc "touching paths:"]} {
+       # If "exact" match then convert backslashes to forward slashes.
+       # Most useful to support Windows-flavoured file paths.
+       if {$findtype eq [mc "Exact"]} {
+           set highlight_files [string map {"\\" "/"} $highlight_files]
+       }
        if {[catch {set paths [shellsplit $highlight_files]}]} return
        set highlight_paths [makepatterns $paths]
        highlight_filelist
-       set gdtargs [concat -- $paths]
+       set relative_paths {}
+       foreach path $paths {
+           lappend relative_paths [file join $cdup $path]
+       }
+       set gdtargs [concat -- $relative_paths]
     } elseif {$gdttype eq [mc "adding/removing string:"]} {
        set gdtargs [list "-S$highlight_files"]
     } else {
@@ -5031,9 +5034,9 @@ proc dohidelocalchanges {} {
 # spawn off a process to do git diff-index --cached HEAD
 proc dodiffindex {} {
     global lserial showlocalchanges vfilelimit curview
-    global isworktree
+    global hasworktree
 
-    if {!$showlocalchanges || !$isworktree} return
+    if {!$showlocalchanges || !$hasworktree} return
     incr lserial
     set cmd "|git diff-index --cached HEAD"
     if {$vfilelimit($curview) ne {}} {
@@ -5899,6 +5902,9 @@ proc drawcmittext {id row col} {
        || [info exists idotherrefs($id)]} {
        set xt [drawtags $id $x $xt $y]
     }
+    if {[lindex $commitinfo($id) 6] > 0} {
+       set xt [drawnotesign $xt $y]
+    }
     set headline [lindex $commitinfo($id) 0]
     set name [lindex $commitinfo($id) 1]
     set date [lindex $commitinfo($id) 2]
@@ -6345,6 +6351,17 @@ proc drawtags {id x xt y1} {
     return $xt
 }
 
+proc drawnotesign {xt y} {
+    global linespc canv fgcolor
+
+    set orad [expr {$linespc / 3}]
+    set t [$canv create rectangle [expr {$xt - $orad}] [expr {$y - $orad}] \
+              [expr {$xt + $orad - 1}] [expr {$y + $orad - 1}] \
+              -fill yellow -outline $fgcolor -width 1 -tags circle]
+    set xt [expr {$xt + $orad * 3}]
+    return $xt
+}
+
 proc xcoord {i level ln} {
     global canvx0 xspc1 xspc2
 
@@ -9043,6 +9060,7 @@ proc exec_citool {tool_args {baseid {}}} {
 proc cherrypick {} {
     global rowmenuid curview
     global mainhead mainheadid
+    global gitdir
 
     set oldhead [exec git rev-parse HEAD]
     set dheads [descheads $rowmenuid]
@@ -9071,7 +9089,7 @@ proc cherrypick {} {
                        conflict.\nDo you wish to run git citool to\
                        resolve it?"]]} {
                # Force citool to read MERGE_MSG
-               file delete [file join [gitdir] "GITGUI_MSG"]
+               file delete [file join $gitdir "GITGUI_MSG"]
                exec_citool {} $rowmenuid
            }
        } else {
@@ -9437,6 +9455,7 @@ proc refill_reflist {} {
 proc getallcommits {} {
     global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
     global idheads idtags idotherrefs allparents tagobjid
+    global gitdir
 
     if {![info exists allcommits]} {
        set nextarc 0
@@ -9444,7 +9463,7 @@ proc getallcommits {} {
        set seeds {}
        set allcwait 0
        set cachedarcs 0
-       set allccache [file join [gitdir] "gitk.cache"]
+       set allccache [file join $gitdir "gitk.cache"]
        if {![catch {
            set f [open $allccache r]
            set allcwait 1
@@ -11024,7 +11043,7 @@ proc prefsok {} {
 proc formatdate {d} {
     global datetimeformat
     if {$d ne {}} {
-       set d [clock format $d -format $datetimeformat]
+       set d [clock format [lindex $d 0] -format $datetimeformat]
     }
     return $d
 }
@@ -11505,14 +11524,10 @@ setui $uicolor
 setoptions
 
 # check that we can find a .git directory somewhere...
-if {[catch {set gitdir [gitdir]}]} {
+if {[catch {set gitdir [exec git rev-parse --git-dir]}]} {
     show_error {} . [mc "Cannot find a git repository here."]
     exit 1
 }
-if {![file isdirectory $gitdir]} {
-    show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
-    exit 1
-}
 
 set selecthead {}
 set selectheadid {}
@@ -11628,7 +11643,12 @@ set stopped 0
 set stuffsaved 0
 set patchnum 0
 set lserial 0
-set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
+set hasworktree [hasworktree]
+set cdup {}
+if {[expr {[exec git rev-parse --is-inside-work-tree] == "true"}]} {
+    set cdup [exec git rev-parse --show-cdup]
+}
+set worktree [exec git rev-parse --show-toplevel]
 setcoords
 makewindow
 catch {
index 4f0c3bd90c7f90dad1674f50999da534c33c0261..f80f2594cb2e498fa788d38f5b06c88e2fef95ca 100755 (executable)
@@ -759,6 +759,7 @@ our @cgi_param_mapping = (
        extra_options => "opt",
        search_use_regexp => "sr",
        ctag => "by_tag",
+       diff_style => "ds",
        # this must be last entry (for manipulation from JavaScript)
        javascript => "js"
 );
@@ -2225,93 +2226,119 @@ sub format_diff_cc_simplified {
        return $result;
 }
 
-# format patch (diff) line (not to be used for diff headers)
-sub format_diff_line {
-       my $line = shift;
-       my ($from, $to) = @_;
-       my $diff_class = "";
-
-       chomp $line;
+sub diff_line_class {
+       my ($line, $from, $to) = @_;
 
+       # ordinary diff
+       my $num_sign = 1;
+       # combined diff
        if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
-               # combined diff
-               my $prefix = substr($line, 0, scalar @{$from->{'href'}});
-               if ($line =~ m/^\@{3}/) {
-                       $diff_class = " chunk_header";
-               } elsif ($line =~ m/^\\/) {
-                       $diff_class = " incomplete";
-               } elsif ($prefix =~ tr/+/+/) {
-                       $diff_class = " add";
-               } elsif ($prefix =~ tr/-/-/) {
-                       $diff_class = " rem";
-               }
-       } else {
-               # assume ordinary diff
-               my $char = substr($line, 0, 1);
-               if ($char eq '+') {
-                       $diff_class = " add";
-               } elsif ($char eq '-') {
-                       $diff_class = " rem";
-               } elsif ($char eq '@') {
-                       $diff_class = " chunk_header";
-               } elsif ($char eq "\\") {
-                       $diff_class = " incomplete";
-               }
+               $num_sign = scalar @{$from->{'href'}};
+       }
+
+       my @diff_line_classifier = (
+               { regexp => qr/^\@\@{$num_sign} /, class => "chunk_header"},
+               { regexp => qr/^\\/,               class => "incomplete"  },
+               { regexp => qr/^ {$num_sign}/,     class => "ctx" },
+               # classifier for context must come before classifier add/rem,
+               # or we would have to use more complicated regexp, for example
+               # qr/(?= {0,$m}\+)[+ ]{$num_sign}/, where $m = $num_sign - 1;
+               { regexp => qr/^[+ ]{$num_sign}/,   class => "add" },
+               { regexp => qr/^[- ]{$num_sign}/,   class => "rem" },
+       );
+       for my $clsfy (@diff_line_classifier) {
+               return $clsfy->{'class'}
+                       if ($line =~ $clsfy->{'regexp'});
        }
-       $line = untabify($line);
-       if ($from && $to && $line =~ m/^\@{2} /) {
-               my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
-                       $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
 
-               $from_lines = 0 unless defined $from_lines;
-               $to_lines   = 0 unless defined $to_lines;
+       # fallback
+       return "";
+}
 
-               if ($from->{'href'}) {
-                       $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
-                                            -class=>"list"}, $from_text);
-               }
-               if ($to->{'href'}) {
-                       $to_text   = $cgi->a({-href=>"$to->{'href'}#l$to_start",
-                                            -class=>"list"}, $to_text);
-               }
-               $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
-                       "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
-               return "<div class=\"diff$diff_class\">$line</div>\n";
-       } elsif ($from && $to && $line =~ m/^\@{3}/) {
-               my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
-               my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
+# assumes that $from and $to are defined and correctly filled,
+# and that $line holds a line of chunk header for unified diff
+sub format_unidiff_chunk_header {
+       my ($line, $from, $to) = @_;
 
-               @from_text = split(' ', $ranges);
-               for (my $i = 0; $i < @from_text; ++$i) {
-                       ($from_start[$i], $from_nlines[$i]) =
-                               (split(',', substr($from_text[$i], 1)), 0);
-               }
+       my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
+               $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
 
-               $to_text   = pop @from_text;
-               $to_start  = pop @from_start;
-               $to_nlines = pop @from_nlines;
+       $from_lines = 0 unless defined $from_lines;
+       $to_lines   = 0 unless defined $to_lines;
 
-               $line = "<span class=\"chunk_info\">$prefix ";
-               for (my $i = 0; $i < @from_text; ++$i) {
-                       if ($from->{'href'}[$i]) {
-                               $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
-                                                 -class=>"list"}, $from_text[$i]);
-                       } else {
-                               $line .= $from_text[$i];
-                       }
-                       $line .= " ";
-               }
-               if ($to->{'href'}) {
-                       $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
-                                         -class=>"list"}, $to_text);
+       if ($from->{'href'}) {
+               $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
+                                    -class=>"list"}, $from_text);
+       }
+       if ($to->{'href'}) {
+               $to_text   = $cgi->a({-href=>"$to->{'href'}#l$to_start",
+                                    -class=>"list"}, $to_text);
+       }
+       $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
+               "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
+       return $line;
+}
+
+# assumes that $from and $to are defined and correctly filled,
+# and that $line holds a line of chunk header for combined diff
+sub format_cc_diff_chunk_header {
+       my ($line, $from, $to) = @_;
+
+       my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
+       my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
+
+       @from_text = split(' ', $ranges);
+       for (my $i = 0; $i < @from_text; ++$i) {
+               ($from_start[$i], $from_nlines[$i]) =
+                       (split(',', substr($from_text[$i], 1)), 0);
+       }
+
+       $to_text   = pop @from_text;
+       $to_start  = pop @from_start;
+       $to_nlines = pop @from_nlines;
+
+       $line = "<span class=\"chunk_info\">$prefix ";
+       for (my $i = 0; $i < @from_text; ++$i) {
+               if ($from->{'href'}[$i]) {
+                       $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
+                                         -class=>"list"}, $from_text[$i]);
                } else {
-                       $line .= $to_text;
+                       $line .= $from_text[$i];
                }
-               $line .= " $prefix</span>" .
-                        "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
-               return "<div class=\"diff$diff_class\">$line</div>\n";
+               $line .= " ";
        }
-       return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
+       if ($to->{'href'}) {
+               $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
+                                 -class=>"list"}, $to_text);
+       } else {
+               $line .= $to_text;
+       }
+       $line .= " $prefix</span>" .
+                "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
+       return $line;
+}
+
+# process patch (diff) line (not to be used for diff headers),
+# returning class and HTML-formatted (but not wrapped) line
+sub process_diff_line {
+       my $line = shift;
+       my ($from, $to) = @_;
+
+       my $diff_class = diff_line_class($line, $from, $to);
+
+       chomp $line;
+       $line = untabify($line);
+
+       if ($from && $to && $line =~ m/^\@{2} /) {
+               $line = format_unidiff_chunk_header($line, $from, $to);
+               return $diff_class, $line;
+
+       } elsif ($from && $to && $line =~ m/^\@{3}/) {
+               $line = format_cc_diff_chunk_header($line, $from, $to);
+               return $diff_class, $line;
+
+       }
+       return $diff_class, esc_html($line, -nbsp=>1);
 }
 
 # Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
@@ -4833,8 +4860,97 @@ sub git_difftree_body {
        print "</table>\n";
 }
 
+sub print_sidebyside_diff_chunk {
+       my @chunk = @_;
+       my (@ctx, @rem, @add);
+
+       return unless @chunk;
+
+       # incomplete last line might be among removed or added lines,
+       # or both, or among context lines: find which
+       for (my $i = 1; $i < @chunk; $i++) {
+               if ($chunk[$i][0] eq 'incomplete') {
+                       $chunk[$i][0] = $chunk[$i-1][0];
+               }
+       }
+
+       # guardian
+       push @chunk, ["", ""];
+
+       foreach my $line_info (@chunk) {
+               my ($class, $line) = @$line_info;
+
+               # print chunk headers
+               if ($class && $class eq 'chunk_header') {
+                       print $line;
+                       next;
+               }
+
+               ## print from accumulator when type of class of lines change
+               # empty contents block on start rem/add block, or end of chunk
+               if (@ctx && (!$class || $class eq 'rem' || $class eq 'add')) {
+                       print join '',
+                               '<div class="chunk_block ctx">',
+                                       '<div class="old">',
+                                       @ctx,
+                                       '</div>',
+                                       '<div class="new">',
+                                       @ctx,
+                                       '</div>',
+                               '</div>';
+                       @ctx = ();
+               }
+               # empty add/rem block on start context block, or end of chunk
+               if ((@rem || @add) && (!$class || $class eq 'ctx')) {
+                       if (!@add) {
+                               # pure removal
+                               print join '',
+                                       '<div class="chunk_block rem">',
+                                               '<div class="old">',
+                                               @rem,
+                                               '</div>',
+                                       '</div>';
+                       } elsif (!@rem) {
+                               # pure addition
+                               print join '',
+                                       '<div class="chunk_block add">',
+                                               '<div class="new">',
+                                               @add,
+                                               '</div>',
+                                       '</div>';
+                       } else {
+                               # assume that it is change
+                               print join '',
+                                       '<div class="chunk_block chg">',
+                                               '<div class="old">',
+                                               @rem,
+                                               '</div>',
+                                               '<div class="new">',
+                                               @add,
+                                               '</div>',
+                                       '</div>';
+                       }
+                       @rem = @add = ();
+               }
+
+               ## adding lines to accumulator
+               # guardian value
+               last unless $line;
+               # rem, add or change
+               if ($class eq 'rem') {
+                       push @rem, $line;
+               } elsif ($class eq 'add') {
+                       push @add, $line;
+               }
+               # context line
+               if ($class eq 'ctx') {
+                       push @ctx, $line;
+               }
+       }
+}
+
 sub git_patchset_body {
-       my ($fd, $difftree, $hash, @hash_parents) = @_;
+       my ($fd, $diff_style, $difftree, $hash, @hash_parents) = @_;
        my ($hash_parent) = $hash_parents[0];
 
        my $is_combined = (@hash_parents > 1);
@@ -4844,6 +4960,7 @@ sub git_patchset_body {
        my $diffinfo;
        my $to_name;
        my (%from, %to);
+       my @chunk; # for side-by-side diff
 
        print "<div class=\"patchset\">\n";
 
@@ -4950,10 +5067,29 @@ sub git_patchset_body {
 
                        next PATCH if ($patch_line =~ m/^diff /);
 
-                       print format_diff_line($patch_line, \%from, \%to);
+                       my ($class, $line) = process_diff_line($patch_line, \%from, \%to);
+                       my $diff_classes = "diff";
+                       $diff_classes .= " $class" if ($class);
+                       $line = "<div class=\"$diff_classes\">$line</div>\n";
+
+                       if ($diff_style eq 'sidebyside' && !$is_combined) {
+                               if ($class eq 'chunk_header') {
+                                       print_sidebyside_diff_chunk(@chunk);
+                                       @chunk = ( [ $class, $line ] );
+                               } else {
+                                       push @chunk, [ $class, $line ];
+                               }
+                       } else {
+                               # default 'inline' style and unknown styles
+                               print $line;
+                       }
                }
 
        } continue {
+               if (@chunk) {
+                       print_sidebyside_diff_chunk(@chunk);
+                       @chunk = ();
+               }
                print "</div>\n"; # class="patch"
        }
 
@@ -6949,6 +7085,7 @@ sub git_object {
 
 sub git_blobdiff {
        my $format = shift || 'html';
+       my $diff_style = $input_params{'diff_style'} || 'inline';
 
        my $fd;
        my @difftree;
@@ -7027,6 +7164,7 @@ sub git_blobdiff {
                my $formats_nav =
                        $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
                                "raw");
+               $formats_nav .= diff_style_nav($diff_style);
                git_header_html(undef, $expires);
                if (defined $hash_base && (my %co = parse_commit($hash_base))) {
                        git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
@@ -7058,7 +7196,8 @@ sub git_blobdiff {
        if ($format eq 'html') {
                print "<div class=\"page_body\">\n";
 
-               git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base);
+               git_patchset_body($fd, $diff_style,
+                                 [ \%diffinfo ], $hash_base, $hash_parent_base);
                close $fd;
 
                print "</div>\n"; # class="page_body"
@@ -7083,9 +7222,31 @@ sub git_blobdiff_plain {
        git_blobdiff('plain');
 }
 
+# assumes that it is added as later part of already existing navigation,
+# so it returns "| foo | bar" rather than just "foo | bar"
+sub diff_style_nav {
+       my ($diff_style, $is_combined) = @_;
+       $diff_style ||= 'inline';
+
+       return "" if ($is_combined);
+
+       my @styles = (inline => 'inline', 'sidebyside' => 'side by side');
+       my %styles = @styles;
+       @styles =
+               @styles[ map { $_ * 2 } 0..$#styles/2 ];
+
+       return join '',
+               map { " | ".$_ }
+               map {
+                       $_ eq $diff_style ? $styles{$_} :
+                       $cgi->a({-href => href(-replay=>1, diff_style => $_)}, $styles{$_})
+               } @styles;
+}
+
 sub git_commitdiff {
        my %params = @_;
        my $format = $params{-format} || 'html';
+       my $diff_style = $input_params{'diff_style'} || 'inline';
 
        my ($patch_max) = gitweb_get_feature('patches');
        if ($format eq 'patch') {
@@ -7111,6 +7272,7 @@ sub git_commitdiff {
                                $cgi->a({-href => href(action=>"patch", -replay=>1)},
                                        "patch");
                }
+               $formats_nav .= diff_style_nav($diff_style, @{$co{'parents'}} > 1);
 
                if (defined $hash_parent &&
                    $hash_parent ne '-c' && $hash_parent ne '--cc') {
@@ -7128,8 +7290,8 @@ sub git_commitdiff {
                                }
                        }
                        $formats_nav .= ': ' .
-                               $cgi->a({-href => href(action=>"commitdiff",
-                                                      hash=>$hash_parent)},
+                               $cgi->a({-href => href(-replay=>1,
+                                                      hash=>$hash_parent, hash_base=>undef)},
                                        esc_html($hash_parent_short)) .
                                ')';
                } elsif (!$co{'parent'}) {
@@ -7139,28 +7301,28 @@ sub git_commitdiff {
                        # single parent commit
                        $formats_nav .=
                                ' (parent: ' .
-                               $cgi->a({-href => href(action=>"commitdiff",
-                                                      hash=>$co{'parent'})},
+                               $cgi->a({-href => href(-replay=>1,
+                                                      hash=>$co{'parent'}, hash_base=>undef)},
                                        esc_html(substr($co{'parent'}, 0, 7))) .
                                ')';
                } else {
                        # merge commit
                        if ($hash_parent eq '--cc') {
                                $formats_nav .= ' | ' .
-                                       $cgi->a({-href => href(action=>"commitdiff",
+                                       $cgi->a({-href => href(-replay=>1,
                                                               hash=>$hash, hash_parent=>'-c')},
                                                'combined');
                        } else { # $hash_parent eq '-c'
                                $formats_nav .= ' | ' .
-                                       $cgi->a({-href => href(action=>"commitdiff",
+                                       $cgi->a({-href => href(-replay=>1,
                                                               hash=>$hash, hash_parent=>'--cc')},
                                                'compact');
                        }
                        $formats_nav .=
                                ' (merge: ' .
                                join(' ', map {
-                                       $cgi->a({-href => href(action=>"commitdiff",
-                                                              hash=>$_)},
+                                       $cgi->a({-href => href(-replay=>1,
+                                                              hash=>$_, hash_base=>undef)},
                                                esc_html(substr($_, 0, 7)));
                                } @{$co{'parents'}} ) .
                                ')';
@@ -7289,7 +7451,8 @@ sub git_commitdiff {
                                  $use_parents ? @{$co{'parents'}} : $hash_parent);
                print "<br/>\n";
 
-               git_patchset_body($fd, \@difftree, $hash,
+               git_patchset_body($fd, $diff_style,
+                                 \@difftree, $hash,
                                  $use_parents ? @{$co{'parents'}} : $hash_parent);
                close $fd;
                print "</div>\n"; # class="page_body"
index 7d88509208417e4b1222629002ea339ecc32526e..c7827e8f1d2f9239bca42d80d2efbaa025300bbb 100644 (file)
@@ -475,6 +475,36 @@ div.diff.nodifferences {
        color: #600000;
 }
 
+/* side-by-side diff */
+div.chunk_block {
+       overflow: hidden;
+}
+
+div.chunk_block div.old {
+       float: left;
+       width: 50%;
+       overflow: hidden;
+}
+
+div.chunk_block div.new {
+       margin-left: 50%;
+       width: 50%;
+}
+
+div.chunk_block.rem div.old div.diff.rem {
+       background-color: #fff5f5;
+}
+div.chunk_block.add div.new div.diff.add {
+       background-color: #f8fff8;
+}
+div.chunk_block.chg div     div.diff {
+       background-color: #fffff0;
+}
+div.chunk_block.ctx div     div.diff.ctx {
+       color: #404040;
+}
+
+
 div.index_include {
        border: solid #d9d8d1;
        border-width: 0px 0px 1px;
index 59ad7da605f711af6970d6832db32efd62b6ea97..869d515383b5fc9c562ea7987b1cd485cce12032 100644 (file)
@@ -545,6 +545,8 @@ int main(int argc, char **argv)
        char *cmd_arg = NULL;
        int i;
 
+       git_setup_gettext();
+
        git_extract_argv0_path(argv[0]);
        set_die_routine(die_webcgi);
 
index 69299b7bd2a956266bf581df9c23589a97fca805..ba3ea106708de01fc933e6743ab109bef2697f75 100644 (file)
@@ -22,6 +22,8 @@ int main(int argc, const char **argv)
        int get_verbosely = 0;
        int get_recover = 0;
 
+       git_setup_gettext();
+
        git_extract_argv0_path(argv[0]);
 
        while (arg < argc && argv[arg][0] == '-') {
@@ -67,7 +69,7 @@ int main(int argc, const char **argv)
 
        git_config(git_default_config, NULL);
 
-       http_init(NULL, url);
+       http_init(NULL, url, 0);
        walker = get_http_walker(url);
        walker->get_tree = get_tree;
        walker->get_history = get_history;
index edd553b7f69ed92fde301966e605e7562703718a..f22f7e43caa3e804c5c8275ba0312d58611d7da3 100644 (file)
@@ -1748,6 +1748,8 @@ int main(int argc, char **argv)
        int new_refs;
        struct ref *ref, *local_refs;
 
+       git_setup_gettext();
+
        git_extract_argv0_path(argv[0]);
 
        repo = xcalloc(sizeof(*repo), 1);
@@ -1820,7 +1822,7 @@ int main(int argc, char **argv)
 
        memset(remote_dir_exists, -1, 256);
 
-       http_init(NULL, repo->url);
+       http_init(NULL, repo->url, 1);
 
 #ifdef USE_CURL_MULTI
        is_running_queue = 0;
diff --git a/http.c b/http.c
index 44fcc4d178fcedaa87f1917608dd32a65c24c98a..0ffd79cd81ba3e722dcdbf4c20469fa551ce9d80 100644 (file)
--- a/http.c
+++ b/http.c
@@ -3,6 +3,7 @@
 #include "sideband.h"
 #include "run-command.h"
 #include "url.h"
+#include "credential.h"
 
 int active_requests;
 int http_is_verbose;
@@ -41,7 +42,8 @@ static long curl_low_speed_time = -1;
 static int curl_ftp_no_epsv;
 static const char *curl_http_proxy;
 static const char *curl_cookie_file;
-static char *user_name, *user_pass, *description;
+static struct credential http_auth = CREDENTIAL_INIT;
+static int http_proactive_auth;
 static const char *user_agent;
 
 #if LIBCURL_VERSION_NUM >= 0x071700
@@ -52,7 +54,7 @@ static const char *user_agent;
 #define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD
 #endif
 
-static char *ssl_cert_password;
+static struct credential cert_auth = CREDENTIAL_INIT;
 static int ssl_cert_password_required;
 
 static struct curl_slist *pragma_header;
@@ -136,27 +138,6 @@ static void process_curl_messages(void)
 }
 #endif
 
-static char *git_getpass_with_description(const char *what, const char *desc)
-{
-       struct strbuf prompt = STRBUF_INIT;
-       char *r;
-
-       if (desc)
-               strbuf_addf(&prompt, "%s for '%s': ", what, desc);
-       else
-               strbuf_addf(&prompt, "%s: ", what);
-       /*
-        * NEEDSWORK: for usernames, we should do something less magical that
-        * actually echoes the characters. However, we need to read from
-        * /dev/tty and not stdio, which is not portable (but getpass will do
-        * it for us). http.c uses the same workaround.
-        */
-       r = git_getpass(prompt.buf);
-
-       strbuf_release(&prompt);
-       return xstrdup(r);
-}
-
 static int http_options(const char *var, const char *value, void *cb)
 {
        if (!strcmp("http.sslverify", var)) {
@@ -229,11 +210,11 @@ static int http_options(const char *var, const char *value, void *cb)
 
 static void init_curl_http_auth(CURL *result)
 {
-       if (user_name) {
+       if (http_auth.username) {
                struct strbuf up = STRBUF_INIT;
-               if (!user_pass)
-                       user_pass = xstrdup(git_getpass_with_description("Password", description));
-               strbuf_addf(&up, "%s:%s", user_name, user_pass);
+               credential_fill(&http_auth);
+               strbuf_addf(&up, "%s:%s",
+                           http_auth.username, http_auth.password);
                curl_easy_setopt(result, CURLOPT_USERPWD,
                                 strbuf_detach(&up, NULL));
        }
@@ -241,18 +222,14 @@ static void init_curl_http_auth(CURL *result)
 
 static int has_cert_password(void)
 {
-       if (ssl_cert_password != NULL)
-               return 1;
        if (ssl_cert == NULL || ssl_cert_password_required != 1)
                return 0;
-       /* Only prompt the user once. */
-       ssl_cert_password_required = -1;
-       ssl_cert_password = git_getpass_with_description("Certificate Password", description);
-       if (ssl_cert_password != NULL) {
-               ssl_cert_password = xstrdup(ssl_cert_password);
-               return 1;
-       } else
-               return 0;
+       if (!cert_auth.password) {
+               cert_auth.protocol = xstrdup("cert");
+               cert_auth.path = xstrdup(ssl_cert);
+               credential_fill(&cert_auth);
+       }
+       return 1;
 }
 
 static CURL *get_curl_handle(void)
@@ -276,10 +253,13 @@ static CURL *get_curl_handle(void)
        curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
 #endif
 
+       if (http_proactive_auth)
+               init_curl_http_auth(result);
+
        if (ssl_cert != NULL)
                curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
        if (has_cert_password())
-               curl_easy_setopt(result, CURLOPT_KEYPASSWD, ssl_cert_password);
+               curl_easy_setopt(result, CURLOPT_KEYPASSWD, cert_auth.password);
 #if LIBCURL_VERSION_NUM >= 0x070903
        if (ssl_key != NULL)
                curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
@@ -321,42 +301,6 @@ static CURL *get_curl_handle(void)
        return result;
 }
 
-static void http_auth_init(const char *url)
-{
-       const char *at, *colon, *cp, *slash, *host;
-
-       cp = strstr(url, "://");
-       if (!cp)
-               return;
-
-       /*
-        * Ok, the URL looks like "proto://something".  Which one?
-        * "proto://<user>:<pass>@<host>/...",
-        * "proto://<user>@<host>/...", or just
-        * "proto://<host>/..."?
-        */
-       cp += 3;
-       at = strchr(cp, '@');
-       colon = strchr(cp, ':');
-       slash = strchrnul(cp, '/');
-       if (!at || slash <= at) {
-               /* No credentials, but we may have to ask for some later */
-               host = cp;
-       }
-       else if (!colon || at <= colon) {
-               /* Only username */
-               user_name = url_decode_mem(cp, at - cp);
-               user_pass = NULL;
-               host = at + 1;
-       } else {
-               user_name = url_decode_mem(cp, colon - cp);
-               user_pass = url_decode_mem(colon + 1, at - (colon + 1));
-               host = at + 1;
-       }
-
-       description = url_decode_mem(host, slash - host);
-}
-
 static void set_from_env(const char **var, const char *envname)
 {
        const char *val = getenv(envname);
@@ -364,7 +308,7 @@ static void set_from_env(const char **var, const char *envname)
                *var = val;
 }
 
-void http_init(struct remote *remote, const char *url)
+void http_init(struct remote *remote, const char *url, int proactive_auth)
 {
        char *low_speed_limit;
        char *low_speed_time;
@@ -375,6 +319,8 @@ void http_init(struct remote *remote, const char *url)
 
        curl_global_init(CURL_GLOBAL_ALL);
 
+       http_proactive_auth = proactive_auth;
+
        if (remote && remote->http_proxy)
                curl_http_proxy = xstrdup(remote->http_proxy);
 
@@ -429,7 +375,7 @@ void http_init(struct remote *remote, const char *url)
                curl_ftp_no_epsv = 1;
 
        if (url) {
-               http_auth_init(url);
+               credential_from_url(&http_auth, url);
                if (!ssl_cert_password_required &&
                    getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") &&
                    !prefixcmp(url, "https://"))
@@ -478,10 +424,10 @@ void http_cleanup(void)
                curl_http_proxy = NULL;
        }
 
-       if (ssl_cert_password != NULL) {
-               memset(ssl_cert_password, 0, strlen(ssl_cert_password));
-               free(ssl_cert_password);
-               ssl_cert_password = NULL;
+       if (cert_auth.password != NULL) {
+               memset(cert_auth.password, 0, strlen(cert_auth.password));
+               free(cert_auth.password);
+               cert_auth.password = NULL;
        }
        ssl_cert_password_required = 0;
 }
@@ -837,17 +783,11 @@ static int http_request(const char *url, void *result, int target, int options)
                else if (missing_target(&results))
                        ret = HTTP_MISSING_TARGET;
                else if (results.http_code == 401) {
-                       if (user_name && user_pass) {
+                       if (http_auth.username && http_auth.password) {
+                               credential_reject(&http_auth);
                                ret = HTTP_NOAUTH;
                        } else {
-                               /*
-                                * git_getpass is needed here because its very likely stdin/stdout are
-                                * pipes to our parent process.  So we instead need to use /dev/tty,
-                                * but that is non-portable.  Using git_getpass() can at least be stubbed
-                                * on other platforms with a different implementation if/when necessary.
-                                */
-                               if (!user_name)
-                                       user_name = xstrdup(git_getpass_with_description("Username", description));
+                               credential_fill(&http_auth);
                                init_curl_http_auth(slot->curl);
                                ret = HTTP_REAUTH;
                        }
@@ -866,6 +806,9 @@ static int http_request(const char *url, void *result, int target, int options)
        curl_slist_free_all(headers);
        strbuf_release(&buf);
 
+       if (ret == HTTP_OK)
+               credential_approve(&http_auth);
+
        return ret;
 }
 
diff --git a/http.h b/http.h
index ee1606942a9716b45da937873949220ab7f8092b..0b61653894eff606980427ee26770fa088438b94 100644 (file)
--- a/http.h
+++ b/http.h
@@ -85,7 +85,8 @@ extern void add_fill_function(void *data, int (*fill)(void *));
 extern void step_active_slots(void);
 #endif
 
-extern void http_init(struct remote *remote, const char *url);
+extern void http_init(struct remote *remote, const char *url,
+                     int proactive_auth);
 extern void http_cleanup(void);
 
 extern int active_requests;
index e1ad1a48ce3b8bd8517568a67477d8d0e32dfaa8..e40125a22b72544c107e365d4f09eaa3b1ce53de 100644 (file)
@@ -25,6 +25,7 @@
 #include "cache.h"
 #include "exec_cmd.h"
 #include "run-command.h"
+#include "prompt.h"
 #ifdef NO_OPENSSL
 typedef void *SSL;
 #else
@@ -161,7 +162,6 @@ static struct imap_server_conf server = {
 struct imap_store_conf {
        struct store_conf gen;
        struct imap_server_conf *server;
-       unsigned use_namespace:1;
 };
 
 #define NIL    (void *)0x1
@@ -1209,13 +1209,10 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
                        goto bail;
                }
                if (!srvc->pass) {
-                       char prompt[80];
-                       sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host);
-                       arg = git_getpass(prompt);
-                       if (!arg) {
-                               perror("getpass");
-                               exit(1);
-                       }
+                       struct strbuf prompt = STRBUF_INIT;
+                       strbuf_addf(&prompt, "Password (%s@%s): ", srvc->user, srvc->host);
+                       arg = git_getpass(prompt.buf);
+                       strbuf_release(&prompt);
                        if (!*arg) {
                                fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host);
                                goto bail;
@@ -1539,6 +1536,8 @@ int main(int argc, char **argv)
 
        git_extract_argv0_path(argv[0]);
 
+       git_setup_gettext();
+
        if (argc != 1)
                usage(imap_send_usage);
 
index 5a2db296b83d1ab23bfce23913c036a71dc81398..d83cd6c662847fb51641d7b8bf16739e588f67a2 100644 (file)
@@ -264,7 +264,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)
 
        if (!cache_tree_fully_valid(active_cache_tree) &&
            cache_tree_update(active_cache_tree,
-                             active_cache, active_nr, 0, 0) < 0)
+                             active_cache, active_nr, 0, 0, 0) < 0)
                die("error building trees");
 
        result = lookup_tree(active_cache_tree->sha1);
index 4c8984ede1e218d3e0aaadb6d8c72bfcafddeee9..bea013eeae70f360e12af01345058b1b217848c4 100644 (file)
@@ -48,6 +48,7 @@ int notes_cache_write(struct notes_cache *c)
 {
        unsigned char tree_sha1[20];
        unsigned char commit_sha1[20];
+       struct strbuf msg = STRBUF_INIT;
 
        if (!c || !c->tree.initialized || !c->tree.ref || !*c->tree.ref)
                return -1;
@@ -56,7 +57,9 @@ int notes_cache_write(struct notes_cache *c)
 
        if (write_notes_tree(&c->tree, tree_sha1))
                return -1;
-       if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)
+       strbuf_attach(&msg, c->validity,
+                     strlen(c->validity), strlen(c->validity) + 1);
+       if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL) < 0)
                return -1;
        if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
                       0, QUIET_ON_ERR) < 0)
index 2de27af751476a62dc9b0afe48eb360f83a0702b..0d38a1b17d8399c7acf6355d598f0a50d0ec1d3b 100644 (file)
@@ -530,7 +530,7 @@ static int merge_from_diffs(struct notes_merge_options *o,
 }
 
 void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
-                        const char *msg, unsigned char *result_sha1)
+                        const struct strbuf *msg, unsigned char *result_sha1)
 {
        unsigned char tree_sha1[20];
 
@@ -668,7 +668,7 @@ int notes_merge(struct notes_merge_options *o,
                struct commit_list *parents = NULL;
                commit_list_insert(remote, &parents); /* LIFO order */
                commit_list_insert(local, &parents);
-               create_notes_commit(local_tree, parents, o->commit_msg.buf,
+               create_notes_commit(local_tree, parents, &o->commit_msg,
                                    result_sha1);
        }
 
@@ -695,7 +695,8 @@ int notes_merge_commit(struct notes_merge_options *o,
        struct dir_struct dir;
        char *path = xstrdup(git_path(NOTES_MERGE_WORKTREE "/"));
        int path_len = strlen(path), i;
-       const char *msg = strstr(partial_commit->buffer, "\n\n");
+       char *msg = strstr(partial_commit->buffer, "\n\n");
+       struct strbuf sb_msg = STRBUF_INIT;
 
        if (o->verbosity >= 3)
                printf("Committing notes in notes merge worktree at %.*s\n",
@@ -733,7 +734,8 @@ int notes_merge_commit(struct notes_merge_options *o,
                                sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
        }
 
-       create_notes_commit(partial_tree, partial_commit->parents, msg,
+       strbuf_attach(&sb_msg, msg, strlen(msg), strlen(msg) + 1);
+       create_notes_commit(partial_tree, partial_commit->parents, &sb_msg,
                            result_sha1);
        if (o->verbosity >= 4)
                printf("Finalized notes merge commit: %s\n",
index 168a6724cd873602e3da1ff3b7e2cc67a5b240ae..0c11b173a1e38ddeb702e022ebdd571ed2ad12f3 100644 (file)
@@ -37,7 +37,7 @@ void init_notes_merge_options(struct notes_merge_options *o);
  * The resulting commit SHA1 is stored in result_sha1.
  */
 void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
-                        const char *msg, unsigned char *result_sha1);
+                        const struct strbuf *msg, unsigned char *result_sha1);
 
 /*
  * Merge notes from o->remote_ref into o->local_ref
index f84adde3eb3bb6f6d3f4a871167d6a07ef73ebd8..de2bd01414f07cb2d3fb9d212895ad0f1ea1ba26 100644 (file)
@@ -182,6 +182,18 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
        return index_name;
 }
 
+off_t write_pack_header(struct sha1file *f, uint32_t nr_entries)
+{
+       struct pack_header hdr;
+
+       hdr.hdr_signature = htonl(PACK_SIGNATURE);
+       hdr.hdr_version = htonl(PACK_VERSION);
+       hdr.hdr_entries = htonl(nr_entries);
+       if (sha1write(f, &hdr, sizeof(hdr)))
+               return 0;
+       return sizeof(hdr);
+}
+
 /*
  * Update pack header with object_count and compute new SHA1 for pack data
  * associated to pack_fd, and write that SHA1 at the end.  That new SHA1
@@ -320,3 +332,44 @@ int encode_in_pack_object_header(enum object_type type, uintmax_t size, unsigned
        *hdr = c;
        return n;
 }
+
+struct sha1file *create_tmp_packfile(char **pack_tmp_name)
+{
+       char tmpname[PATH_MAX];
+       int fd;
+
+       fd = odb_mkstemp(tmpname, sizeof(tmpname), "pack/tmp_pack_XXXXXX");
+       *pack_tmp_name = xstrdup(tmpname);
+       return sha1fd(fd, *pack_tmp_name);
+}
+
+void finish_tmp_packfile(char *name_buffer,
+                        const char *pack_tmp_name,
+                        struct pack_idx_entry **written_list,
+                        uint32_t nr_written,
+                        struct pack_idx_option *pack_idx_opts,
+                        unsigned char sha1[])
+{
+       const char *idx_tmp_name;
+       char *end_of_name_prefix = strrchr(name_buffer, 0);
+
+       if (adjust_shared_perm(pack_tmp_name))
+               die_errno("unable to make temporary pack file readable");
+
+       idx_tmp_name = write_idx_file(NULL, written_list, nr_written,
+                                     pack_idx_opts, sha1);
+       if (adjust_shared_perm(idx_tmp_name))
+               die_errno("unable to make temporary index file readable");
+
+       sprintf(end_of_name_prefix, "%s.pack", sha1_to_hex(sha1));
+       free_pack_by_name(name_buffer);
+
+       if (rename(pack_tmp_name, name_buffer))
+               die_errno("unable to rename temporary pack file");
+
+       sprintf(end_of_name_prefix, "%s.idx", sha1_to_hex(sha1));
+       if (rename(idx_tmp_name, name_buffer))
+               die_errno("unable to rename temporary index file");
+
+       free((void *)idx_tmp_name);
+}
diff --git a/pack.h b/pack.h
index a8d9b9f2fcc41bf56007cd48dcfcb94a7e00d3ca..aa6ee7d606f1d5336bff9a3067b44057c362e788 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -2,6 +2,7 @@
 #define PACK_H
 
 #include "object.h"
+#include "csum-file.h"
 
 /*
  * Packed object header
@@ -79,6 +80,7 @@ extern const char *write_idx_file(const char *index_name, struct pack_idx_entry
 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_index(struct packed_git *);
 extern int verify_pack(struct packed_git *, verify_fn fn, struct progress *, uint32_t);
+extern off_t write_pack_header(struct sha1file *f, uint32_t);
 extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t);
 extern char *index_pack_lockfile(int fd);
 extern int encode_in_pack_object_header(enum object_type, uintmax_t, unsigned char *);
@@ -87,4 +89,8 @@ extern int encode_in_pack_object_header(enum object_type, uintmax_t, unsigned ch
 #define PH_ERROR_PACK_SIGNATURE        (-2)
 #define PH_ERROR_PROTOCOL      (-3)
 extern int read_pack_header(int fd, struct pack_header *);
+
+extern struct sha1file *create_tmp_packfile(char **pack_tmp_name);
+extern void finish_tmp_packfile(char *name_buffer, const char *pack_tmp_name, struct pack_idx_entry **written_list, uint32_t nr_written, struct pack_idx_option *pack_idx_opts, unsigned char sha1[]);
+
 #endif
diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm
new file mode 100644 (file)
index 0000000..07597dc
--- /dev/null
@@ -0,0 +1,89 @@
+package Git::I18N;
+use 5.008;
+use strict;
+use warnings;
+use Exporter 'import';
+
+our @EXPORT = qw(__);
+our @EXPORT_OK = @EXPORT;
+
+sub __bootstrap_locale_messages {
+       our $TEXTDOMAIN = 'git';
+       our $TEXTDOMAINDIR = $ENV{GIT_TEXTDOMAINDIR} || '++LOCALEDIR++';
+
+       require POSIX;
+       POSIX->import(qw(setlocale));
+       # Non-core prerequisite module
+       require Locale::Messages;
+       Locale::Messages->import(qw(:locale_h :libintl_h));
+
+       setlocale(LC_MESSAGES(), '');
+       setlocale(LC_CTYPE(), '');
+       textdomain($TEXTDOMAIN);
+       bindtextdomain($TEXTDOMAIN => $TEXTDOMAINDIR);
+
+       return;
+}
+
+BEGIN
+{
+       # Used by our test script to see if it should test fallbacks or
+       # not.
+       our $__HAS_LIBRARY = 1;
+
+       local $@;
+       eval {
+               __bootstrap_locale_messages();
+               *__ = \&Locale::Messages::gettext;
+               1;
+       } or do {
+               # Tell test.pl that we couldn't load the gettext library.
+               $Git::I18N::__HAS_LIBRARY = 0;
+
+               # Just a fall-through no-op
+               *__ = sub ($) { $_[0] };
+       };
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Git::I18N - Perl interface to Git's Gettext localizations
+
+=head1 SYNOPSIS
+
+       use Git::I18N;
+
+       print __("Welcome to Git!\n");
+
+       printf __("The following error occured: %s\n"), $error;
+
+=head1 DESCRIPTION
+
+Git's internal Perl interface to gettext via L<Locale::Messages>. If
+L<Locale::Messages> can't be loaded (it's not a core module) we
+provide stub passthrough fallbacks.
+
+This is a distilled interface to gettext, see C<info '(gettext)Perl'>
+for the full interface. This module implements only a small part of
+it.
+
+=head1 FUNCTIONS
+
+=head2 __($)
+
+L<Locale::Messages>'s gettext function if all goes well, otherwise our
+passthrough fallback function.
+
+=head1 AUTHOR
+
+E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=head1 COPYRIGHT
+
+Copyright 2010 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=cut
index a2ffb6402d45420dff4dcd545dfa08b57305d8cd..b2977cd0bc8f23d75a228ca13d6cb42e1c72628f 100644 (file)
@@ -5,6 +5,7 @@ makfile:=perl.mak
 
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 prefix_SQ = $(subst ','\'',$(prefix))
+localedir_SQ = $(subst ','\'',$(localedir))
 
 ifndef V
        QUIET = @
@@ -38,7 +39,7 @@ $(makfile): ../GIT-CFLAGS Makefile
        echo '  echo $(instdir_SQ)' >> $@
 else
 $(makfile): Makefile.PL ../GIT-CFLAGS
-       $(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE=''
+       $(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE='' --localedir='$(localedir_SQ)'
 endif
 
 # this is just added comfort for calling make directly in perl dir
index 0b9deca2cc6ef77897a23b2096a7acdd577c2482..456d45bf4092467e290ce478ceb8032938a01aac 100644 (file)
@@ -1,4 +1,12 @@
+use strict;
+use warnings;
 use ExtUtils::MakeMaker;
+use Getopt::Long;
+
+# Sanity: die at first unknown option
+Getopt::Long::Configure qw/ pass_through /;
+
+GetOptions("localedir=s" => \my $localedir);
 
 sub MY::postamble {
        return <<'MAKE_FRAG';
@@ -16,7 +24,10 @@ endif
 MAKE_FRAG
 }
 
-my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+my %pm = (
+       'Git.pm' => '$(INST_LIBDIR)/Git.pm',
+       'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',
+);
 
 # We come with our own bundled Error.pm. It's not in the set of default
 # Perl modules so install it if it's not available on the system yet.
@@ -33,6 +44,7 @@ WriteMakefile(
        NAME            => 'Git',
        VERSION_FROM    => 'Git.pm',
        PM              => \%pm,
+       PM_FILTER       => qq[\$(PERL) -pe "s<\\Q++LOCALEDIR++\\E><$localedir>"],
        MAKEFILE        => 'perl.mak',
        INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3'
 );
index a242a86e9376402cefa9dce7cee266a510a1ab80..4caa631ff020e0e81d0e5507eef085c7feca9912 100644 (file)
@@ -1 +1,2 @@
 /git.pot
+/build
diff --git a/po/README b/po/README
new file mode 100644 (file)
index 0000000..10b0ad2
--- /dev/null
+++ b/po/README
@@ -0,0 +1,229 @@
+Core GIT Translations
+=====================
+
+This directory holds the translations for the core of Git. This
+document describes how to add to and maintain these translations, and
+how to mark source strings for translation.
+
+
+Generating a .pot file
+----------------------
+
+The po/git.pot file contains a message catalog extracted from Git's
+sources. You need to generate it to add new translations with
+msginit(1), or update existing ones with msgmerge(1).
+
+Since the file can be automatically generated it's not checked into
+git.git. To generate it do, at the top-level:
+
+    make pot
+
+
+Initializing a .po file
+-----------------------
+
+To add a new translation first generate git.pot (see above) and then
+in the po/ directory do:
+
+    msginit --locale=XX
+
+Where XX is your locale, e.g. "is", "de" or "pt_BR".
+
+Then edit the automatically generated copyright info in your new XX.po
+to be correct, e.g. for Icelandic:
+
+    @@ -1,6 +1,6 @@
+    -# Icelandic translations for PACKAGE package.
+    -# Copyright (C) 2010 THE PACKAGE'S COPYRIGHT HOLDER
+    -# This file is distributed under the same license as the PACKAGE package.
+    +# Icelandic translations for Git.
+    +# Copyright (C) 2010 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+    +# This file is distributed under the same license as the Git package.
+     # Ævar Arnfjörð Bjarmason <avarab@gmail.com>, 2010.
+
+And change references to PACKAGE VERSION in the PO Header Entry to
+just "Git":
+
+    perl -pi -e 's/(?<="Project-Id-Version: )PACKAGE VERSION/Git/' XX.po
+
+
+Updating a .po file
+-------------------
+
+If there's an existing *.po file for your language but you need to
+update the translation you first need to generate git.pot (see above)
+and then in the po/ directory do:
+
+    msgmerge --add-location --backup=off -U XX.po git.pot
+
+Where XX.po is the file you want to update.
+
+Testing your changes
+--------------------
+
+Before you submit your changes go back to the top-level and do:
+
+    make
+
+On systems with GNU gettext (i.e. not Solaris) this will compile your
+changed PO file with `msgfmt --check`, the --check option flags many
+common errors, e.g. missing printf format strings, or translated
+messages that deviate from the originals in whether they begin/end
+with a newline or not.
+
+
+Marking strings for translation
+-------------------------------
+
+Before strings can be translated they first have to be marked for
+translation.
+
+Git uses an internationalization interface that wraps the system's
+gettext library, so most of the advice in your gettext documentation
+(on GNU systems `info gettext` in a terminal) applies.
+
+General advice:
+
+ - Don't mark everything for translation, only strings which will be
+   read by humans (the porcelain interface) should be translated.
+
+   The output from Git's plumbing utilities will primarily be read by
+   programs and would break scripts under non-C locales if it was
+   translated. Plumbing strings should not be translated, since
+   they're part of Git's API.
+
+ - Adjust the strings so that they're easy to translate. Most of the
+   advice in `info '(gettext)Preparing Strings'` applies here.
+
+ - If something is unclear or ambiguous you can use a "TRANSLATORS"
+   comment to tell the translators what to make of it. These will be
+   extracted by xgettext(1) and put in the po/*.po files, e.g. from
+   git-am.sh:
+
+       # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+       # in your translation. The program will only accept English
+       # input at this point.
+       gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+
+   Or in C, from builtin/revert.c:
+
+       /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
+       die(_("%s: Unable to write new index file"), action_name(opts));
+
+We provide wrappers for C, Shell and Perl programs. Here's how they're
+used:
+
+C:
+
+ - Include builtin.h at the top, it'll pull in in gettext.h, which
+   defines the gettext interface. Consult with the list if you need to
+   use gettext.h directly.
+
+ - The C interface is a subset of the normal GNU gettext
+   interface. We currently export these functions:
+
+   - _()
+
+    Mark and translate a string. E.g.:
+
+        printf(_("HEAD is now at %s"), hex);
+
+   - Q_()
+
+    Mark and translate a plural string. E.g.:
+
+        printf(Q_("%d commit", "%d commits", number_of_commits));
+
+    This is just a wrapper for the ngettext() function.
+
+   - N_()
+
+    A no-op pass-through macro for marking strings inside static
+    initializations, e.g.:
+
+        static const char *reset_type_names[] = {
+            N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL
+        };
+
+    And then, later:
+
+        die(_("%s reset is not allowed in a bare repository"),
+               _(reset_type_names[reset_type]));
+
+    Here _() couldn't have statically determined what the translation
+    string will be, but since it was already marked for translation
+    with N_() the look-up in the message catalog will succeed.
+
+Shell:
+
+ - The Git gettext shell interface is just a wrapper for
+   gettext.sh. Import it right after git-sh-setup like this:
+
+       . git-sh-setup
+       . git-sh-i18n
+
+   And then use the gettext or eval_gettext functions:
+
+       # For constant interface messages:
+       gettext "A message for the user"; echo
+
+       # To interpolate variables:
+       details="oh noes"
+       eval_gettext "An error occured: \$details"; echo
+
+   In addition we have wrappers for messages that end with a trailing
+   newline. I.e. you could write the above as:
+
+       # For constant interface messages:
+       gettextln "A message for the user"
+
+       # To interpolate variables:
+       details="oh noes"
+       eval_gettextln "An error occured: \$details"
+
+   More documentation about the interface is available in the GNU info
+   page: `info '(gettext)sh'`. Looking at git-am.sh (the first shell
+   command to be translated) for examples is also useful:
+
+       git log --reverse -p --grep=i18n git-am.sh
+
+Perl:
+
+ - The Git::I18N module provides a limited subset of the
+   Locale::Messages functionality, e.g.:
+
+       use Git::I18N;
+       print __("Welcome to Git!\n");
+       printf __("The following error occured: %s\n"), $error;
+
+   Run `perldoc perl/Git/I18N.pm` for more info.
+
+
+Testing marked strings
+----------------------
+
+Even if you've correctly marked porcelain strings for translation
+something in the test suite might still depend on the US English
+version of the strings, e.g. to grep some error message or other
+output.
+
+To smoke out issues like these Git can be compiled with gettext poison
+support, at the top-level:
+
+    make GETTEXT_POISON=YesPlease
+
+That'll give you a git which emits gibberish on every call to
+gettext. It's obviously not meant to be installed, but you should run
+the test suite with it:
+
+    cd t && prove -j 9 ./t[0-9]*.sh
+
+If tests break with it you should inspect them manually and see if
+what you're translating is sane, i.e. that you're not translating
+plumbing output.
+
+If not you should replace calls to grep with test_i18ngrep, or
+test_cmp calls with test_i18ncmp. If that's not enough you can skip
+the whole test by making it depend on the C_LOCALE_OUTPUT
+prerequisite. See existing test files with this prerequisite for
+examples.
diff --git a/po/is.po b/po/is.po
new file mode 100644 (file)
index 0000000..8692a8b
--- /dev/null
+++ b/po/is.po
@@ -0,0 +1,93 @@
+# Icelandic translations for Git.
+# Copyright (C) 2010 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+# This file is distributed under the same license as the Git package.
+# Ævar Arnfjörð Bjarmason <avarab@gmail.com>, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"POT-Creation-Date: 2010-09-20 14:44+0000\n"
+"PO-Revision-Date: 2010-06-05 19:06 +0000\n"
+"Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"
+"Language-Team: Git Mailing List <git@vger.kernel.org>\n"
+"Language: is\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:5
+msgid "See 'git help COMMAND' for more information on a specific command."
+msgstr "Sjá 'git help SKIPUN' til að sjá hjálp fyrir tiltekna skipun."
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:10
+msgid "TEST: A C test string"
+msgstr "TILRAUN: C tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:13
+#, c-format
+msgid "TEST: A C test string %s"
+msgstr "TILRAUN: C tilraunastrengur %s"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:16
+#, c-format
+msgid "TEST: Hello World!"
+msgstr "TILRAUN: Halló Heimur!"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:19
+#, c-format
+msgid "TEST: Old English Runes"
+msgstr "TILRAUN: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:22
+#, c-format
+msgid "TEST: ‘single’ and “double” quotes"
+msgstr "TILRAUN: ‚einfaldar‘ og „tvöfaldar“ gæsalappir"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:8
+msgid "TEST: A Shell test string"
+msgstr "TILRAUN: Skeljartilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:11
+#, sh-format
+msgid "TEST: A Shell test $variable"
+msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:8
+msgid "TEST: A Perl test string"
+msgstr "TILRAUN: Perl tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:11
+#, perl-format
+msgid "TEST: A Perl test variable %s"
+msgstr "TILRAUN: Perl tilraunastrengur með breytunni %s"
+
+#. TRANSLATORS: The first '%s' is either "Reinitialized
+#. existing" or "Initialized empty", the second " shared" or
+#. "", and the last '%s%s' is the verbatim directory name.
+#: builtin/init-db.c:355
+#, c-format
+msgid "%s%s Git repository in %s%s\n"
+msgstr "%s%s Git lind í %s%s\n"
+
+#: builtin/init-db.c:356
+msgid "Reinitialized existing"
+msgstr "Endurgerði"
+
+#: builtin/init-db.c:356
+msgid "Initialized empty"
+msgstr "Bjó til tóma"
+
+#: builtin/init-db.c:357
+msgid " shared"
+msgstr " sameiginlega"
index 230fe1cc82e3a3bf7c0fef604350868d917acef4..1580299d4037ada9e1f0e3c11c6c5578f44f52dd 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -822,6 +822,23 @@ static void rewrap_message_tail(struct strbuf *sb,
        c->indent2 = new_indent2;
 }
 
+static int format_reflog_person(struct strbuf *sb,
+                               char part,
+                               struct reflog_walk_info *log,
+                               enum date_mode dmode)
+{
+       const char *ident;
+
+       if (!log)
+               return 2;
+
+       ident = get_reflog_ident(log);
+       if (!ident)
+               return 2;
+
+       return format_person_part(sb, part, ident, strlen(ident), dmode);
+}
+
 static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
                                void *context)
 {
@@ -963,6 +980,14 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
                        if (c->pretty_ctx->reflog_info)
                                get_reflog_message(sb, c->pretty_ctx->reflog_info);
                        return 2;
+               case 'n':
+               case 'N':
+               case 'e':
+               case 'E':
+                       return format_reflog_person(sb,
+                                                   placeholder[1],
+                                                   c->pretty_ctx->reflog_info,
+                                                   c->pretty_ctx->date_mode);
                }
                return 0;       /* unknown %g placeholder */
        case 'N':
diff --git a/prompt.c b/prompt.c
new file mode 100644 (file)
index 0000000..72ab9de
--- /dev/null
+++ b/prompt.c
@@ -0,0 +1,63 @@
+#include "cache.h"
+#include "run-command.h"
+#include "strbuf.h"
+#include "prompt.h"
+#include "compat/terminal.h"
+
+static char *do_askpass(const char *cmd, const char *prompt)
+{
+       struct child_process pass;
+       const char *args[3];
+       static struct strbuf buffer = STRBUF_INIT;
+
+       args[0] = cmd;
+       args[1] = prompt;
+       args[2] = NULL;
+
+       memset(&pass, 0, sizeof(pass));
+       pass.argv = args;
+       pass.out = -1;
+
+       if (start_command(&pass))
+               exit(1);
+
+       strbuf_reset(&buffer);
+       if (strbuf_read(&buffer, pass.out, 20) < 0)
+               die("failed to get '%s' from %s\n", prompt, cmd);
+
+       close(pass.out);
+
+       if (finish_command(&pass))
+               exit(1);
+
+       strbuf_setlen(&buffer, strcspn(buffer.buf, "\r\n"));
+
+       return buffer.buf;
+}
+
+char *git_prompt(const char *prompt, int flags)
+{
+       char *r;
+
+       if (flags & PROMPT_ASKPASS) {
+               const char *askpass;
+
+               askpass = getenv("GIT_ASKPASS");
+               if (!askpass)
+                       askpass = askpass_program;
+               if (!askpass)
+                       askpass = getenv("SSH_ASKPASS");
+               if (askpass && *askpass)
+                       return do_askpass(askpass, prompt);
+       }
+
+       r = git_terminal_prompt(prompt, flags & PROMPT_ECHO);
+       if (!r)
+               die_errno("could not read '%s'", prompt);
+       return r;
+}
+
+char *git_getpass(const char *prompt)
+{
+       return git_prompt(prompt, PROMPT_ASKPASS);
+}
diff --git a/prompt.h b/prompt.h
new file mode 100644 (file)
index 0000000..04f321a
--- /dev/null
+++ b/prompt.h
@@ -0,0 +1,10 @@
+#ifndef PROMPT_H
+#define PROMPT_H
+
+#define PROMPT_ASKPASS (1<<0)
+#define PROMPT_ECHO    (1<<1)
+
+char *git_prompt(const char *prompt, int flags);
+char *git_getpass(const char *prompt);
+
+#endif /* PROMPT_H */
index da71a85851aa3664f14b406c57cbedbee79591f2..86d18843f52046d87741bffa9f865ec973a2ae73 100644 (file)
@@ -50,11 +50,12 @@ static struct complete_reflogs *read_complete_reflog(const char *ref)
        for_each_reflog_ent(ref, read_one_reflog, reflogs);
        if (reflogs->nr == 0) {
                unsigned char sha1[20];
-               const char *name = resolve_ref(ref, sha1, 1, NULL);
+               const char *name;
+               void *name_to_free;
+               name = name_to_free = resolve_refdup(ref, sha1, 1, NULL);
                if (name) {
-                       name = xstrdup(name);
                        for_each_reflog_ent(name, read_one_reflog, reflogs);
-                       free((char *)name);
+                       free(name_to_free);
                }
        }
        if (reflogs->nr == 0) {
@@ -171,11 +172,11 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
        else {
                if (*branch == '\0') {
                        unsigned char sha1[20];
-                       const char *head = resolve_ref("HEAD", sha1, 0, NULL);
-                       if (!head)
-                               die ("No current branch");
                        free(branch);
-                       branch = xstrdup(head);
+                       branch = resolve_refdup("HEAD", sha1, 0, NULL);
+                       if (!branch)
+                               die ("No current branch");
+
                }
                reflogs = read_complete_reflog(branch);
                if (!reflogs || reflogs->nr == 0) {
@@ -294,6 +295,18 @@ void get_reflog_message(struct strbuf *sb,
        strbuf_add(sb, info->message, len);
 }
 
+const char *get_reflog_ident(struct reflog_walk_info *reflog_info)
+{
+       struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
+       struct reflog_info *info;
+
+       if (!commit_reflog)
+               return NULL;
+
+       info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
+       return info->email;
+}
+
 void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline,
        enum date_mode dmode)
 {
index 7bd2cd4c4e5cd9d51e645509eeacc49a83a44ba7..afb1ae3fde93a5c61def211be56b0554a25dd45e 100644 (file)
@@ -14,6 +14,7 @@ extern void show_reflog_message(struct reflog_walk_info *info, int,
                enum date_mode);
 extern void get_reflog_message(struct strbuf *sb,
                struct reflog_walk_info *reflog_info);
+extern const char *get_reflog_ident(struct reflog_walk_info *reflog_info);
 extern void get_reflog_selector(struct strbuf *sb,
                struct reflog_walk_info *reflog_info,
                enum date_mode dmode,
diff --git a/refs.c b/refs.c
index f5cb297292f5ae577e3d2719f5f78512aae8cf28..6f436f1cb05e62c6afda086f5e410a268cf8bb52 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -11,6 +11,7 @@ struct ref_entry {
        unsigned char flag; /* ISSYMREF? ISPACKED? */
        unsigned char sha1[20];
        unsigned char peeled[20];
+       /* The full name of the reference (e.g., "refs/heads/master"): */
        char name[FLEX_ARRAY];
 };
 
@@ -19,6 +20,11 @@ struct ref_array {
        struct ref_entry **refs;
 };
 
+/*
+ * Parse one line from a packed-refs file.  Write the SHA1 to sha1.
+ * Return a pointer to the refname within the line (null-terminated),
+ * or NULL if there was a problem.
+ */
 static const char *parse_ref_line(char *line, unsigned char *sha1)
 {
        /*
@@ -47,27 +53,30 @@ static const char *parse_ref_line(char *line, unsigned char *sha1)
        return line;
 }
 
-static void add_ref(const char *name, const unsigned char *sha1,
-                   int flag, int check_name, struct ref_array *refs,
-                   struct ref_entry **new_entry)
+static struct ref_entry *create_ref_entry(const char *refname,
+                                         const unsigned char *sha1, int flag,
+                                         int check_name)
 {
        int len;
-       struct ref_entry *entry;
+       struct ref_entry *ref;
 
-       /* Allocate it and add it in.. */
-       len = strlen(name) + 1;
-       entry = xmalloc(sizeof(struct ref_entry) + len);
-       hashcpy(entry->sha1, sha1);
-       hashclr(entry->peeled);
        if (check_name &&
-           check_refname_format(name, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))
-               die("Reference has invalid format: '%s'", name);
-       memcpy(entry->name, name, len);
-       entry->flag = flag;
-       if (new_entry)
-               *new_entry = entry;
+           check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))
+               die("Reference has invalid format: '%s'", refname);
+       len = strlen(refname) + 1;
+       ref = xmalloc(sizeof(struct ref_entry) + len);
+       hashcpy(ref->sha1, sha1);
+       hashclr(ref->peeled);
+       memcpy(ref->name, refname, len);
+       ref->flag = flag;
+       return ref;
+}
+
+/* Add a ref_entry to the end of the ref_array (unsorted). */
+static void add_ref(struct ref_array *refs, struct ref_entry *ref)
+{
        ALLOC_GROW(refs->refs, refs->nr + 1, refs->alloc);
-       refs->refs[refs->nr++] = entry;
+       refs->refs[refs->nr++] = ref;
 }
 
 static int ref_entry_cmp(const void *a, const void *b)
@@ -77,9 +86,28 @@ static int ref_entry_cmp(const void *a, const void *b)
        return strcmp(one->name, two->name);
 }
 
+/*
+ * Emit a warning and return true iff ref1 and ref2 have the same name
+ * and the same sha1.  Die if they have the same name but different
+ * sha1s.
+ */
+static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2)
+{
+       if (!strcmp(ref1->name, ref2->name)) {
+               /* Duplicate name; make sure that the SHA1s match: */
+               if (hashcmp(ref1->sha1, ref2->sha1))
+                       die("Duplicated ref, and SHA1s don't match: %s",
+                           ref1->name);
+               warning("Duplicated ref: %s", ref1->name);
+               return 1;
+       } else {
+               return 0;
+       }
+}
+
 static void sort_ref_array(struct ref_array *array)
 {
-       int i = 0, j = 1;
+       int i, j;
 
        /* Nothing to sort unless there are at least two entries */
        if (array->nr < 2)
@@ -88,37 +116,31 @@ static void sort_ref_array(struct ref_array *array)
        qsort(array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
 
        /* Remove any duplicates from the ref_array */
-       for (; j < array->nr; j++) {
-               struct ref_entry *a = array->refs[i];
-               struct ref_entry *b = array->refs[j];
-               if (!strcmp(a->name, b->name)) {
-                       if (hashcmp(a->sha1, b->sha1))
-                               die("Duplicated ref, and SHA1s don't match: %s",
-                                   a->name);
-                       warning("Duplicated ref: %s", a->name);
-                       free(b);
+       i = 0;
+       for (j = 1; j < array->nr; j++) {
+               if (is_dup_ref(array->refs[i], array->refs[j])) {
+                       free(array->refs[j]);
                        continue;
                }
-               i++;
-               array->refs[i] = array->refs[j];
+               array->refs[++i] = array->refs[j];
        }
        array->nr = i + 1;
 }
 
-static struct ref_entry *search_ref_array(struct ref_array *array, const char *name)
+static struct ref_entry *search_ref_array(struct ref_array *array, const char *refname)
 {
        struct ref_entry *e, **r;
        int len;
 
-       if (name == NULL)
+       if (refname == NULL)
                return NULL;
 
        if (!array->nr)
                return NULL;
 
-       len = strlen(name) + 1;
+       len = strlen(refname) + 1;
        e = xmalloc(sizeof(struct ref_entry) + len);
-       memcpy(e->name, name, len);
+       memcpy(e->name, refname, len);
 
        r = bsearch(&e, array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
 
@@ -148,7 +170,7 @@ static struct ref_entry *current_ref;
 
 static struct ref_array extra_refs;
 
-static void free_ref_array(struct ref_array *array)
+static void clear_ref_array(struct ref_array *array)
 {
        int i;
        for (i = 0; i < array->nr; i++)
@@ -161,14 +183,14 @@ static void free_ref_array(struct ref_array *array)
 static void clear_packed_ref_cache(struct ref_cache *refs)
 {
        if (refs->did_packed)
-               free_ref_array(&refs->packed);
+               clear_ref_array(&refs->packed);
        refs->did_packed = 0;
 }
 
 static void clear_loose_ref_cache(struct ref_cache *refs)
 {
        if (refs->did_loose)
-               free_ref_array(&refs->loose);
+               clear_ref_array(&refs->loose);
        refs->did_loose = 0;
 }
 
@@ -222,7 +244,7 @@ static void read_packed_refs(FILE *f, struct ref_array *array)
 
        while (fgets(refline, sizeof(refline), f)) {
                unsigned char sha1[20];
-               const char *name;
+               const char *refname;
                static const char header[] = "# pack-refs with:";
 
                if (!strncmp(refline, header, sizeof(header)-1)) {
@@ -233,9 +255,10 @@ static void read_packed_refs(FILE *f, struct ref_array *array)
                        continue;
                }
 
-               name = parse_ref_line(refline, sha1);
-               if (name) {
-                       add_ref(name, sha1, flag, 1, array, &last);
+               refname = parse_ref_line(refline, sha1);
+               if (refname) {
+                       last = create_ref_entry(refname, sha1, flag, 1);
+                       add_ref(array, last);
                        continue;
                }
                if (last &&
@@ -248,26 +271,24 @@ static void read_packed_refs(FILE *f, struct ref_array *array)
        sort_ref_array(array);
 }
 
-void add_extra_ref(const char *name, const unsigned char *sha1, int flag)
+void add_extra_ref(const char *refname, const unsigned char *sha1, int flag)
 {
-       add_ref(name, sha1, flag, 0, &extra_refs, NULL);
+       add_ref(&extra_refs, create_ref_entry(refname, sha1, flag, 0));
 }
 
 void clear_extra_refs(void)
 {
-       free_ref_array(&extra_refs);
+       clear_ref_array(&extra_refs);
 }
 
-static struct ref_array *get_packed_refs(const char *submodule)
+static struct ref_array *get_packed_refs(struct ref_cache *refs)
 {
-       struct ref_cache *refs = get_ref_cache(submodule);
-
        if (!refs->did_packed) {
                const char *packed_refs_file;
                FILE *f;
 
-               if (submodule)
-                       packed_refs_file = git_path_submodule(submodule, "packed-refs");
+               if (*refs->name)
+                       packed_refs_file = git_path_submodule(refs->name, "packed-refs");
                else
                        packed_refs_file = git_path("packed-refs");
                f = fopen(packed_refs_file, "r");
@@ -280,14 +301,14 @@ static struct ref_array *get_packed_refs(const char *submodule)
        return &refs->packed;
 }
 
-static void get_ref_dir(const char *submodule, const char *base,
+static void get_ref_dir(struct ref_cache *refs, const char *base,
                        struct ref_array *array)
 {
        DIR *dir;
        const char *path;
 
-       if (submodule)
-               path = git_path_submodule(submodule, "%s", base);
+       if (*refs->name)
+               path = git_path_submodule(refs->name, "%s", base);
        else
                path = git_path("%s", base);
 
@@ -297,11 +318,11 @@ static void get_ref_dir(const char *submodule, const char *base,
        if (dir) {
                struct dirent *de;
                int baselen = strlen(base);
-               char *ref = xmalloc(baselen + 257);
+               char *refname = xmalloc(baselen + 257);
 
-               memcpy(ref, base, baselen);
+               memcpy(refname, base, baselen);
                if (baselen && base[baselen-1] != '/')
-                       ref[baselen++] = '/';
+                       refname[baselen++] = '/';
 
                while ((de = readdir(dir)) != NULL) {
                        unsigned char sha1[20];
@@ -317,30 +338,30 @@ static void get_ref_dir(const char *submodule, const char *base,
                                continue;
                        if (has_extension(de->d_name, ".lock"))
                                continue;
-                       memcpy(ref + baselen, de->d_name, namelen+1);
-                       refdir = submodule
-                               ? git_path_submodule(submodule, "%s", ref)
-                               : git_path("%s", ref);
+                       memcpy(refname + baselen, de->d_name, namelen+1);
+                       refdir = *refs->name
+                               ? git_path_submodule(refs->name, "%s", refname)
+                               : git_path("%s", refname);
                        if (stat(refdir, &st) < 0)
                                continue;
                        if (S_ISDIR(st.st_mode)) {
-                               get_ref_dir(submodule, ref, array);
+                               get_ref_dir(refs, refname, array);
                                continue;
                        }
-                       if (submodule) {
+                       if (*refs->name) {
                                hashclr(sha1);
                                flag = 0;
-                               if (resolve_gitlink_ref(submodule, ref, sha1) < 0) {
+                               if (resolve_gitlink_ref(refs->name, refname, sha1) < 0) {
                                        hashclr(sha1);
                                        flag |= REF_ISBROKEN;
                                }
-                       } else if (read_ref_full(ref, sha1, 1, &flag)) {
+                       } else if (read_ref_full(refname, sha1, 1, &flag)) {
                                hashclr(sha1);
                                flag |= REF_ISBROKEN;
                        }
-                       add_ref(ref, sha1, flag, 1, array, NULL);
+                       add_ref(array, create_ref_entry(refname, sha1, flag, 1));
                }
-               free(ref);
+               free(refname);
                closedir(dir);
        }
 }
@@ -361,7 +382,7 @@ static int warn_if_dangling_symref(const char *refname, const unsigned char *sha
        if (!(flags & REF_ISSYMREF))
                return 0;
 
-       resolves_to = resolve_ref(refname, junk, 0, NULL);
+       resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
        if (!resolves_to || strcmp(resolves_to, d->refname))
                return 0;
 
@@ -379,12 +400,10 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
        for_each_rawref(warn_if_dangling_symref, &data);
 }
 
-static struct ref_array *get_loose_refs(const char *submodule)
+static struct ref_array *get_loose_refs(struct ref_cache *refs)
 {
-       struct ref_cache *refs = get_ref_cache(submodule);
-
        if (!refs->did_loose) {
-               get_ref_dir(submodule, "refs", &refs->loose);
+               get_ref_dir(refs, "refs", &refs->loose);
                sort_ref_array(&refs->loose);
                refs->did_loose = 1;
        }
@@ -397,39 +416,39 @@ static struct ref_array *get_loose_refs(const char *submodule)
 
 /*
  * Called by resolve_gitlink_ref_recursive() after it failed to read
- * from "name", which is "module/.git/<refname>". Find <refname> in
- * the packed-refs file for the submodule.
+ * from the loose refs in ref_cache refs. Find <refname> in the
+ * packed-refs file for the submodule.
  */
-static int resolve_gitlink_packed_ref(char *name, int pathlen, const char *refname, unsigned char *result)
+static int resolve_gitlink_packed_ref(struct ref_cache *refs,
+                                     const char *refname, unsigned char *sha1)
 {
-       int retval = -1;
        struct ref_entry *ref;
-       struct ref_array *array;
+       struct ref_array *array = get_packed_refs(refs);
 
-       /* being defensive: resolve_gitlink_ref() did this for us */
-       if (pathlen < 6 || memcmp(name + pathlen - 6, "/.git/", 6))
-               die("Oops");
-       name[pathlen - 6] = '\0'; /* make it path to the submodule */
-       array = get_packed_refs(name);
        ref = search_ref_array(array, refname);
-       if (ref != NULL) {
-               memcpy(result, ref->sha1, 20);
-               retval = 0;
-       }
-       return retval;
+       if (ref == NULL)
+               return -1;
+
+       memcpy(sha1, ref->sha1, 20);
+       return 0;
 }
 
-static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *refname, unsigned char *result, int recursion)
+static int resolve_gitlink_ref_recursive(struct ref_cache *refs,
+                                        const char *refname, unsigned char *sha1,
+                                        int recursion)
 {
-       int fd, len = strlen(refname);
+       int fd, len;
        char buffer[128], *p;
+       char *path;
 
-       if (recursion > MAXDEPTH || len > MAXREFLEN)
+       if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
                return -1;
-       memcpy(name + pathlen, refname, len+1);
-       fd = open(name, O_RDONLY);
+       path = *refs->name
+               ? git_path_submodule(refs->name, "%s", refname)
+               : git_path("%s", refname);
+       fd = open(path, O_RDONLY);
        if (fd < 0)
-               return resolve_gitlink_packed_ref(name, pathlen, refname, result);
+               return resolve_gitlink_packed_ref(refs, refname, sha1);
 
        len = read(fd, buffer, sizeof(buffer)-1);
        close(fd);
@@ -440,7 +459,7 @@ static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *re
        buffer[len] = 0;
 
        /* Was it a detached head or an old-fashioned symlink? */
-       if (!get_sha1_hex(buffer, result))
+       if (!get_sha1_hex(buffer, sha1))
                return 0;
 
        /* Symref? */
@@ -450,35 +469,24 @@ static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *re
        while (isspace(*p))
                p++;
 
-       return resolve_gitlink_ref_recursive(name, pathlen, p, result, recursion+1);
+       return resolve_gitlink_ref_recursive(refs, p, sha1, recursion+1);
 }
 
-int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *result)
+int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1)
 {
        int len = strlen(path), retval;
-       char *gitdir;
-       const char *tmp;
+       char *submodule;
+       struct ref_cache *refs;
 
        while (len && path[len-1] == '/')
                len--;
        if (!len)
                return -1;
-       gitdir = xmalloc(len + MAXREFLEN + 8);
-       memcpy(gitdir, path, len);
-       memcpy(gitdir + len, "/.git", 6);
-       len += 5;
-
-       tmp = read_gitfile(gitdir);
-       if (tmp) {
-               free(gitdir);
-               len = strlen(tmp);
-               gitdir = xmalloc(len + MAXREFLEN + 3);
-               memcpy(gitdir, tmp, len);
-       }
-       gitdir[len] = '/';
-       gitdir[++len] = '\0';
-       retval = resolve_gitlink_ref_recursive(gitdir, len, refname, result, 0);
-       free(gitdir);
+       submodule = xstrndup(path, len);
+       refs = get_ref_cache(submodule);
+       free(submodule);
+
+       retval = resolve_gitlink_ref_recursive(refs, refname, sha1, 0);
        return retval;
 }
 
@@ -486,10 +494,10 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re
  * Try to read ref from the packed references.  On success, set sha1
  * and return 0; otherwise, return -1.
  */
-static int get_packed_ref(const char *ref, unsigned char *sha1)
+static int get_packed_ref(const char *refname, unsigned char *sha1)
 {
-       struct ref_array *packed = get_packed_refs(NULL);
-       struct ref_entry *entry = search_ref_array(packed, ref);
+       struct ref_array *packed = get_packed_refs(get_ref_cache(NULL));
+       struct ref_entry *entry = search_ref_array(packed, refname);
        if (entry) {
                hashcpy(sha1, entry->sha1);
                return 0;
@@ -497,17 +505,17 @@ static int get_packed_ref(const char *ref, unsigned char *sha1)
        return -1;
 }
 
-const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
+const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
 {
        int depth = MAXDEPTH;
        ssize_t len;
        char buffer[256];
-       static char ref_buffer[256];
+       static char refname_buffer[256];
 
        if (flag)
                *flag = 0;
 
-       if (check_refname_format(ref, REFNAME_ALLOW_ONELEVEL))
+       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
                return NULL;
 
        for (;;) {
@@ -519,7 +527,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                if (--depth < 0)
                        return NULL;
 
-               git_snpath(path, sizeof(path), "%s", ref);
+               git_snpath(path, sizeof(path), "%s", refname);
 
                if (lstat(path, &st) < 0) {
                        if (errno != ENOENT)
@@ -528,17 +536,17 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                         * The loose reference file does not exist;
                         * check for a packed reference.
                         */
-                       if (!get_packed_ref(ref, sha1)) {
+                       if (!get_packed_ref(refname, sha1)) {
                                if (flag)
                                        *flag |= REF_ISPACKED;
-                               return ref;
+                               return refname;
                        }
                        /* The reference is not a packed reference, either. */
                        if (reading) {
                                return NULL;
                        } else {
                                hashclr(sha1);
-                               return ref;
+                               return refname;
                        }
                }
 
@@ -550,8 +558,8 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                        buffer[len] = 0;
                        if (!prefixcmp(buffer, "refs/") &&
                                        !check_refname_format(buffer, 0)) {
-                               strcpy(ref_buffer, buffer);
-                               ref = ref_buffer;
+                               strcpy(refname_buffer, buffer);
+                               refname = refname_buffer;
                                if (flag)
                                        *flag |= REF_ISSYMREF;
                                continue;
@@ -594,7 +602,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                                *flag |= REF_ISBROKEN;
                        return NULL;
                }
-               ref = strcpy(ref_buffer, buf);
+               refname = strcpy(refname_buffer, buf);
        }
        /* Please note that FETCH_HEAD has a second line containing other data. */
        if (get_sha1_hex(buffer, sha1) || (buffer[40] != '\0' && !isspace(buffer[40]))) {
@@ -602,7 +610,13 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                        *flag |= REF_ISBROKEN;
                return NULL;
        }
-       return ref;
+       return refname;
+}
+
+char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)
+{
+       const char *ret = resolve_ref_unsafe(ref, sha1, reading, flag);
+       return ret ? xstrdup(ret) : NULL;
 }
 
 /* The argument to filter_refs */
@@ -612,16 +626,16 @@ struct ref_filter {
        void *cb_data;
 };
 
-int read_ref_full(const char *ref, unsigned char *sha1, int reading, int *flags)
+int read_ref_full(const char *refname, unsigned char *sha1, int reading, int *flags)
 {
-       if (resolve_ref(ref, sha1, reading, flags))
+       if (resolve_ref_unsafe(refname, sha1, reading, flags))
                return 0;
        return -1;
 }
 
-int read_ref(const char *ref, unsigned char *sha1)
+int read_ref(const char *refname, unsigned char *sha1)
 {
-       return read_ref_full(ref, sha1, 1, NULL);
+       return read_ref_full(refname, sha1, 1, NULL);
 }
 
 #define DO_FOR_EACH_INCLUDE_BROKEN 01
@@ -643,23 +657,23 @@ 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)
+static int filter_refs(const char *refname, const unsigned char *sha1, int flags,
+                      void *data)
 {
        struct ref_filter *filter = (struct ref_filter *)data;
-       if (fnmatch(filter->pattern, ref, 0))
+       if (fnmatch(filter->pattern, refname, 0))
                return 0;
-       return filter->fn(ref, sha, flags, filter->cb_data);
+       return filter->fn(refname, sha1, flags, filter->cb_data);
 }
 
-int peel_ref(const char *ref, unsigned char *sha1)
+int peel_ref(const char *refname, unsigned char *sha1)
 {
        int flag;
        unsigned char base[20];
        struct object *o;
 
-       if (current_ref && (current_ref->name == ref
-               || !strcmp(current_ref->name, ref))) {
+       if (current_ref && (current_ref->name == refname
+               || !strcmp(current_ref->name, refname))) {
                if (current_ref->flag & REF_KNOWS_PEELED) {
                        hashcpy(sha1, current_ref->peeled);
                        return 0;
@@ -668,12 +682,12 @@ int peel_ref(const char *ref, unsigned char *sha1)
                goto fallback;
        }
 
-       if (read_ref_full(ref, base, 1, &flag))
+       if (read_ref_full(refname, base, 1, &flag))
                return -1;
 
        if ((flag & REF_ISPACKED)) {
-               struct ref_array *array = get_packed_refs(NULL);
-               struct ref_entry *r = search_ref_array(array, ref);
+               struct ref_array *array = get_packed_refs(get_ref_cache(NULL));
+               struct ref_entry *r = search_ref_array(array, refname);
 
                if (r != NULL && r->flag & REF_KNOWS_PEELED) {
                        hashcpy(sha1, r->peeled);
@@ -684,7 +698,7 @@ int peel_ref(const char *ref, unsigned char *sha1)
 fallback:
        o = parse_object(base);
        if (o && o->type == OBJ_TAG) {
-               o = deref_tag(o, ref, 0);
+               o = deref_tag(o, refname, 0);
                if (o) {
                        hashcpy(sha1, o->sha1);
                        return 0;
@@ -697,8 +711,9 @@ static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn
                           int trim, int flags, void *cb_data)
 {
        int retval = 0, i, p = 0, l = 0;
-       struct ref_array *packed = get_packed_refs(submodule);
-       struct ref_array *loose = get_loose_refs(submodule);
+       struct ref_cache *refs = get_ref_cache(submodule);
+       struct ref_array *packed = get_packed_refs(refs);
+       struct ref_array *loose = get_loose_refs(refs);
 
        struct ref_array *extra = &extra_refs;
 
@@ -914,16 +929,16 @@ static inline int bad_ref_char(int ch)
 }
 
 /*
- * Try to read one refname component from the front of ref.  Return
+ * Try to read one refname component from the front of refname.  Return
  * the length of the component found, or -1 if the component is not
  * legal.
  */
-static int check_refname_component(const char *ref, int flags)
+static int check_refname_component(const char *refname, int flags)
 {
        const char *cp;
        char last = '\0';
 
-       for (cp = ref; ; cp++) {
+       for (cp = refname; ; cp++) {
                char ch = *cp;
                if (ch == '\0' || ch == '/')
                        break;
@@ -935,34 +950,34 @@ static int check_refname_component(const char *ref, int flags)
                        return -1; /* Refname contains "@{". */
                last = ch;
        }
-       if (cp == ref)
+       if (cp == refname)
                return -1; /* Component has zero length. */
-       if (ref[0] == '.') {
+       if (refname[0] == '.') {
                if (!(flags & REFNAME_DOT_COMPONENT))
                        return -1; /* Component starts with '.'. */
                /*
                 * Even if leading dots are allowed, don't allow "."
                 * as a component (".." is prevented by a rule above).
                 */
-               if (ref[1] == '\0')
+               if (refname[1] == '\0')
                        return -1; /* Component equals ".". */
        }
-       if (cp - ref >= 5 && !memcmp(cp - 5, ".lock", 5))
+       if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
                return -1; /* Refname ends with ".lock". */
-       return cp - ref;
+       return cp - refname;
 }
 
-int check_refname_format(const char *ref, int flags)
+int check_refname_format(const char *refname, int flags)
 {
        int component_len, component_count = 0;
 
        while (1) {
                /* We are at the start of a path component. */
-               component_len = check_refname_component(ref, flags);
+               component_len = check_refname_component(refname, flags);
                if (component_len < 0) {
                        if ((flags & REFNAME_REFSPEC_PATTERN) &&
-                                       ref[0] == '*' &&
-                                       (ref[1] == '\0' || ref[1] == '/')) {
+                                       refname[0] == '*' &&
+                                       (refname[1] == '\0' || refname[1] == '/')) {
                                /* Accept one wildcard as a full refname component. */
                                flags &= ~REFNAME_REFSPEC_PATTERN;
                                component_len = 1;
@@ -971,13 +986,13 @@ int check_refname_format(const char *ref, int flags)
                        }
                }
                component_count++;
-               if (ref[component_len] == '\0')
+               if (refname[component_len] == '\0')
                        break;
                /* Skip to next component. */
-               ref += component_len + 1;
+               refname += component_len + 1;
        }
 
-       if (ref[component_len - 1] == '.')
+       if (refname[component_len - 1] == '.')
                return -1; /* Refname ends with '.'. */
        if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)
                return -1; /* Refname has only one component. */
@@ -1053,22 +1068,28 @@ static int remove_empty_directories(const char *file)
        return result;
 }
 
-static int is_refname_available(const char *ref, const char *oldref,
-                               struct ref_array *array, int quiet)
+/*
+ * Return true iff a reference named refname could be created without
+ * conflicting with the name of an existing reference.  If oldrefname
+ * is non-NULL, ignore potential conflicts with oldrefname (e.g.,
+ * because oldrefname is scheduled for deletion in the same
+ * operation).
+ */
+static int is_refname_available(const char *refname, const char *oldrefname,
+                               struct ref_array *array)
 {
-       int i, namlen = strlen(ref); /* e.g. 'foo/bar' */
+       int i, namlen = strlen(refname); /* e.g. 'foo/bar' */
        for (i = 0; i < array->nr; i++ ) {
                struct ref_entry *entry = array->refs[i];
                /* entry->name could be 'foo' or 'foo/bar/baz' */
-               if (!oldref || strcmp(oldref, entry->name)) {
+               if (!oldrefname || strcmp(oldrefname, entry->name)) {
                        int len = strlen(entry->name);
                        int cmplen = (namlen < len) ? namlen : len;
-                       const char *lead = (namlen < len) ? entry->name : ref;
-                       if (!strncmp(ref, entry->name, cmplen) &&
+                       const char *lead = (namlen < len) ? entry->name : refname;
+                       if (!strncmp(refname, entry->name, cmplen) &&
                            lead[cmplen] == '/') {
-                               if (!quiet)
-                                       error("'%s' exists; cannot create '%s'",
-                                             entry->name, ref);
+                               error("'%s' exists; cannot create '%s'",
+                                     entry->name, refname);
                                return 0;
                        }
                }
@@ -1111,7 +1132,7 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
 
                this_result = refs_found ? sha1_from_ref : sha1;
                mksnpath(fullref, sizeof(fullref), *p, len, str);
-               r = resolve_ref(fullref, this_result, 1, &flag);
+               r = resolve_ref_unsafe(fullref, this_result, 1, &flag);
                if (r) {
                        if (!refs_found++)
                                *ref = xstrdup(r);
@@ -1141,7 +1162,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
                const char *ref, *it;
 
                mksnpath(path, sizeof(path), *p, len, str);
-               ref = resolve_ref(path, hash, 1, NULL);
+               ref = resolve_ref_unsafe(path, hash, 1, NULL);
                if (!ref)
                        continue;
                if (!stat(git_path("logs/%s", path), &st) &&
@@ -1164,10 +1185,12 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
        return logs_found;
 }
 
-static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int flags, int *type_p)
+static struct ref_lock *lock_ref_sha1_basic(const char *refname,
+                                           const unsigned char *old_sha1,
+                                           int flags, int *type_p)
 {
        char *ref_file;
-       const char *orig_ref = ref;
+       const char *orig_refname = refname;
        struct ref_lock *lock;
        int last_errno = 0;
        int type, lflags;
@@ -1177,27 +1200,27 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
        lock = xcalloc(1, sizeof(struct ref_lock));
        lock->lock_fd = -1;
 
-       ref = resolve_ref(ref, lock->old_sha1, mustexist, &type);
-       if (!ref && errno == EISDIR) {
+       refname = resolve_ref_unsafe(refname, lock->old_sha1, mustexist, &type);
+       if (!refname && errno == EISDIR) {
                /* we are trying to lock foo but we used to
                 * have foo/bar which now does not exist;
                 * it is normal for the empty directory 'foo'
                 * to remain.
                 */
-               ref_file = git_path("%s", orig_ref);
+               ref_file = git_path("%s", orig_refname);
                if (remove_empty_directories(ref_file)) {
                        last_errno = errno;
-                       error("there are still refs under '%s'", orig_ref);
+                       error("there are still refs under '%s'", orig_refname);
                        goto error_return;
                }
-               ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, &type);
+               refname = resolve_ref_unsafe(orig_refname, lock->old_sha1, mustexist, &type);
        }
        if (type_p)
            *type_p = type;
-       if (!ref) {
+       if (!refname) {
                last_errno = errno;
                error("unable to resolve reference %s: %s",
-                       orig_ref, strerror(errno));
+                       orig_refname, strerror(errno));
                goto error_return;
        }
        missing = is_null_sha1(lock->old_sha1);
@@ -1207,7 +1230,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
         * name is a proper prefix of our refname.
         */
        if (missing &&
-            !is_refname_available(ref, NULL, get_packed_refs(NULL), 0)) {
+            !is_refname_available(refname, NULL, get_packed_refs(get_ref_cache(NULL)))) {
                last_errno = ENOTDIR;
                goto error_return;
        }
@@ -1216,12 +1239,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
 
        lflags = LOCK_DIE_ON_ERROR;
        if (flags & REF_NODEREF) {
-               ref = orig_ref;
+               refname = orig_refname;
                lflags |= LOCK_NODEREF;
        }
-       lock->ref_name = xstrdup(ref);
-       lock->orig_ref_name = xstrdup(orig_ref);
-       ref_file = git_path("%s", ref);
+       lock->ref_name = xstrdup(refname);
+       lock->orig_ref_name = xstrdup(orig_refname);
+       ref_file = git_path("%s", refname);
        if (missing)
                lock->force_write = 1;
        if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
@@ -1242,20 +1265,21 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
        return NULL;
 }
 
-struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
+struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)
 {
        char refpath[PATH_MAX];
-       if (check_refname_format(ref, 0))
+       if (check_refname_format(refname, 0))
                return NULL;
-       strcpy(refpath, mkpath("refs/%s", ref));
+       strcpy(refpath, mkpath("refs/%s", refname));
        return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
 }
 
-struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags)
+struct ref_lock *lock_any_ref_for_update(const char *refname,
+                                        const unsigned char *old_sha1, int flags)
 {
-       if (check_refname_format(ref, REFNAME_ALLOW_ONELEVEL))
+       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
                return NULL;
-       return lock_ref_sha1_basic(ref, old_sha1, flags, NULL);
+       return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
 }
 
 static struct lock_file packlock;
@@ -1263,12 +1287,10 @@ static struct lock_file packlock;
 static int repack_without_ref(const char *refname)
 {
        struct ref_array *packed;
-       struct ref_entry *ref;
        int fd, i;
 
-       packed = get_packed_refs(NULL);
-       ref = search_ref_array(packed, refname);
-       if (ref == NULL)
+       packed = get_packed_refs(get_ref_cache(NULL));
+       if (search_ref_array(packed, refname) == NULL)
                return 0;
        fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
        if (fd < 0) {
@@ -1279,8 +1301,7 @@ static int repack_without_ref(const char *refname)
        for (i = 0; i < packed->nr; i++) {
                char line[PATH_MAX + 100];
                int len;
-
-               ref = packed->refs[i];
+               struct ref_entry *ref = packed->refs[i];
 
                if (!strcmp(refname, ref->name))
                        continue;
@@ -1341,97 +1362,98 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
  */
 #define TMP_RENAMED_LOG  "logs/refs/.tmp-renamed-log"
 
-int rename_ref(const char *oldref, const char *newref, const char *logmsg)
+int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
 {
        unsigned char sha1[20], orig_sha1[20];
        int flag = 0, logmoved = 0;
        struct ref_lock *lock;
        struct stat loginfo;
-       int log = !lstat(git_path("logs/%s", oldref), &loginfo);
+       int log = !lstat(git_path("logs/%s", oldrefname), &loginfo);
        const char *symref = NULL;
+       struct ref_cache *refs = get_ref_cache(NULL);
 
        if (log && S_ISLNK(loginfo.st_mode))
-               return error("reflog for %s is a symlink", oldref);
+               return error("reflog for %s is a symlink", oldrefname);
 
-       symref = resolve_ref(oldref, orig_sha1, 1, &flag);
+       symref = resolve_ref_unsafe(oldrefname, orig_sha1, 1, &flag);
        if (flag & REF_ISSYMREF)
                return error("refname %s is a symbolic ref, renaming it is not supported",
-                       oldref);
+                       oldrefname);
        if (!symref)
-               return error("refname %s not found", oldref);
+               return error("refname %s not found", oldrefname);
 
-       if (!is_refname_available(newref, oldref, get_packed_refs(NULL), 0))
+       if (!is_refname_available(newrefname, oldrefname, get_packed_refs(refs)))
                return 1;
 
-       if (!is_refname_available(newref, oldref, get_loose_refs(NULL), 0))
+       if (!is_refname_available(newrefname, oldrefname, get_loose_refs(refs)))
                return 1;
 
-       if (log && rename(git_path("logs/%s", oldref), git_path(TMP_RENAMED_LOG)))
+       if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG)))
                return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s",
-                       oldref, strerror(errno));
+                       oldrefname, strerror(errno));
 
-       if (delete_ref(oldref, orig_sha1, REF_NODEREF)) {
-               error("unable to delete old %s", oldref);
+       if (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) {
+               error("unable to delete old %s", oldrefname);
                goto rollback;
        }
 
-       if (!read_ref_full(newref, sha1, 1, &flag) &&
-           delete_ref(newref, sha1, REF_NODEREF)) {
+       if (!read_ref_full(newrefname, sha1, 1, &flag) &&
+           delete_ref(newrefname, sha1, REF_NODEREF)) {
                if (errno==EISDIR) {
-                       if (remove_empty_directories(git_path("%s", newref))) {
-                               error("Directory not empty: %s", newref);
+                       if (remove_empty_directories(git_path("%s", newrefname))) {
+                               error("Directory not empty: %s", newrefname);
                                goto rollback;
                        }
                } else {
-                       error("unable to delete existing %s", newref);
+                       error("unable to delete existing %s", newrefname);
                        goto rollback;
                }
        }
 
-       if (log && safe_create_leading_directories(git_path("logs/%s", newref))) {
-               error("unable to create directory for %s", newref);
+       if (log && safe_create_leading_directories(git_path("logs/%s", newrefname))) {
+               error("unable to create directory for %s", newrefname);
                goto rollback;
        }
 
  retry:
-       if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newref))) {
+       if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
                if (errno==EISDIR || errno==ENOTDIR) {
                        /*
                         * rename(a, b) when b is an existing
                         * directory ought to result in ISDIR, but
                         * Solaris 5.8 gives ENOTDIR.  Sheesh.
                         */
-                       if (remove_empty_directories(git_path("logs/%s", newref))) {
-                               error("Directory not empty: logs/%s", newref);
+                       if (remove_empty_directories(git_path("logs/%s", newrefname))) {
+                               error("Directory not empty: logs/%s", newrefname);
                                goto rollback;
                        }
                        goto retry;
                } else {
                        error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
-                               newref, strerror(errno));
+                               newrefname, strerror(errno));
                        goto rollback;
                }
        }
        logmoved = log;
 
-       lock = lock_ref_sha1_basic(newref, NULL, 0, NULL);
+       lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL);
        if (!lock) {
-               error("unable to lock %s for update", newref);
+               error("unable to lock %s for update", newrefname);
                goto rollback;
        }
        lock->force_write = 1;
        hashcpy(lock->old_sha1, orig_sha1);
        if (write_ref_sha1(lock, orig_sha1, logmsg)) {
-               error("unable to write current sha1 into %s", newref);
+               error("unable to write current sha1 into %s", newrefname);
                goto rollback;
        }
 
        return 0;
 
  rollback:
-       lock = lock_ref_sha1_basic(oldref, NULL, 0, NULL);
+       lock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL);
        if (!lock) {
-               error("unable to lock %s for rollback", oldref);
+               error("unable to lock %s for rollback", oldrefname);
                goto rollbacklog;
        }
 
@@ -1439,17 +1461,17 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        flag = log_all_ref_updates;
        log_all_ref_updates = 0;
        if (write_ref_sha1(lock, orig_sha1, NULL))
-               error("unable to write current sha1 into %s", oldref);
+               error("unable to write current sha1 into %s", oldrefname);
        log_all_ref_updates = flag;
 
  rollbacklog:
-       if (logmoved && rename(git_path("logs/%s", newref), git_path("logs/%s", oldref)))
+       if (logmoved && rename(git_path("logs/%s", newrefname), git_path("logs/%s", oldrefname)))
                error("unable to restore logfile %s from %s: %s",
-                       oldref, newref, strerror(errno));
+                       oldrefname, newrefname, strerror(errno));
        if (!logmoved && log &&
-           rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldref)))
+           rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldrefname)))
                error("unable to restore logfile %s from "TMP_RENAMED_LOG": %s",
-                       oldref, strerror(errno));
+                       oldrefname, strerror(errno));
 
        return 1;
 }
@@ -1506,16 +1528,16 @@ static int copy_msg(char *buf, const char *msg)
        return cp - buf;
 }
 
-int log_ref_setup(const char *ref_name, char *logfile, int bufsize)
+int log_ref_setup(const char *refname, char *logfile, int bufsize)
 {
        int logfd, oflags = O_APPEND | O_WRONLY;
 
-       git_snpath(logfile, bufsize, "logs/%s", ref_name);
+       git_snpath(logfile, bufsize, "logs/%s", refname);
        if (log_all_ref_updates &&
-           (!prefixcmp(ref_name, "refs/heads/") ||
-            !prefixcmp(ref_name, "refs/remotes/") ||
-            !prefixcmp(ref_name, "refs/notes/") ||
-            !strcmp(ref_name, "HEAD"))) {
+           (!prefixcmp(refname, "refs/heads/") ||
+            !prefixcmp(refname, "refs/remotes/") ||
+            !prefixcmp(refname, "refs/notes/") ||
+            !strcmp(refname, "HEAD"))) {
                if (safe_create_leading_directories(logfile) < 0)
                        return error("unable to create directory for %s",
                                     logfile);
@@ -1545,7 +1567,7 @@ int log_ref_setup(const char *ref_name, char *logfile, int bufsize)
        return 0;
 }
 
-static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
+static int log_ref_write(const char *refname, const unsigned char *old_sha1,
                         const unsigned char *new_sha1, const char *msg)
 {
        int logfd, result, written, oflags = O_APPEND | O_WRONLY;
@@ -1558,7 +1580,7 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
        if (log_all_ref_updates < 0)
                log_all_ref_updates = !is_bare_repository();
 
-       result = log_ref_setup(ref_name, log_file, sizeof(log_file));
+       result = log_ref_setup(refname, log_file, sizeof(log_file));
        if (result)
                return result;
 
@@ -1642,7 +1664,7 @@ int write_ref_sha1(struct ref_lock *lock,
                unsigned char head_sha1[20];
                int head_flag;
                const char *head_ref;
-               head_ref = resolve_ref("HEAD", head_sha1, 1, &head_flag);
+               head_ref = resolve_ref_unsafe("HEAD", head_sha1, 1, &head_flag);
                if (head_ref && (head_flag & REF_ISSYMREF) &&
                    !strcmp(head_ref, lock->ref_name))
                        log_ref_write("HEAD", lock->old_sha1, sha1, logmsg);
@@ -1729,7 +1751,9 @@ static char *ref_msg(const char *line, const char *endp)
        return xmemdupz(line, ep - line);
 }
 
-int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
+int read_ref_at(const char *refname, unsigned long at_time, int cnt,
+               unsigned char *sha1, char **msg,
+               unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
 {
        const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
        char *tz_c;
@@ -1740,7 +1764,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        void *log_mapped;
        size_t mapsz;
 
-       logfile = git_path("logs/%s", ref);
+       logfile = git_path("logs/%s", refname);
        logfd = open(logfile, O_RDONLY, 0);
        if (logfd < 0)
                die_errno("Unable to read log '%s'", logfile);
@@ -1833,14 +1857,14 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        return 1;
 }
 
-int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data)
+int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long ofs, void *cb_data)
 {
        const char *logfile;
        FILE *logfp;
        struct strbuf sb = STRBUF_INIT;
        int ret = 0;
 
-       logfile = git_path("logs/%s", ref);
+       logfile = git_path("logs/%s", refname);
        logfp = fopen(logfile, "r");
        if (!logfp)
                return -1;
@@ -1891,9 +1915,9 @@ int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs,
        return ret;
 }
 
-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data)
 {
-       return for_each_recent_reflog_ent(ref, fn, 0, cb_data);
+       return for_each_recent_reflog_ent(refname, fn, 0, cb_data);
 }
 
 static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
@@ -1979,7 +2003,7 @@ int update_ref(const char *action, const char *refname,
 int ref_exists(const char *refname)
 {
        unsigned char sha1[20];
-       return !!resolve_ref(refname, sha1, 1, NULL);
+       return !!resolve_ref_unsafe(refname, sha1, 1, NULL);
 }
 
 struct ref *find_ref_by_name(const struct ref *list, const char *name)
@@ -2013,7 +2037,7 @@ static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
        return;
 }
 
-char *shorten_unambiguous_ref(const char *ref, int strict)
+char *shorten_unambiguous_ref(const char *refname, int strict)
 {
        int i;
        static char **scanf_fmts;
@@ -2042,10 +2066,10 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
 
        /* bail out if there are no rules */
        if (!nr_rules)
-               return xstrdup(ref);
+               return xstrdup(refname);
 
-       /* buffer for scanf result, at most ref must fit */
-       short_name = xstrdup(ref);
+       /* buffer for scanf result, at most refname must fit */
+       short_name = xstrdup(refname);
 
        /* skip first rule, it will always match */
        for (i = nr_rules - 1; i > 0 ; --i) {
@@ -2053,7 +2077,7 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
                int rules_to_fail = i;
                int short_name_len;
 
-               if (1 != sscanf(ref, scanf_fmts[i], short_name))
+               if (1 != sscanf(refname, scanf_fmts[i], short_name))
                        continue;
 
                short_name_len = strlen(short_name);
@@ -2097,5 +2121,5 @@ char *shorten_unambiguous_ref(const char *ref, int strict)
        }
 
        free(short_name);
-       return xstrdup(ref);
+       return xstrdup(refname);
 }
diff --git a/refs.h b/refs.h
index 3fd55369f90829706c3f4b11329fadc38380da32..d4982915c58c7c17229e4a6621d02337e2550e13 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -60,14 +60,16 @@ extern void add_extra_ref(const char *refname, const unsigned char *sha1, int fl
 extern void clear_extra_refs(void);
 extern int ref_exists(const char *);
 
-extern int peel_ref(const char *, unsigned char *);
+extern int peel_ref(const char *refname, unsigned char *sha1);
 
 /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
-extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
+extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1);
 
 /** Locks any ref (for 'HEAD' type refs). */
 #define REF_NODEREF    0x01
-extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags);
+extern struct ref_lock *lock_any_ref_for_update(const char *refname,
+                                               const unsigned char *old_sha1,
+                                               int flags);
 
 /** Close the file descriptor owned by a lock and return the status */
 extern int close_ref(struct ref_lock *lock);
@@ -93,12 +95,14 @@ extern void invalidate_ref_cache(const char *submodule);
 int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
 
 /** Reads log for the value of ref during at_time. **/
-extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
+extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
+                      unsigned char *sha1, char **msg,
+                      unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
 
 /* iterate over reflog entries */
 typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
-int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data);
+int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
+int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long, void *cb_data);
 
 /*
  * Calls the specified function for each reflog file until it returns nonzero,
@@ -111,9 +115,9 @@ extern int for_each_reflog(each_ref_fn, void *);
 #define REFNAME_DOT_COMPONENT 4
 
 /*
- * Return 0 iff ref has the correct format for a refname according to
- * the rules described in Documentation/git-check-ref-format.txt.  If
- * REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level
+ * Return 0 iff refname has the correct format for a refname according
+ * to the rules described in Documentation/git-check-ref-format.txt.
+ * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level
  * reference names.  If REFNAME_REFSPEC_PATTERN is set in flags, then
  * allow a "*" wildcard character in place of one of the name
  * components.  No leading or repeated slashes are accepted.  If
@@ -121,16 +125,20 @@ extern int for_each_reflog(each_ref_fn, void *);
  * components to start with "." (but not a whole component equal to
  * "." or "..").
  */
-extern int check_refname_format(const char *ref, int flags);
+extern int check_refname_format(const char *refname, int flags);
 
 extern const char *prettify_refname(const char *refname);
-extern char *shorten_unambiguous_ref(const char *ref, int strict);
+extern char *shorten_unambiguous_ref(const char *refname, int strict);
 
 /** rename ref, return 0 on success **/
 extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
 
-/** resolve ref in nested "gitlink" repository */
-extern int resolve_gitlink_ref(const char *name, const char *refname, unsigned char *result);
+/**
+ * Resolve refname in the nested "gitlink" repository that is located
+ * at path.  If the resolution is successful, return 0 and set sha1 to
+ * the name of the object; otherwise, return a non-zero value.
+ */
+extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);
 
 /** lock a ref and then write its file */
 enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR };
index 0e720ee8bbf4cbc6a50336a1f1c93bfc63842fe3..48c20b86f3cfc6c189972718046a6890aaefacf3 100644 (file)
@@ -188,7 +188,7 @@ static int write_discovery(int in, int out, void *data)
        return err;
 }
 
-static struct ref *parse_git_refs(struct discovery *heads)
+static struct ref *parse_git_refs(struct discovery *heads, int for_push)
 {
        struct ref *list = NULL;
        struct async async;
@@ -200,7 +200,8 @@ static struct ref *parse_git_refs(struct discovery *heads)
 
        if (start_async(&async))
                die("cannot start thread to parse advertised refs");
-       get_remote_heads(async.out, &list, 0, NULL, 0, NULL);
+       get_remote_heads(async.out, &list,
+                       for_push ? REF_NORMAL : 0, NULL);
        close(async.out);
        if (finish_async(&async))
                die("ref parsing thread failed");
@@ -268,7 +269,7 @@ static struct ref *get_refs(int for_push)
                heads = discover_refs("git-upload-pack");
 
        if (heads->proto_git)
-               return parse_git_refs(heads);
+               return parse_git_refs(heads, for_push);
        return parse_info_refs(heads);
 }
 
@@ -859,7 +860,7 @@ int main(int argc, const char **argv)
 
        url = strbuf_detach(&buf, NULL);
 
-       http_init(remote, url);
+       http_init(remote, url, 0);
 
        do {
                if (strbuf_getline(&buf, stdin, '\n') == EOF) {
index 6655bb05b2d31bca080cf3cfc431394b82d89160..73a3809300c9e4589b71c486a531debfdc2cf056 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -482,7 +482,7 @@ static void read_config(void)
                return;
        default_remote_name = xstrdup("origin");
        current_branch = NULL;
-       head_ref = resolve_ref("HEAD", sha1, 0, &flag);
+       head_ref = resolve_ref_unsafe("HEAD", sha1, 0, &flag);
        if (head_ref && (flag & REF_ISSYMREF) &&
            !prefixcmp(head_ref, "refs/heads/")) {
                current_branch =
@@ -1007,7 +1007,7 @@ static char *guess_ref(const char *name, struct ref *peer)
        struct strbuf buf = STRBUF_INIT;
        unsigned char sha1[20];
 
-       const char *r = resolve_ref(peer->name, sha1, 1, NULL);
+       const char *r = resolve_ref_unsafe(peer->name, sha1, 1, NULL);
        if (!r)
                return NULL;
 
@@ -1058,7 +1058,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
                unsigned char sha1[20];
                int flag;
 
-               dst_value = resolve_ref(matched_src->name, sha1, 1, &flag);
+               dst_value = resolve_ref_unsafe(matched_src->name, sha1, 1, &flag);
                if (!dst_value ||
                    ((flag & REF_ISSYMREF) &&
                     prefixcmp(dst_value, "refs/heads/")))
index bc2c046aab23c5b3de8caa02c23703856ee86fb4..d1f28a6945cbbdc8f58a32d7bb481ecd4af6cc39 100644 (file)
@@ -3,17 +3,11 @@
 #include "strbuf.h"
 #include "dir.h"
 
-void remove_sequencer_state(int aggressive)
+void remove_sequencer_state(void)
 {
        struct strbuf seq_dir = STRBUF_INIT;
-       struct strbuf seq_old_dir = STRBUF_INIT;
 
        strbuf_addf(&seq_dir, "%s", git_path(SEQ_DIR));
-       strbuf_addf(&seq_old_dir, "%s", git_path(SEQ_OLD_DIR));
-       remove_dir_recursively(&seq_old_dir, 0);
-       rename(git_path(SEQ_DIR), git_path(SEQ_OLD_DIR));
-       if (aggressive)
-               remove_dir_recursively(&seq_old_dir, 0);
+       remove_dir_recursively(&seq_dir, 0);
        strbuf_release(&seq_dir);
-       strbuf_release(&seq_old_dir);
 }
index f435fdb4b147d2e2ce5480ddb4832228af1cb73b..2d4528f2928053827aedd0b737bf01985f1c4957 100644 (file)
@@ -2,19 +2,11 @@
 #define SEQUENCER_H
 
 #define SEQ_DIR                "sequencer"
-#define SEQ_OLD_DIR    "sequencer-old"
 #define SEQ_HEAD_FILE  "sequencer/head"
 #define SEQ_TODO_FILE  "sequencer/todo"
 #define SEQ_OPTS_FILE  "sequencer/opts"
 
-/*
- * Removes SEQ_OLD_DIR and renames SEQ_DIR to SEQ_OLD_DIR, ignoring
- * any errors.  Intended to be used by 'git reset'.
- *
- * With the aggressive flag, it additionally removes SEQ_OLD_DIR,
- * ignoring any errors.  Inteded to be used by the sequencer's
- * '--quit' subcommand.
- */
-void remove_sequencer_state(int aggressive);
+/* Removes SEQ_DIR. */
+extern void remove_sequencer_state(void);
 
 #endif
index 956422ba4a5df5f46caaf85f10e4c85321439272..f291f3f0f7d919c7d59b8b7dc3c3f2450a5a9f09 100644 (file)
@@ -18,6 +18,7 @@
 #include "refs.h"
 #include "pack-revindex.h"
 #include "sha1-lookup.h"
+#include "bulk-checkin.h"
 
 #ifndef O_NOATIME
 #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -2680,10 +2681,8 @@ static int index_core(unsigned char *sha1, int fd, size_t size,
 }
 
 /*
- * This creates one packfile per large blob, because the caller
- * immediately wants the result sha1, and fast-import can report the
- * object name via marks mechanism only by closing the created
- * packfile.
+ * This creates one packfile per large blob unless bulk-checkin
+ * machinery is "plugged".
  *
  * This also bypasses the usual "convert-to-git" dance, and that is on
  * purpose. We could write a streaming version of the converting
@@ -2697,65 +2696,7 @@ static int index_stream(unsigned char *sha1, int fd, size_t size,
                        enum object_type type, const char *path,
                        unsigned flags)
 {
-       struct child_process fast_import;
-       char export_marks[512];
-       const char *argv[] = { "fast-import", "--quiet", export_marks, NULL };
-       char tmpfile[512];
-       char fast_import_cmd[512];
-       char buf[512];
-       int len, tmpfd;
-
-       strcpy(tmpfile, git_path("hashstream_XXXXXX"));
-       tmpfd = git_mkstemp_mode(tmpfile, 0600);
-       if (tmpfd < 0)
-               die_errno("cannot create tempfile: %s", tmpfile);
-       if (close(tmpfd))
-               die_errno("cannot close tempfile: %s", tmpfile);
-       sprintf(export_marks, "--export-marks=%s", tmpfile);
-
-       memset(&fast_import, 0, sizeof(fast_import));
-       fast_import.in = -1;
-       fast_import.argv = argv;
-       fast_import.git_cmd = 1;
-       if (start_command(&fast_import))
-               die_errno("index-stream: git fast-import failed");
-
-       len = sprintf(fast_import_cmd, "blob\nmark :1\ndata %lu\n",
-                     (unsigned long) size);
-       write_or_whine(fast_import.in, fast_import_cmd, len,
-                      "index-stream: feeding fast-import");
-       while (size) {
-               char buf[10240];
-               size_t sz = size < sizeof(buf) ? size : sizeof(buf);
-               ssize_t actual;
-
-               actual = read_in_full(fd, buf, sz);
-               if (actual < 0)
-                       die_errno("index-stream: reading input");
-               if (write_in_full(fast_import.in, buf, actual) != actual)
-                       die_errno("index-stream: feeding fast-import");
-               size -= actual;
-       }
-       if (close(fast_import.in))
-               die_errno("index-stream: closing fast-import");
-       if (finish_command(&fast_import))
-               die_errno("index-stream: finishing fast-import");
-
-       tmpfd = open(tmpfile, O_RDONLY);
-       if (tmpfd < 0)
-               die_errno("index-stream: cannot open fast-import mark");
-       len = read(tmpfd, buf, sizeof(buf));
-       if (len < 0)
-               die_errno("index-stream: reading fast-import mark");
-       if (close(tmpfd) < 0)
-               die_errno("index-stream: closing fast-import mark");
-       if (unlink(tmpfile))
-               die_errno("index-stream: unlinking fast-import mark");
-       if (len != 44 ||
-           memcmp(":1 ", buf, 3) ||
-           get_sha1_hex(buf + 3, sha1))
-               die_errno("index-stream: unexpected fast-import mark: <%s>", buf);
-       return 0;
+       return index_bulk_checkin(sha1, fd, size, type, path, flags);
 }
 
 int index_fd(unsigned char *sha1, int fd, struct stat *st,
diff --git a/shell.c b/shell.c
index abb862246ef01743424e4a447ee169f3fdbb9f51..84b237fef352e2f94f853897dbb5f2364d50962d 100644 (file)
--- a/shell.c
+++ b/shell.c
@@ -137,6 +137,8 @@ int main(int argc, char **argv)
        int devnull_fd;
        int count;
 
+       git_setup_gettext();
+
        git_extract_argv0_path(argv[0]);
 
        /*
index 63f9da53237d4233bede66a28e4bcf27d5b44af1..5a9eed7fd858b6f2e454421d68e884a21acb7a23 100644 (file)
@@ -11,6 +11,8 @@ int main(int argc, char **argv)
        unsigned int version;
        static unsigned int top_index[256];
 
+       git_setup_gettext();
+
        if (argc != 1)
                usage(show_index_usage);
        if (fread(top_index, 2 * 4, 1, stdin) != 1)
index a849705197a6ad3d0d2ab9de22b269de6b4fc2b1..ff0b96b4162bd92162a7eb05eee5be7a5ec2b6ba 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -411,3 +411,40 @@ void strbuf_add_lines(struct strbuf *out, const char *prefix,
        }
        strbuf_complete_line(out);
 }
+
+static int is_rfc3986_reserved(char ch)
+{
+       switch (ch) {
+               case '!': case '*': case '\'': case '(': case ')': case ';':
+               case ':': case '@': case '&': case '=': case '+': case '$':
+               case ',': case '/': case '?': case '#': case '[': case ']':
+                       return 1;
+       }
+       return 0;
+}
+
+static int is_rfc3986_unreserved(char ch)
+{
+       return isalnum(ch) ||
+               ch == '-' || ch == '_' || ch == '.' || ch == '~';
+}
+
+void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len,
+                         int reserved)
+{
+       strbuf_grow(sb, len);
+       while (len--) {
+               char ch = *s++;
+               if (is_rfc3986_unreserved(ch) ||
+                   (!reserved && is_rfc3986_reserved(ch)))
+                       strbuf_addch(sb, ch);
+               else
+                       strbuf_addf(sb, "%%%02x", ch);
+       }
+}
+
+void strbuf_addstr_urlencode(struct strbuf *sb, const char *s,
+                            int reserved)
+{
+       strbuf_add_urlencode(sb, s, strlen(s), reserved);
+}
index 08fc13d386b410ee9163c8408557e06ab4df7833..fbf059f4d371441b58fcad748e74e106a436241f 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -123,4 +123,9 @@ extern int launch_editor(const char *path, struct strbuf *buffer, const char *co
 extern int strbuf_branchname(struct strbuf *sb, const char *name);
 extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name);
 
+extern void strbuf_add_urlencode(struct strbuf *, const char *, size_t,
+                                int reserved);
+extern void strbuf_addstr_urlencode(struct strbuf *, const char *,
+                                   int reserved);
+
 #endif /* STRBUF_H */
index 52cdcc6a6347a786deafad34efdb9dc4dc3670ff..68c1ba90b9e746b869830ec66a6f23437a4d7d08 100644 (file)
@@ -689,7 +689,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
        cp.out = -1;
        cp.dir = path;
        if (start_command(&cp))
-               die("Could not run git status --porcelain");
+               die("Could not run 'git status --porcelain' in submodule %s", path);
 
        len = strbuf_read(&buf, cp.out, 1024);
        line = buf.buf;
@@ -714,7 +714,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
        close(cp.out);
 
        if (finish_command(&cp))
-               die("git status --porcelain failed");
+               die("'git status --porcelain' failed in submodule %s", path);
 
        strbuf_release(&buf);
        return dirty_submodule;
diff --git a/t/lib-credential.sh b/t/lib-credential.sh
new file mode 100755 (executable)
index 0000000..4a37cd7
--- /dev/null
@@ -0,0 +1,254 @@
+#!/bin/sh
+
+# Try a set of credential helpers; the expected stdin,
+# stdout and stderr should be provided on stdin,
+# separated by "--".
+check() {
+       read_chunk >stdin &&
+       read_chunk >expect-stdout &&
+       read_chunk >expect-stderr &&
+       test-credential "$@" <stdin >stdout 2>stderr &&
+       test_cmp expect-stdout stdout &&
+       test_cmp expect-stderr stderr
+}
+
+read_chunk() {
+       while read line; do
+               case "$line" in
+               --) break ;;
+               *) echo "$line" ;;
+               esac
+       done
+}
+
+# Clear any residual data from previous tests. We only
+# need this when testing third-party helpers which read and
+# write outside of our trash-directory sandbox.
+#
+# Don't bother checking for success here, as it is
+# outside the scope of tests and represents a best effort to
+# clean up after ourselves.
+helper_test_clean() {
+       reject $1 https example.com store-user
+       reject $1 https example.com user1
+       reject $1 https example.com user2
+       reject $1 http path.tld user
+       reject $1 https timeout.tld user
+}
+
+reject() {
+       (
+               echo protocol=$2
+               echo host=$3
+               echo username=$4
+       ) | test-credential reject $1
+}
+
+helper_test() {
+       HELPER=$1
+
+       test_expect_success "helper ($HELPER) has no existing data" '
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               --
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''https://example.com'\'':
+               askpass: Password for '\''https://askpass-username@example.com'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) stores password" '
+               check approve $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               username=store-user
+               password=store-pass
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) can retrieve password" '
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               --
+               username=store-user
+               password=store-pass
+               --
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) requires matching protocol" '
+               check fill $HELPER <<-\EOF
+               protocol=http
+               host=example.com
+               --
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''http://example.com'\'':
+               askpass: Password for '\''http://askpass-username@example.com'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) requires matching host" '
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=other.tld
+               --
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''https://other.tld'\'':
+               askpass: Password for '\''https://askpass-username@other.tld'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) requires matching username" '
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               username=other
+               --
+               username=other
+               password=askpass-password
+               --
+               askpass: Password for '\''https://other@example.com'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) requires matching path" '
+               test_config credential.usehttppath true &&
+               check approve $HELPER <<-\EOF &&
+               protocol=http
+               host=path.tld
+               path=foo.git
+               username=user
+               password=pass
+               EOF
+               check fill $HELPER <<-\EOF
+               protocol=http
+               host=path.tld
+               path=bar.git
+               --
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''http://path.tld/bar.git'\'':
+               askpass: Password for '\''http://askpass-username@path.tld/bar.git'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) can forget host" '
+               check reject $HELPER <<-\EOF &&
+               protocol=https
+               host=example.com
+               EOF
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               --
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''https://example.com'\'':
+               askpass: Password for '\''https://askpass-username@example.com'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) can store multiple users" '
+               check approve $HELPER <<-\EOF &&
+               protocol=https
+               host=example.com
+               username=user1
+               password=pass1
+               EOF
+               check approve $HELPER <<-\EOF &&
+               protocol=https
+               host=example.com
+               username=user2
+               password=pass2
+               EOF
+               check fill $HELPER <<-\EOF &&
+               protocol=https
+               host=example.com
+               username=user1
+               --
+               username=user1
+               password=pass1
+               EOF
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               username=user2
+               --
+               username=user2
+               password=pass2
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) can forget user" '
+               check reject $HELPER <<-\EOF &&
+               protocol=https
+               host=example.com
+               username=user1
+               EOF
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               username=user1
+               --
+               username=user1
+               password=askpass-password
+               --
+               askpass: Password for '\''https://user1@example.com'\'':
+               EOF
+       '
+
+       test_expect_success "helper ($HELPER) remembers other user" '
+               check fill $HELPER <<-\EOF
+               protocol=https
+               host=example.com
+               username=user2
+               --
+               username=user2
+               password=pass2
+               EOF
+       '
+}
+
+helper_test_timeout() {
+       HELPER="$*"
+
+       test_expect_success "helper ($HELPER) times out" '
+               check approve "$HELPER" <<-\EOF &&
+               protocol=https
+               host=timeout.tld
+               username=user
+               password=pass
+               EOF
+               sleep 2 &&
+               check fill "$HELPER" <<-\EOF
+               protocol=https
+               host=timeout.tld
+               --
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''https://timeout.tld'\'':
+               askpass: Password for '\''https://askpass-username@timeout.tld'\'':
+               EOF
+       '
+}
+
+cat >askpass <<\EOF
+#!/bin/sh
+echo >&2 askpass: $*
+what=`echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z`
+echo "askpass-$what"
+EOF
+chmod +x askpass
+GIT_ASKPASS="$PWD/askpass"
+export GIT_ASKPASS
diff --git a/t/lib-gettext.sh b/t/lib-gettext.sh
new file mode 100644 (file)
index 0000000..0f76f6c
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_BUILD_DIR/po/build/locale"
+GIT_PO_PATH="$GIT_BUILD_DIR/po"
+export GIT_TEXTDOMAINDIR GIT_PO_PATH
+
+. "$GIT_BUILD_DIR"/git-sh-i18n
+
+if test_have_prereq GETTEXT && ! test_have_prereq GETTEXT_POISON
+then
+       # is_IS.UTF-8 on Solaris and FreeBSD, is_IS.utf8 on Debian
+       is_IS_locale=$(locale -a | sed -n '/^is_IS\.[uU][tT][fF]-*8$/{
+               p
+               q
+       }')
+       # is_IS.ISO8859-1 on Solaris and FreeBSD, is_IS.iso88591 on Debian
+       is_IS_iso_locale=$(locale -a | sed -n '/^is_IS\.[iI][sS][oO]8859-*1$/{
+               p
+               q
+       }')
+
+       # Export them as an environment variable so the t0202/test.pl Perl
+       # test can use it too
+       export is_IS_locale is_IS_iso_locale
+
+       if test -n "$is_IS_locale" &&
+               test $GIT_INTERNAL_GETTEXT_SH_SCHEME != "fallthrough"
+       then
+               # Some of the tests need the reference Icelandic locale
+               test_set_prereq GETTEXT_LOCALE
+
+               # Exporting for t0202/test.pl
+               GETTEXT_LOCALE=1
+               export GETTEXT_LOCALE
+               say "# lib-gettext: Found '$is_IS_locale' as an is_IS UTF-8 locale"
+       else
+               say "# lib-gettext: No is_IS UTF-8 locale available"
+       fi
+
+       if test -n "$is_IS_iso_locale" &&
+               test $GIT_INTERNAL_GETTEXT_SH_SCHEME != "fallthrough"
+       then
+               # Some of the tests need the reference Icelandic locale
+               test_set_prereq GETTEXT_ISO_LOCALE
+
+               say "# lib-gettext: Found '$is_IS_iso_locale' as an is_IS ISO-8859-1 locale"
+       else
+               say "# lib-gettext: No is_IS ISO-8859-1 locale available"
+       fi
+fi
index 0a4cdfa93ece7d8a4177835b5569583c22303564..3c12b05d60849b4f3063527338140c717b720c5d 100644 (file)
@@ -92,6 +92,9 @@ SSLEngine On
        <Location /dumb/>
                Dav on
        </Location>
+       <Location /auth/dumb>
+               Dav on
+       </Location>
 </IfDefine>
 
 <IfDefine SVN>
diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh
new file mode 100755 (executable)
index 0000000..6c33e28
--- /dev/null
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+test_description="Test whether cache-tree is properly updated
+
+Tests whether various commands properly update and/or rewrite the
+cache-tree extension.
+"
+ . ./test-lib.sh
+
+cmp_cache_tree () {
+       test-dump-cache-tree >actual &&
+       sed "s/$_x40/SHA/" <actual >filtered &&
+       test_cmp "$1" filtered
+}
+
+# We don't bother with actually checking the SHA1:
+# test-dump-cache-tree already verifies that all existing data is
+# correct.
+test_shallow_cache_tree () {
+       printf "SHA  (%d entries, 0 subtrees)\n" $(git ls-files|wc -l) >expect &&
+       cmp_cache_tree expect
+}
+
+test_invalid_cache_tree () {
+       echo "invalid                                   (0 subtrees)" >expect &&
+       printf "SHA #(ref)  (%d entries, 0 subtrees)\n" $(git ls-files|wc -l) >>expect &&
+       cmp_cache_tree expect
+}
+
+test_no_cache_tree () {
+       : >expect &&
+       cmp_cache_tree expect
+}
+
+test_expect_failure 'initial commit has cache-tree' '
+       test_commit foo &&
+       test_shallow_cache_tree
+'
+
+test_expect_success 'read-tree HEAD establishes cache-tree' '
+       git read-tree HEAD &&
+       test_shallow_cache_tree
+'
+
+test_expect_success 'git-add invalidates cache-tree' '
+       test_when_finished "git reset --hard; git read-tree HEAD" &&
+       echo "I changed this file" > foo &&
+       git add foo &&
+       test_invalid_cache_tree
+'
+
+test_expect_success 'update-index invalidates cache-tree' '
+       test_when_finished "git reset --hard; git read-tree HEAD" &&
+       echo "I changed this file" > foo &&
+       git update-index --add foo &&
+       test_invalid_cache_tree
+'
+
+test_expect_success 'write-tree establishes cache-tree' '
+       test-scrap-cache-tree &&
+       git write-tree &&
+       test_shallow_cache_tree
+'
+
+test_expect_success 'test-scrap-cache-tree works' '
+       git read-tree HEAD &&
+       test-scrap-cache-tree &&
+       test_no_cache_tree
+'
+
+test_expect_success 'second commit has cache-tree' '
+       test_commit bar &&
+       test_shallow_cache_tree
+'
+
+test_expect_success 'reset --hard gives cache-tree' '
+       test-scrap-cache-tree &&
+       git reset --hard &&
+       test_shallow_cache_tree
+'
+
+test_expect_success 'reset --hard without index gives cache-tree' '
+       rm -f .git/index &&
+       git reset --hard &&
+       test_shallow_cache_tree
+'
+
+test_expect_failure 'checkout gives cache-tree' '
+       git checkout HEAD^ &&
+       test_shallow_cache_tree
+'
+
+test_done
diff --git a/t/t0200-gettext-basic.sh b/t/t0200-gettext-basic.sh
new file mode 100755 (executable)
index 0000000..8853d8a
--- /dev/null
@@ -0,0 +1,108 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Gettext support for Git'
+
+. ./lib-gettext.sh
+
+test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success 'sanity: $TEXTDOMAIN is git' '
+    test $TEXTDOMAIN = "git"
+'
+
+test_expect_success 'xgettext sanity: Perl _() strings are not extracted' '
+    ! grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
+'
+
+test_expect_success 'xgettext sanity: Comment extraction with --add-comments' '
+    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l >expect &&
+    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext sanity: Comment extraction with --add-comments stops at statements' '
+    ! grep "This is a phony" "$GIT_PO_PATH"/is.po &&
+    ! grep "the above comment" "$GIT_PO_PATH"/is.po
+'
+
+test_expect_success GETTEXT 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' '
+    test -d "$TEXTDOMAINDIR" &&
+    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
+'
+
+test_expect_success GETTEXT 'sanity: Icelandic locale was compiled' '
+    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
+'
+
+# TODO: When we have more locales, generalize this to test them
+# all. Maybe we'll need a dir->locale map for that.
+test_expect_success GETTEXT_LOCALE 'sanity: gettext("") metadata is OK' '
+    # Return value may be non-zero
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >zero-expect &&
+    grep "Project-Id-Version: Git" zero-expect &&
+    grep "Git Mailing List <git@vger.kernel.org>" zero-expect &&
+    grep "Content-Type: text/plain; charset=UTF-8" zero-expect &&
+    grep "Content-Transfer-Encoding: 8bit" zero-expect
+'
+
+test_expect_success GETTEXT_LOCALE 'sanity: gettext(unknown) is passed through' '
+    printf "This is not a translation string"  >expect &&
+    gettext "This is not a translation string" >actual &&
+    eval_gettext "This is not a translation string" >actual &&
+    test_cmp expect actual
+'
+
+# xgettext from C
+test_expect_success GETTEXT_LOCALE 'xgettext: C extraction of _() and N_() strings' '
+    printf "TILRAUN: C tilraunastrengur" >expect &&
+    printf "\n" >>expect &&
+    printf "Sjá '\''git help SKIPUN'\'' til að sjá hjálp fyrir tiltekna skipun." >>expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A C test string" >actual &&
+    printf "\n" >>actual &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "See '\''git help COMMAND'\'' for more information on a specific command." >>actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'xgettext: C extraction with %s' '
+    printf "TILRAUN: C tilraunastrengur %%s" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A C test string %s" >actual &&
+    test_cmp expect actual
+'
+
+# xgettext from Shell
+test_expect_success GETTEXT_LOCALE 'xgettext: Shell extraction' '
+    printf "TILRAUN: Skeljartilraunastrengur" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Shell test string" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'xgettext: Shell extraction with $variable' '
+    printf "TILRAUN: Skeljartilraunastrengur með breytunni a var i able" >x-expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" variable="a var i able" eval_gettext "TEST: A Shell test \$variable" >x-actual &&
+    test_cmp x-expect x-actual
+'
+
+# xgettext from Perl
+test_expect_success GETTEXT_LOCALE 'xgettext: Perl extraction' '
+    printf "TILRAUN: Perl tilraunastrengur" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Perl test string" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'xgettext: Perl extraction with %s' '
+    printf "TILRAUN: Perl tilraunastrengur með breytunni %%s" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Perl test variable %s" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'sanity: Some gettext("") data for real locale' '
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >real-locale &&
+    test -s real-locale
+'
+
+test_done
diff --git a/t/t0200/test.c b/t/t0200/test.c
new file mode 100644 (file)
index 0000000..584d45c
--- /dev/null
@@ -0,0 +1,23 @@
+/* This is a phony C program that's only here to test xgettext message extraction */
+
+const char help[] =
+       /* TRANSLATORS: This is a test. You don't need to translate it. */
+       N_("See 'git help COMMAND' for more information on a specific command.");
+
+int main(void)
+{
+       /* TRANSLATORS: This is a test. You don't need to translate it. */
+       puts(_("TEST: A C test string"));
+
+       /* TRANSLATORS: This is a test. You don't need to translate it. */
+       printf(_("TEST: A C test string %s"), "variable");
+
+       /* TRANSLATORS: This is a test. You don't need to translate it. */
+       printf(_("TEST: Hello World!"));
+
+       /* TRANSLATORS: This is a test. You don't need to translate it. */
+       printf(_("TEST: Old English Runes"));
+
+       /* TRANSLATORS: This is a test. You don't need to translate it. */
+       printf(_("TEST: ‘single’ and “double” quotes"));
+}
diff --git a/t/t0200/test.perl b/t/t0200/test.perl
new file mode 100644 (file)
index 0000000..36fba34
--- /dev/null
@@ -0,0 +1,14 @@
+# This is a phony Perl program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+1;
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+print __("TEST: A Perl test string");
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+printf __("TEST: A Perl test variable %s"), "moo";
+
+# TRANSLATORS: If you see this, Git has a bug
+print _"TEST: A Perl string xgettext will not get";
diff --git a/t/t0200/test.sh b/t/t0200/test.sh
new file mode 100644 (file)
index 0000000..022d607
--- /dev/null
@@ -0,0 +1,14 @@
+# This is a phony Shell program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+echo
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+gettext "TEST: A Shell test string"
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+eval_gettext "TEST: A Shell test \$variable"
+
+# TRANSLATORS: If you see this, Git has a bug
+_("TEST: A Shell string xgettext won't get")
index 54d98b9b109441025b9b61c2964e29895eb360b7..52b1c27c2c9d3c6111447df4fa241b724f0e6ee1 100755 (executable)
@@ -5,8 +5,24 @@
 
 test_description='Gettext Shell fallbacks'
 
-. ./test-lib.sh
-. "$GIT_BUILD_DIR"/git-sh-i18n
+GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease
+export GIT_INTERNAL_GETTEXT_TEST_FALLBACKS
+
+. ./lib-gettext.sh
+
+test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_TEST_FALLBACKS is set' '
+    test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
+'
+
+test_expect_success C_LOCALE_OUTPUT 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is fallthrough' '
+    echo fallthrough >expect &&
+    echo $GIT_INTERNAL_GETTEXT_SH_SCHEME >actual &&
+    test_cmp expect actual
+'
 
 test_expect_success 'gettext: our gettext() fallback has pass-through semantics' '
     printf "test" >expect &&
diff --git a/t/t0202-gettext-perl.sh b/t/t0202-gettext-perl.sh
new file mode 100755 (executable)
index 0000000..428ebb0
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Perl gettext interface (Git::I18N)'
+
+. ./lib-gettext.sh
+
+if ! test_have_prereq PERL; then
+       skip_all='skipping perl interface tests, perl not available'
+       test_done
+fi
+
+"$PERL_PATH" -MTest::More -e 0 2>/dev/null || {
+       skip_all="Perl Test::More unavailable, skipping test"
+       test_done
+}
+
+# The external test will outputs its own plan
+test_external_has_tap=1
+
+test_external_without_stderr \
+    'Perl Git::I18N API' \
+    "$PERL_PATH" "$TEST_DIRECTORY"/t0202/test.pl
+
+test_done
diff --git a/t/t0202/test.pl b/t/t0202/test.pl
new file mode 100644 (file)
index 0000000..2c10cb4
--- /dev/null
@@ -0,0 +1,110 @@
+#!/usr/bin/perl
+use 5.008;
+use lib (split(/:/, $ENV{GITPERLLIB}));
+use strict;
+use warnings;
+use POSIX qw(:locale_h);
+use Test::More tests => 8;
+use Git::I18N;
+
+my $has_gettext_library = $Git::I18N::__HAS_LIBRARY;
+
+ok(1, "Testing Git::I18N with " .
+        ($has_gettext_library
+         ? (defined $Locale::Messages::VERSION
+                ? "Locale::Messages version $Locale::Messages::VERSION"
+                # Versions of Locale::Messages before 1.17 didn't have a
+                # $VERSION variable.
+                : "Locale::Messages version <1.17")
+         : "NO Perl gettext library"));
+ok(1, "Git::I18N is located at $INC{'Git/I18N.pm'}");
+
+{
+       my $exports = @Git::I18N::EXPORT;
+       ok($exports, "sanity: Git::I18N has $exports export(s)");
+}
+is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N exports everything by default");
+
+# prototypes
+{
+       # Add prototypes here when modifying the public interface to add
+       # more gettext wrapper functions.
+       my %prototypes = (qw(
+               __      $
+       ));
+       while (my ($sub, $proto) = each %prototypes) {
+               is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype");
+       }
+}
+
+# Test basic passthrough in the C locale
+{
+       local $ENV{LANGUAGE} = 'C';
+       local $ENV{LC_ALL}   = 'C';
+       local $ENV{LANG}     = 'C';
+
+       my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+       is(__($got), $expect, "Passing a string through __() in the C locale works");
+}
+
+# Test a basic message on different locales
+SKIP: {
+       unless ($ENV{GETTEXT_LOCALE}) {
+               # Can't reliably test __() with a non-C locales because the
+               # required locales may not be installed on the system.
+               #
+               # We test for these anyway as part of the shell
+               # tests. Skipping these here will eliminate failures on odd
+               # platforms with incomplete locale data.
+
+               skip "GETTEXT_LOCALE must be set by lib-gettext.sh for exhaustive Git::I18N tests", 2;
+       }
+
+       # The is_IS UTF-8 locale passed from lib-gettext.sh
+       my $is_IS_locale = $ENV{is_IS_locale};
+
+       my $test = sub {
+               my ($got, $expect, $msg, $locale) = @_;
+               # Maybe this system doesn't have the locale we're trying to
+               # test.
+               my $locale_ok = setlocale(LC_ALL, $locale);
+               is(__($got), $expect, "$msg a gettext library + <$locale> locale <$got> turns into <$expect>");
+       };
+
+       my $env_C = sub {
+               $ENV{LANGUAGE} = 'C';
+               $ENV{LC_ALL}   = 'C';
+       };
+
+       my $env_is = sub {
+               $ENV{LANGUAGE} = 'is';
+               $ENV{LC_ALL}   = $is_IS_locale;
+       };
+
+       # Translation's the same as the original
+       my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+       if ($has_gettext_library) {
+               {
+                       local %ENV; $env_C->();
+                       $test->($got, $expect, "With", 'C');
+               }
+
+               {
+                       my ($got, $expect) = ($got, 'TILRAUN: Perl tilraunastrengur');
+                       local %ENV; $env_is->();
+                       $test->($got, $expect, "With", $is_IS_locale);
+               }
+       } else {
+               {
+                       local %ENV; $env_C->();
+                       $test->($got, $expect, "Without", 'C');
+               }
+
+               {
+                       local %ENV; $env_is->();
+                       $test->($got, $expect, "Without", 'is');
+               }
+       }
+}
diff --git a/t/t0203-gettext-setlocale-sanity.sh b/t/t0203-gettext-setlocale-sanity.sh
new file mode 100755 (executable)
index 0000000..a212460
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description="The Git C functions aren't broken by setlocale(3)"
+
+. ./lib-gettext.sh
+
+test_expect_success 'git show a ISO-8859-1 commit under C locale' '
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+       test_commit "iso-c-commit" iso-under-c &&
+       git show >out 2>err &&
+       ! test -s err &&
+       grep -q "iso-c-commit" out
+'
+
+test_expect_success GETTEXT_LOCALE 'git show a ISO-8859-1 commit under a UTF-8 locale' '
+       . "$TEST_DIRECTORY"/t3901-8859-1.txt &&
+       test_commit "iso-utf8-commit" iso-under-utf8 &&
+       LANGUAGE=is LC_ALL="$is_IS_locale" git show >out 2>err &&
+       ! test -s err &&
+       grep -q "iso-utf8-commit" out
+'
+
+test_done
diff --git a/t/t0204-gettext-reencode-sanity.sh b/t/t0204-gettext-reencode-sanity.sh
new file mode 100755 (executable)
index 0000000..189af90
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description="Gettext reencoding of our *.po/*.mo files works"
+
+. ./lib-gettext.sh
+
+
+test_expect_success GETTEXT_LOCALE 'gettext: Emitting UTF-8 from our UTF-8 *.mo files / Icelandic' '
+    printf "TILRAUN: Halló Heimur!" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: Hello World!" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_LOCALE 'gettext: Emitting UTF-8 from our UTF-8 *.mo files / Runes' '
+    printf "TILRAUN: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: Old English Runes" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_ISO_LOCALE 'gettext: Emitting ISO-8859-1 from our UTF-8 *.mo files / Icelandic' '
+    printf "TILRAUN: Halló Heimur!" | iconv -f UTF-8 -t ISO8859-1 >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: Hello World!" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_ISO_LOCALE 'gettext: Emitting ISO-8859-1 from our UTF-8 *.mo files / Runes' '
+    LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: Old English Runes" >runes &&
+
+       if grep "^TEST: Old English Runes$" runes
+       then
+               say "Your system can not handle this complexity and returns the string as-is"
+       else
+               # Both Solaris and GNU libintl will return this stream of
+               # question marks, so it is s probably portable enough
+               printf "TILRAUN: ?? ???? ??? ?? ???? ?? ??? ????? ??????????? ??? ?? ????" >runes-expect &&
+               test_cmp runes-expect runes
+       fi
+'
+
+test_expect_success GETTEXT_LOCALE 'gettext: Fetching a UTF-8 msgid -> UTF-8' '
+    printf "TILRAUN: ‚einfaldar‘ og „tvöfaldar“ gæsalappir" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: ‘single’ and “double” quotes" >actual &&
+    test_cmp expect actual
+'
+
+# How these quotes get transliterated depends on the gettext implementation:
+#
+#   Debian:  ,einfaldar' og ,,tvöfaldar" [GNU libintl]
+#   FreeBSD: `einfaldar` og "tvöfaldar"  [GNU libintl]
+#   Solaris: ?einfaldar? og ?tvöfaldar?  [Solaris libintl]
+#
+# Just make sure the contents are transliterated, and don't use grep -q
+# so that these differences are emitted under --verbose for curious
+# eyes.
+test_expect_success GETTEXT_ISO_LOCALE 'gettext: Fetching a UTF-8 msgid -> ISO-8859-1' '
+    LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: ‘single’ and “double” quotes" >actual &&
+    grep "einfaldar" actual &&
+    grep "$(echo tvöfaldar | iconv -f UTF-8 -t ISO8859-1)" actual
+'
+
+test_expect_success GETTEXT_LOCALE 'gettext.c: git init UTF-8 -> UTF-8' '
+    printf "Bjó til tóma Git lind" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_locale" git init repo >actual &&
+    test_when_finished "rm -rf repo" &&
+    grep "^$(cat expect) " actual
+'
+
+test_expect_success GETTEXT_ISO_LOCALE 'gettext.c: git init UTF-8 -> ISO-8859-1' '
+    printf "Bjó til tóma Git lind" >expect &&
+    LANGUAGE=is LC_ALL="$is_IS_iso_locale" git init repo >actual &&
+    test_when_finished "rm -rf repo" &&
+    grep "^$(cat expect | iconv -f UTF-8 -t ISO8859-1) " actual
+'
+
+test_done
diff --git a/t/t0205-gettext-poison.sh b/t/t0205-gettext-poison.sh
new file mode 100755 (executable)
index 0000000..2361590
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+
+test_description='Gettext Shell poison'
+
+. ./lib-gettext.sh
+
+test_expect_success GETTEXT_POISON "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success GETTEXT_POISON 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is poison' '
+    test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "poison"
+'
+
+test_expect_success GETTEXT_POISON 'gettext: our gettext() fallback has poison semantics' '
+    printf "# GETTEXT POISON #" >expect &&
+    gettext "test" >actual &&
+    test_cmp expect actual &&
+    printf "# GETTEXT POISON #" >expect &&
+    gettext "test more words" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success GETTEXT_POISON 'eval_gettext: our eval_gettext() fallback has poison semantics' '
+    printf "# GETTEXT POISON #" >expect &&
+    eval_gettext "test" >actual &&
+    test_cmp expect actual &&
+    printf "# GETTEXT POISON #" >expect &&
+    eval_gettext "test more words" >actual &&
+    test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
new file mode 100755 (executable)
index 0000000..885af8f
--- /dev/null
@@ -0,0 +1,279 @@
+#!/bin/sh
+
+test_description='basic credential helper tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
+
+test_expect_success 'setup helper scripts' '
+       cat >dump <<-\EOF &&
+       whoami=`echo $0 | sed s/.*git-credential-//`
+       echo >&2 "$whoami: $*"
+       while IFS== read key value; do
+               echo >&2 "$whoami: $key=$value"
+               eval "$key=$value"
+       done
+       EOF
+
+       cat >git-credential-useless <<-\EOF &&
+       #!/bin/sh
+       . ./dump
+       exit 0
+       EOF
+       chmod +x git-credential-useless &&
+
+       cat >git-credential-verbatim <<-\EOF &&
+       #!/bin/sh
+       user=$1; shift
+       pass=$1; shift
+       . ./dump
+       test -z "$user" || echo username=$user
+       test -z "$pass" || echo password=$pass
+       EOF
+       chmod +x git-credential-verbatim &&
+
+       PATH="$PWD:$PATH"
+'
+
+test_expect_success 'credential_fill invokes helper' '
+       check fill "verbatim foo bar" <<-\EOF
+       --
+       username=foo
+       password=bar
+       --
+       verbatim: get
+       EOF
+'
+
+test_expect_success 'credential_fill invokes multiple helpers' '
+       check fill useless "verbatim foo bar" <<-\EOF
+       --
+       username=foo
+       password=bar
+       --
+       useless: get
+       verbatim: get
+       EOF
+'
+
+test_expect_success 'credential_fill stops when we get a full response' '
+       check fill "verbatim one two" "verbatim three four" <<-\EOF
+       --
+       username=one
+       password=two
+       --
+       verbatim: get
+       EOF
+'
+
+test_expect_success 'credential_fill continues through partial response' '
+       check fill "verbatim one \"\"" "verbatim two three" <<-\EOF
+       --
+       username=two
+       password=three
+       --
+       verbatim: get
+       verbatim: get
+       verbatim: username=one
+       EOF
+'
+
+test_expect_success 'credential_fill passes along metadata' '
+       check fill "verbatim one two" <<-\EOF
+       protocol=ftp
+       host=example.com
+       path=foo.git
+       --
+       username=one
+       password=two
+       --
+       verbatim: get
+       verbatim: protocol=ftp
+       verbatim: host=example.com
+       verbatim: path=foo.git
+       EOF
+'
+
+test_expect_success 'credential_approve calls all helpers' '
+       check approve useless "verbatim one two" <<-\EOF
+       username=foo
+       password=bar
+       --
+       --
+       useless: store
+       useless: username=foo
+       useless: password=bar
+       verbatim: store
+       verbatim: username=foo
+       verbatim: password=bar
+       EOF
+'
+
+test_expect_success 'do not bother storing password-less credential' '
+       check approve useless <<-\EOF
+       username=foo
+       --
+       --
+       EOF
+'
+
+
+test_expect_success 'credential_reject calls all helpers' '
+       check reject useless "verbatim one two" <<-\EOF
+       username=foo
+       password=bar
+       --
+       --
+       useless: erase
+       useless: username=foo
+       useless: password=bar
+       verbatim: erase
+       verbatim: username=foo
+       verbatim: password=bar
+       EOF
+'
+
+test_expect_success 'usernames can be preserved' '
+       check fill "verbatim \"\" three" <<-\EOF
+       username=one
+       --
+       username=one
+       password=three
+       --
+       verbatim: get
+       verbatim: username=one
+       EOF
+'
+
+test_expect_success 'usernames can be overridden' '
+       check fill "verbatim two three" <<-\EOF
+       username=one
+       --
+       username=two
+       password=three
+       --
+       verbatim: get
+       verbatim: username=one
+       EOF
+'
+
+test_expect_success 'do not bother completing already-full credential' '
+       check fill "verbatim three four" <<-\EOF
+       username=one
+       password=two
+       --
+       username=one
+       password=two
+       --
+       EOF
+'
+
+# We can't test the basic terminal password prompt here because
+# getpass() tries too hard to find the real terminal. But if our
+# askpass helper is run, we know the internal getpass is working.
+test_expect_success 'empty helper list falls back to internal getpass' '
+       check fill <<-\EOF
+       --
+       username=askpass-username
+       password=askpass-password
+       --
+       askpass: Username:
+       askpass: Password:
+       EOF
+'
+
+test_expect_success 'internal getpass does not ask for known username' '
+       check fill <<-\EOF
+       username=foo
+       --
+       username=foo
+       password=askpass-password
+       --
+       askpass: Password:
+       EOF
+'
+
+HELPER="!f() {
+               cat >/dev/null
+               echo username=foo
+               echo password=bar
+       }; f"
+test_expect_success 'respect configured credentials' '
+       test_config credential.helper "$HELPER" &&
+       check fill <<-\EOF
+       --
+       username=foo
+       password=bar
+       --
+       EOF
+'
+
+test_expect_success 'match configured credential' '
+       test_config credential.https://example.com.helper "$HELPER" &&
+       check fill <<-\EOF
+       protocol=https
+       host=example.com
+       path=repo.git
+       --
+       username=foo
+       password=bar
+       --
+       EOF
+'
+
+test_expect_success 'do not match configured credential' '
+       test_config credential.https://foo.helper "$HELPER" &&
+       check fill <<-\EOF
+       protocol=https
+       host=bar
+       --
+       username=askpass-username
+       password=askpass-password
+       --
+       askpass: Username for '\''https://bar'\'':
+       askpass: Password for '\''https://askpass-username@bar'\'':
+       EOF
+'
+
+test_expect_success 'pull username from config' '
+       test_config credential.https://example.com.username foo &&
+       check fill <<-\EOF
+       protocol=https
+       host=example.com
+       --
+       username=foo
+       password=askpass-password
+       --
+       askpass: Password for '\''https://foo@example.com'\'':
+       EOF
+'
+
+test_expect_success 'http paths can be part of context' '
+       check fill "verbatim foo bar" <<-\EOF &&
+       protocol=https
+       host=example.com
+       path=foo.git
+       --
+       username=foo
+       password=bar
+       --
+       verbatim: get
+       verbatim: protocol=https
+       verbatim: host=example.com
+       EOF
+       test_config credential.https://example.com.useHttpPath true &&
+       check fill "verbatim foo bar" <<-\EOF
+       protocol=https
+       host=example.com
+       path=foo.git
+       --
+       username=foo
+       password=bar
+       --
+       verbatim: get
+       verbatim: protocol=https
+       verbatim: host=example.com
+       verbatim: path=foo.git
+       EOF
+'
+
+test_done
diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh
new file mode 100755 (executable)
index 0000000..82c8411
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description='credential-cache tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
+
+test -z "$NO_UNIX_SOCKETS" || {
+       skip_all='skipping credential-cache tests, unix sockets not available'
+       test_done
+}
+
+# don't leave a stale daemon running
+trap 'code=$?; git credential-cache exit; (exit $code); die' EXIT
+
+helper_test cache
+helper_test_timeout cache --timeout=1
+
+# we can't rely on our "trap" above working after test_done,
+# as test_done will delete the trash directory containing
+# our socket, leaving us with no way to access the daemon.
+git credential-cache exit
+
+test_done
diff --git a/t/t0302-credential-store.sh b/t/t0302-credential-store.sh
new file mode 100755 (executable)
index 0000000..f61b40c
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+test_description='credential-store tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
+
+helper_test store
+
+test_done
diff --git a/t/t0303-credential-external.sh b/t/t0303-credential-external.sh
new file mode 100755 (executable)
index 0000000..267f4c8
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+test_description='external credential helper tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
+
+pre_test() {
+       test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" ||
+       eval "$GIT_TEST_CREDENTIAL_HELPER_SETUP"
+
+       # clean before the test in case there is cruft left
+       # over from a previous run that would impact results
+       helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
+}
+
+post_test() {
+       # clean afterwards so that we are good citizens
+       # and don't leave cruft in the helper's storage, which
+       # might be long-term system storage
+       helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
+}
+
+if test -z "$GIT_TEST_CREDENTIAL_HELPER"; then
+       say "# skipping external helper tests (set GIT_TEST_CREDENTIAL_HELPER)"
+else
+       pre_test
+       helper_test "$GIT_TEST_CREDENTIAL_HELPER"
+       post_test
+fi
+
+if test -z "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"; then
+       say "# skipping external helper timeout tests"
+else
+       pre_test
+       helper_test_timeout "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"
+       post_test
+fi
+
+test_done
index 6d52b824b115964c5551aaf4284205b98b885ce3..f83df8eb8b143086df00e163a30ad96e43404001 100755 (executable)
@@ -189,7 +189,7 @@ for args in "-w --stdin-paths" "--stdin-paths -w"; do
 done
 
 test_expect_success 'corrupt tree' '
-       echo abc >malformed-tree
+       echo abc >malformed-tree &&
        test_must_fail git hash-object -t tree malformed-tree
 '
 
index 0a9cedd374012f6cdb5b2658639c4168a8cdf159..fbf5f2fc0070b8f17e8d6b10ea9993eec6f35982 100755 (executable)
@@ -34,7 +34,7 @@ assert_blob_equals() {
 }
 
 test_expect_success setup '
-       cp -R "$TEST_DIRECTORY/t1013/objects" .git/
+       cp -R "$TEST_DIRECTORY/t1013/objects" .git/ &&
        git --version
 '
 
index deba111bd7c2d26f3bd38dbfa086cec209f81874..29d6024b7f1b55c09cbd7e9ed682a3e745c550d6 100755 (executable)
@@ -7,21 +7,97 @@ test_description='adding and checking out large blobs'
 
 test_expect_success setup '
        git config core.bigfilethreshold 200k &&
-       echo X | dd of=large bs=1k seek=2000
+       echo X | dd of=large1 bs=1k seek=2000 &&
+       echo X | dd of=large2 bs=1k seek=2000 &&
+       echo X | dd of=large3 bs=1k seek=2000 &&
+       echo Y | dd of=huge bs=1k seek=2500
 '
 
-test_expect_success 'add a large file' '
-       git add large &&
-       # make sure we got a packfile and no loose objects
-       test -f .git/objects/pack/pack-*.pack &&
-       test ! -f .git/objects/??/??????????????????????????????????????
+test_expect_success 'add a large file or two' '
+       git add large1 huge large2 &&
+       # make sure we got a single packfile and no loose objects
+       bad= count=0 idx= &&
+       for p in .git/objects/pack/pack-*.pack
+       do
+               count=$(( $count + 1 ))
+               if test -f "$p" && idx=${p%.pack}.idx && test -f "$idx"
+               then
+                       continue
+               fi
+               bad=t
+       done &&
+       test -z "$bad" &&
+       test $count = 1 &&
+       cnt=$(git show-index <"$idx" | wc -l) &&
+       test $cnt = 2 &&
+       for l in .git/objects/??/??????????????????????????????????????
+       do
+               test -f "$l" || continue
+               bad=t
+       done &&
+       test -z "$bad" &&
+
+       # attempt to add another copy of the same
+       git add large3 &&
+       bad= count=0 &&
+       for p in .git/objects/pack/pack-*.pack
+       do
+               count=$(( $count + 1 ))
+               if test -f "$p" && idx=${p%.pack}.idx && test -f "$idx"
+               then
+                       continue
+               fi
+               bad=t
+       done &&
+       test -z "$bad" &&
+       test $count = 1
 '
 
 test_expect_success 'checkout a large file' '
-       large=$(git rev-parse :large) &&
-       git update-index --add --cacheinfo 100644 $large another &&
+       large1=$(git rev-parse :large1) &&
+       git update-index --add --cacheinfo 100644 $large1 another &&
        git checkout another &&
-       cmp large another ;# this must not be test_cmp
+       cmp large1 another ;# this must not be test_cmp
+'
+
+test_expect_success 'packsize limit' '
+       test_create_repo mid &&
+       (
+               cd mid &&
+               git config core.bigfilethreshold 64k &&
+               git config pack.packsizelimit 256k &&
+
+               # mid1 and mid2 will fit within 256k limit but
+               # appending mid3 will bust the limit and will
+               # result in a separate packfile.
+               test-genrandom "a" $(( 66 * 1024 )) >mid1 &&
+               test-genrandom "b" $(( 80 * 1024 )) >mid2 &&
+               test-genrandom "c" $(( 128 * 1024 )) >mid3 &&
+               git add mid1 mid2 mid3 &&
+
+               count=0
+               for pi in .git/objects/pack/pack-*.idx
+               do
+                       test -f "$pi" && count=$(( $count + 1 ))
+               done &&
+               test $count = 2 &&
+
+               (
+                       git hash-object --stdin <mid1
+                       git hash-object --stdin <mid2
+                       git hash-object --stdin <mid3
+               ) |
+               sort >expect &&
+
+               for pi in .git/objects/pack/pack-*.idx
+               do
+                       git show-index <"$pi"
+               done |
+               sed -e "s/^[0-9]* \([0-9a-f]*\) .*/\1/" |
+               sort >actual &&
+
+               test_cmp expect actual
+       )
 '
 
 test_done
index 51caff047b0da1a6d1df7fa651b3a8c31e8ae3e2..0690e0edf4e758200d4febb1c7837b5c7059add6 100755 (executable)
@@ -38,7 +38,7 @@ cat > expect << EOF
        WhatEver = Second
 EOF
 test_expect_success 'similar section' '
-       git config Cores.WhatEver Second
+       git config Cores.WhatEver Second &&
        test_cmp expect .git/config
 '
 
index 647d888507a4b74b82ae4016c2f30f7d171e98ca..3acd895afb7fde7f02a446b7508966e20734c4c5 100755 (executable)
@@ -20,7 +20,7 @@ test_expect_success 'setup reflog with alternating commits' '
 '
 
 test_expect_success 'reflog shows all entries' '
-       cat >expect <<-\EOF
+       cat >expect <<-\EOF &&
                topic@{0} reset: moving to two
                topic@{1} reset: moving to one
                topic@{2} reset: moving to two
index 63849836c8b088eb495dc565ff909c367c868301..e661147c573fa7312e8f533a4a4c8ea1eda7763f 100755 (executable)
@@ -48,7 +48,7 @@ test_expect_success 'setup: helper for testing rev-parse' '
 '
 
 test_expect_success 'setup: core.worktree = relative path' '
-       unset GIT_WORK_TREE;
+       sane_unset GIT_WORK_TREE &&
        GIT_DIR=repo.git &&
        GIT_CONFIG="$(pwd)"/$GIT_DIR/config &&
        export GIT_DIR GIT_CONFIG &&
@@ -89,7 +89,7 @@ test_expect_success 'subdir of work tree' '
 '
 
 test_expect_success 'setup: core.worktree = absolute path' '
-       unset GIT_WORK_TREE;
+       sane_unset GIT_WORK_TREE &&
        GIT_DIR=$(pwd)/repo.git &&
        GIT_CONFIG=$GIT_DIR/config &&
        export GIT_DIR GIT_CONFIG &&
@@ -334,7 +334,7 @@ test_expect_success 'absolute pathspec should fail gracefully' '
 '
 
 test_expect_success 'make_relative_path handles double slashes in GIT_DIR' '
-       >dummy_file
+       >dummy_file &&
        echo git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file &&
        git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file
 '
index ec50a9ad70450cb80074a6002c66bf2efb8e7833..80aedfca8c15bea1104b4985023f338059ae751c 100755 (executable)
@@ -603,7 +603,7 @@ test_expect_success '#22a: core.worktree = GIT_DIR = .git dir' '
        # like case #6.
 
        setup_repo 22a "$here/22a/.git" "" unset &&
-       setup_repo 22ab . "" unset
+       setup_repo 22ab . "" unset &&
        mkdir -p 22a/.git/sub 22a/sub &&
        mkdir -p 22ab/.git/sub 22ab/sub &&
        try_case 22a/.git unset . \
@@ -742,7 +742,7 @@ test_expect_success '#28: core.worktree and core.bare conflict (gitfile case)' '
 # Case #29: GIT_WORK_TREE(+core.worktree) overrides core.bare (gitfile case).
 test_expect_success '#29: setup' '
        setup_repo 29 non-existent gitfile true &&
-       mkdir -p 29/sub/sub 29/wt/sub
+       mkdir -p 29/sub/sub 29/wt/sub &&
        (
                cd 29 &&
                GIT_WORK_TREE="$here/29" &&
index e043cb7c64958ede89eb640f121d2daabf5f9503..eaefc777bd98aeb4cae2ba34eec0e5fe2c2fbd72 100755 (executable)
@@ -6,7 +6,7 @@ test_description='tests for ref^{stuff}'
 
 test_expect_success 'setup' '
        echo blob >a-blob &&
-       git tag -a -m blob blob-tag `git hash-object -w a-blob`
+       git tag -a -m blob blob-tag `git hash-object -w a-blob` &&
        mkdir a-tree &&
        echo moreblobs >a-tree/another-blob &&
        git add . &&
index 75874e85dfbcae8ea9634693a93524841b741559..2741262369e40a05cdc6732e4c9e6f04acb63bba 100755 (executable)
@@ -189,12 +189,13 @@ test_expect_success 'checkout -b <describe>' '
        test_cmp expect actual
 '
 
-test_expect_success 'checkout -B to the current branch fails before merging' '
+test_expect_success 'checkout -B to the current branch works' '
        git checkout branch1 &&
+       git checkout -B branch1-scratch &&
+
        setup_dirty_mergeable &&
-       git commit -mfooble &&
-       test_must_fail git checkout -B branch1 initial &&
-       test_must_fail test_dirty_mergeable
+       git checkout -B branch1-scratch initial &&
+       test_dirty_mergeable
 '
 
 test_done
diff --git a/t/t2023-checkout-m.sh b/t/t2023-checkout-m.sh
new file mode 100755 (executable)
index 0000000..7e18985
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description='checkout -m -- <conflicted path>
+
+Ensures that checkout -m on a resolved file restores the conflicted file'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       test_tick &&
+       test_commit both.txt both.txt initial &&
+       git branch topic &&
+       test_commit modified_in_master both.txt in_master &&
+       test_commit added_in_master each.txt in_master &&
+       git checkout topic &&
+       test_commit modified_in_topic both.txt in_topic &&
+       test_commit added_in_topic each.txt in_topic
+'
+
+test_expect_success 'git merge master' '
+    test_must_fail git merge master
+'
+
+clean_branchnames () {
+       # Remove branch names after conflict lines
+       sed 's/^\([<>]\{5,\}\) .*$/\1/'
+}
+
+test_expect_success '-m restores 2-way conflicted+resolved file' '
+       cp each.txt each.txt.conflicted &&
+       echo resolved >each.txt &&
+       git add each.txt &&
+       git checkout -m -- each.txt &&
+       clean_branchnames <each.txt >each.txt.cleaned &&
+       clean_branchnames <each.txt.conflicted >each.txt.conflicted.cleaned &&
+       test_cmp each.txt.conflicted.cleaned each.txt.cleaned
+'
+
+test_expect_success '-m restores 3-way conflicted+resolved file' '
+       cp both.txt both.txt.conflicted &&
+       echo resolved >both.txt &&
+       git add both.txt &&
+       git checkout -m -- both.txt &&
+       clean_branchnames <both.txt >both.txt.cleaned &&
+       clean_branchnames <both.txt.conflicted >both.txt.conflicted.cleaned &&
+       test_cmp both.txt.conflicted.cleaned both.txt.cleaned
+'
+
+test_done
index 55ef1895d7fd15348c47a5dc4a7f93541a1d38c1..a5e3da7e419e6f13ea0960722b4c1c712a995112 100755 (executable)
@@ -285,17 +285,7 @@ test_expect_success 'merge-recursive simple' '
        rm -fr [abcd] &&
        git checkout -f "$c2" &&
 
-       git merge-recursive "$c0" -- "$c2" "$c1"
-       status=$?
-       case "$status" in
-       1)
-               : happy
-               ;;
-       *)
-               echo >&2 "why status $status!!!"
-               false
-               ;;
-       esac
+       test_expect_code 1 git merge-recursive "$c0" -- "$c2" "$c1"
 '
 
 test_expect_success 'merge-recursive result' '
@@ -334,17 +324,7 @@ test_expect_success 'merge-recursive remove conflict' '
        rm -fr [abcd] &&
        git checkout -f "$c1" &&
 
-       git merge-recursive "$c0" -- "$c1" "$c5"
-       status=$?
-       case "$status" in
-       1)
-               : happy
-               ;;
-       *)
-               echo >&2 "why status $status!!!"
-               false
-               ;;
-       esac
+       test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c5"
 '
 
 test_expect_success 'merge-recursive remove conflict' '
@@ -388,17 +368,7 @@ test_expect_success 'merge-recursive d/f conflict' '
        git reset --hard &&
        git checkout -f "$c1" &&
 
-       git merge-recursive "$c0" -- "$c1" "$c4"
-       status=$?
-       case "$status" in
-       1)
-               : happy
-               ;;
-       *)
-               echo >&2 "why status $status!!!"
-               false
-               ;;
-       esac
+       test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c4"
 '
 
 test_expect_success 'merge-recursive d/f conflict result' '
@@ -422,17 +392,7 @@ test_expect_success 'merge-recursive d/f conflict the other way' '
        git reset --hard &&
        git checkout -f "$c4" &&
 
-       git merge-recursive "$c0" -- "$c4" "$c1"
-       status=$?
-       case "$status" in
-       1)
-               : happy
-               ;;
-       *)
-               echo >&2 "why status $status!!!"
-               false
-               ;;
-       esac
+       test_expect_code 1 git merge-recursive "$c0" -- "$c4" "$c1"
 '
 
 test_expect_success 'merge-recursive d/f conflict result the other way' '
@@ -456,17 +416,7 @@ test_expect_success 'merge-recursive d/f conflict' '
        git reset --hard &&
        git checkout -f "$c1" &&
 
-       git merge-recursive "$c0" -- "$c1" "$c6"
-       status=$?
-       case "$status" in
-       1)
-               : happy
-               ;;
-       *)
-               echo >&2 "why status $status!!!"
-               false
-               ;;
-       esac
+       test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c6"
 '
 
 test_expect_success 'merge-recursive d/f conflict result' '
@@ -490,17 +440,7 @@ test_expect_success 'merge-recursive d/f conflict' '
        git reset --hard &&
        git checkout -f "$c6" &&
 
-       git merge-recursive "$c0" -- "$c6" "$c1"
-       status=$?
-       case "$status" in
-       1)
-               : happy
-               ;;
-       *)
-               echo >&2 "why status $status!!!"
-               false
-               ;;
-       esac
+       test_expect_code 1 git merge-recursive "$c0" -- "$c6" "$c1"
 '
 
 test_expect_success 'merge-recursive d/f conflict result' '
index f6973e96a59916c6048222bfa0064aec5dea3746..0a4ff6d824a0a3bf44ba8d93c2a8dffdb9c1af65 100755 (executable)
@@ -3,81 +3,81 @@
 test_description='Basic subproject functionality'
 . ./test-lib.sh
 
-test_expect_success 'Super project creation' \
-    ': >Makefile &&
-    git add Makefile &&
-    git commit -m "Superproject created"'
-
-
-cat >expected <<EOF
-:000000 160000 00000... A      sub1
-:000000 160000 00000... A      sub2
-EOF
-test_expect_success 'create subprojects' \
-    'mkdir sub1 &&
-    ( cd sub1 && git init && : >Makefile && git add * &&
-    git commit -q -m "subproject 1" ) &&
-    mkdir sub2 &&
-    ( cd sub2 && git init && : >Makefile && git add * &&
-    git commit -q -m "subproject 2" ) &&
-    git update-index --add sub1 &&
-    git add sub2 &&
-    git commit -q -m "subprojects added" &&
-    git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current &&
-    test_cmp expected current'
-
-git branch save HEAD
-
-test_expect_success 'check if fsck ignores the subprojects' \
-    'git fsck --full'
-
-test_expect_success 'check if commit in a subproject detected' \
-    '( cd sub1 &&
-    echo "all:" >>Makefile &&
-    echo "     true" >>Makefile &&
-    git commit -q -a -m "make all" ) && {
-        git diff-files --exit-code
-       test $? = 1
-    }'
-
-test_expect_success 'check if a changed subproject HEAD can be committed' \
-    'git commit -q -a -m "sub1 changed" && {
-       git diff-tree --exit-code HEAD^ HEAD
-       test $? = 1
-    }'
-
-test_expect_success 'check if diff-index works for subproject elements' \
-    'git diff-index --exit-code --cached save -- sub1
-    test $? = 1'
-
-test_expect_success 'check if diff-tree works for subproject elements' \
-    'git diff-tree --exit-code HEAD^ HEAD -- sub1
-    test $? = 1'
-
-test_expect_success 'check if git diff works for subproject elements' \
-    'git diff --exit-code HEAD^ HEAD
-    test $? = 1'
-
-test_expect_success 'check if clone works' \
-    'git ls-files -s >expected &&
-    git clone -l -s . cloned &&
-    ( cd cloned && git ls-files -s ) >current &&
-    test_cmp expected current'
-
-test_expect_success 'removing and adding subproject' \
-    'git update-index --force-remove -- sub2 &&
-    mv sub2 sub3 &&
-    git add sub3 &&
-    git commit -q -m "renaming a subproject" && {
-       git diff -M --name-status --exit-code HEAD^ HEAD
-       test $? = 1
-    }'
+test_expect_success 'setup: create superproject' '
+       : >Makefile &&
+       git add Makefile &&
+       git commit -m "Superproject created"
+'
+
+test_expect_success 'setup: create subprojects' '
+       mkdir sub1 &&
+       ( cd sub1 && git init && : >Makefile && git add * &&
+       git commit -q -m "subproject 1" ) &&
+       mkdir sub2 &&
+       ( cd sub2 && git init && : >Makefile && git add * &&
+       git commit -q -m "subproject 2" ) &&
+       git update-index --add sub1 &&
+       git add sub2 &&
+       git commit -q -m "subprojects added" &&
+       git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current &&
+       git branch save HEAD &&
+       cat >expected <<-\EOF &&
+       :000000 160000 00000... A       sub1
+       :000000 160000 00000... A       sub2
+       EOF
+       test_cmp expected current
+'
+
+test_expect_success 'check if fsck ignores the subprojects' '
+       git fsck --full
+'
+
+test_expect_success 'check if commit in a subproject detected' '
+       ( cd sub1 &&
+       echo "all:" >>Makefile &&
+       echo "  true" >>Makefile &&
+       git commit -q -a -m "make all" ) &&
+       test_expect_code 1 git diff-files --exit-code
+'
+
+test_expect_success 'check if a changed subproject HEAD can be committed' '
+       git commit -q -a -m "sub1 changed" &&
+       test_expect_code 1 git diff-tree --exit-code HEAD^ HEAD
+'
+
+test_expect_success 'check if diff-index works for subproject elements' '
+       test_expect_code 1 git diff-index --exit-code --cached save -- sub1
+'
+
+test_expect_success 'check if diff-tree works for subproject elements' '
+       test_expect_code 1 git diff-tree --exit-code HEAD^ HEAD -- sub1
+'
+
+test_expect_success 'check if git diff works for subproject elements' '
+       test_expect_code 1 git diff --exit-code HEAD^ HEAD
+'
+
+test_expect_success 'check if clone works' '
+       git ls-files -s >expected &&
+       git clone -l -s . cloned &&
+       ( cd cloned && git ls-files -s ) >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'removing and adding subproject' '
+       git update-index --force-remove -- sub2 &&
+       mv sub2 sub3 &&
+       git add sub3 &&
+       git commit -q -m "renaming a subproject" &&
+       test_expect_code 1 git diff -M --name-status --exit-code HEAD^ HEAD
+'
 
 # the index must contain the object name the HEAD of the
 # subproject sub1 was at the point "save"
-test_expect_success 'checkout in superproject' \
-    'git checkout save &&
-    git diff-index --exit-code --raw --cached save -- sub1'
+test_expect_success 'checkout in superproject' '
+       git checkout save &&
+       git diff-index --exit-code --raw --cached save -- sub1
+'
 
 # just interesting what happened...
 # git diff --name-status -M save master
index bc73c2099b5069ab136a1d93aef5a8ab4a0dad1f..ea82424e471cd53cd08f91c77500412fa192f960 100755 (executable)
@@ -22,7 +22,7 @@ test_expect_success \
 
 test_expect_success \
     'git branch --help should not have created a bogus branch' '
-     git branch --help </dev/null >/dev/null 2>/dev/null;
+     test_might_fail git branch --help </dev/null >/dev/null 2>/dev/null &&
      test_path_is_missing .git/refs/heads/--help
 '
 
@@ -88,7 +88,7 @@ test_expect_success \
 test_expect_success \
     'git branch -m n/n n should work' \
        'git branch -l n/n &&
-        git branch -m n/n n
+       git branch -m n/n n &&
        test_path_is_file .git/logs/refs/heads/n'
 
 test_expect_success 'git branch -m o/o o should fail when o/p exists' '
@@ -115,6 +115,22 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou
        git branch -M baz bam
 '
 
+test_expect_success 'git branch -M master should work when master is checked out' '
+       git checkout master &&
+       git branch -M master
+'
+
+test_expect_success 'git branch -M master master should work when master is checked out' '
+       git checkout master &&
+       git branch -M master master
+'
+
+test_expect_success 'git branch -M master2 master2 should work when master is checked out' '
+       git checkout master &&
+       git branch master2 &&
+       git branch -M master2 master2
+'
+
 test_expect_success 'git branch -v -d t should work' '
        git branch t &&
        test_path_is_file .git/refs/heads/t &&
index 4ec4d11450e0cee83ed0b3639341a1e1a0ea6f7f..436719795376f78e3a32a441e9e7e0a4606ac2f5 100755 (executable)
@@ -389,7 +389,7 @@ test_expect_success 'abort notes merge' '
        test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
        test_cmp /dev/null output &&
        # m has not moved (still == y)
-       test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)"
+       test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" &&
        # Verify that other notes refs has not changed (w, x, y and z)
        verify_notes w &&
        verify_notes x &&
@@ -525,9 +525,9 @@ EOF
        test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
        test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
        # Refs are unchanged
-       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
-       test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)"
-       test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)"
+       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
+       test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
+       test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
        # Mention refs/notes/m, and its current and expected value in output
        grep -q "refs/notes/m" output &&
        grep -q "$(git rev-parse refs/notes/m)" output &&
@@ -545,7 +545,7 @@ test_expect_success 'resolve situation by aborting the notes merge' '
        test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
        test_cmp /dev/null output &&
        # m has not moved (still == w)
-       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
        # Verify that other notes refs has not changed (w, x, y and z)
        verify_notes w &&
        verify_notes x &&
index 6eaecec906c49749237b243f772f2e33eb0efedd..c3555332366d687d04bfbc031b1be808f3caa802 100755 (executable)
@@ -172,8 +172,8 @@ test_expect_success 'fail when upstream arg is missing and not configured' '
 
 test_expect_success 'default to @{upstream} when upstream arg is missing' '
        git checkout -b default topic &&
-       git config branch.default.remote .
-       git config branch.default.merge refs/heads/master
+       git config branch.default.remote . &&
+       git config branch.default.merge refs/heads/master &&
        git rebase &&
        test "$(git rev-parse default~1)" = "$(git rev-parse master)"
 '
index aea6685984b9f0e132d34842c3ac99d7ea044905..7ba17974c585d005fb4f1c757b76377f0c0518a5 100755 (executable)
@@ -11,51 +11,35 @@ local branch.
 '
 . ./test-lib.sh
 
-test_expect_success \
-    'prepare repository with topic branch' \
-    'echo First > A &&
-     git update-index --add A &&
-     git commit -m "Add A." &&
-
-     git checkout -b my-topic-branch &&
-
-     echo Second > B &&
-     git update-index --add B &&
-     git commit -m "Add B." &&
-
-     echo AnotherSecond > C &&
-     git update-index --add C &&
-     git commit -m "Add C." &&
-
-     git checkout -f master &&
-
-     echo Third >> A &&
-     git update-index A &&
-     git commit -m "Modify A."
+test_expect_success 'prepare repository with topic branch' '
+       test_commit A &&
+       git checkout -b my-topic-branch &&
+       test_commit B &&
+       test_commit C &&
+       git checkout -f master &&
+       test_commit A2 A.t
 '
 
-test_expect_success \
-    'pick top patch from topic branch into master' \
-    'git cherry-pick my-topic-branch^0 &&
-     git checkout -f my-topic-branch &&
-     git branch master-merge master &&
-     git branch my-topic-branch-merge my-topic-branch
+test_expect_success 'pick top patch from topic branch into master' '
+       git cherry-pick C &&
+       git checkout -f my-topic-branch
 '
 
-test_debug \
-    'git cherry master &&
-     git format-patch -k --stdout --full-index master >/dev/null &&
-     gitk --all & sleep 1
+test_debug '
+       git cherry master &&
+       git format-patch -k --stdout --full-index master >/dev/null &&
+       gitk --all & sleep 1
 '
 
-test_expect_success \
-    'rebase topic branch against new master and check git am did not get halted' \
-    'git rebase master && test ! -d .git/rebase-apply'
+test_expect_success 'rebase topic branch against new master and check git am did not get halted' '
+       git rebase master &&
+       test_path_is_missing .git/rebase-apply
+'
 
-test_expect_success \
-       'rebase --merge topic branch that was partially merged upstream' \
-       'git checkout -f my-topic-branch-merge &&
-        git rebase --merge master-merge &&
-        test ! -d .git/rebase-merge'
+test_expect_success 'rebase --merge topic branch that was partially merged upstream' '
+       git reset --hard C &&
+       git rebase --merge master &&
+       test_path_is_missing .git/rebase-merge
+'
 
 test_done
index 1e855cdae55ff46cbb878b724328b57cdb8069ce..2680375628207d0d46ee4a9c8335840609461e0a 100755 (executable)
@@ -51,7 +51,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' '
        test_commit "commit-new-file-F3-on-topic-branch" F3 32 &&
        test_when_finished "rm -fr test-bin funny.was.run" &&
        mkdir test-bin &&
-       cat >test-bin/git-merge-funny <<-EOF
+       cat >test-bin/git-merge-funny <<-EOF &&
        #!$SHELL_PATH
        case "\$1" in --opt) ;; *) exit 2 ;; esac
        shift &&
@@ -77,7 +77,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' '
 test_expect_success 'rebase --continue remembers --rerere-autoupdate' '
        rm -fr .git/rebase-* &&
        git reset --hard commit-new-file-F3-on-topic-branch &&
-       git checkout master
+       git checkout master &&
        test_commit "commit-new-file-F3" F3 3 &&
        git config rerere.enabled true &&
        test_must_fail git rebase -m master topic &&
index bd8efaf005a9708f153ea873850acca994c64f29..e70ac10a0cdbcd112b7b3c3e74aa89b09d46d1df 100755 (executable)
@@ -39,7 +39,7 @@ run()
 }
 
 test_expect_success 'setup' '
-       git commit --allow-empty -m initial
+       git commit --allow-empty -m initial &&
        git tag root
 '
 
index 2c4c1c851dcbb1cd38edc2a2620e2b04e36f7192..e80050e1fef9c7c2d83a34aaa671415ea168e8c4 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='Test cherry-pick continuation features
 
+ +  conflicting: rewrites unrelated to conflicting
   + yetanotherpick: rewrites foo to e
   + anotherpick: rewrites foo to d
   + picked: rewrites foo to c
@@ -27,6 +28,7 @@ test_cmp_rev () {
 }
 
 test_expect_success setup '
+       git config advice.detachedhead false
        echo unrelated >unrelated &&
        git add unrelated &&
        test_commit initial foo a &&
@@ -35,8 +37,8 @@ test_expect_success setup '
        test_commit picked foo c &&
        test_commit anotherpick foo d &&
        test_commit yetanotherpick foo e &&
-       git config advice.detachedhead false
-
+       pristine_detach initial &&
+       test_commit conflicting unrelated
 '
 
 test_expect_success 'cherry-pick persists data on failure' '
@@ -48,6 +50,18 @@ test_expect_success 'cherry-pick persists data on failure' '
        test_path_is_file .git/sequencer/opts
 '
 
+test_expect_success 'cherry-pick mid-cherry-pick-sequence' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..anotherpick &&
+       test_cmp_rev picked CHERRY_PICK_HEAD &&
+       # "oops, I forgot that these patches rely on the change from base"
+       git checkout HEAD foo &&
+       git cherry-pick base &&
+       git cherry-pick picked &&
+       git cherry-pick --continue &&
+       git diff --exit-code anotherpick
+'
+
 test_expect_success 'cherry-pick persists opts correctly' '
        pristine_detach initial &&
        test_must_fail git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours base..anotherpick &&
@@ -189,10 +203,10 @@ test_expect_success '--abort refuses to clobber unrelated change, harder case' '
        test_cmp_rev initial HEAD
 '
 
-test_expect_success 'cherry-pick cleans up sequencer state when one commit is left' '
+test_expect_success 'cherry-pick still writes sequencer state when one commit is left' '
        pristine_detach initial &&
        test_must_fail git cherry-pick base..picked &&
-       test_path_is_missing .git/sequencer &&
+       test_path_is_dir .git/sequencer &&
        echo "resolved" >foo &&
        git add foo &&
        git commit &&
@@ -213,7 +227,7 @@ test_expect_success 'cherry-pick cleans up sequencer state when one commit is le
        test_cmp expect actual
 '
 
-test_expect_failure '--abort after last commit in sequence' '
+test_expect_success '--abort after last commit in sequence' '
        pristine_detach initial &&
        test_must_fail git cherry-pick base..picked &&
        git cherry-pick --abort &&
@@ -243,7 +257,66 @@ test_expect_success '--continue complains when there are unresolved conflicts' '
        test_must_fail git cherry-pick --continue
 '
 
-test_expect_success '--continue continues after conflicts are resolved' '
+test_expect_success '--continue of single cherry-pick' '
+       pristine_detach initial &&
+       echo c >expect &&
+       test_must_fail git cherry-pick picked &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+
+       test_cmp expect foo &&
+       test_cmp_rev initial HEAD^ &&
+       git diff --exit-code HEAD &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
+test_expect_success '--continue of single revert' '
+       pristine_detach initial &&
+       echo resolved >expect &&
+       echo "Revert \"picked\"" >expect.msg &&
+       test_must_fail git revert picked &&
+       echo resolved >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+
+       git diff --exit-code HEAD &&
+       test_cmp expect foo &&
+       test_cmp_rev initial HEAD^ &&
+       git diff-tree -s --pretty=tformat:%s HEAD >msg &&
+       test_cmp expect.msg msg &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+       test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success '--continue after resolving conflicts' '
+       pristine_detach initial &&
+       echo d >expect &&
+       cat >expect.log <<-\EOF &&
+       OBJID
+       :100644 100644 OBJID OBJID M    foo
+       OBJID
+       :100644 100644 OBJID OBJID M    foo
+       OBJID
+       :100644 100644 OBJID OBJID M    unrelated
+       OBJID
+       :000000 100644 OBJID OBJID A    foo
+       :000000 100644 OBJID OBJID A    unrelated
+       EOF
+       test_must_fail git cherry-pick base..anotherpick &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+       {
+               git rev-list HEAD |
+               git diff-tree --root --stdin |
+               sed "s/$_x40/OBJID/g"
+       } >actual.log &&
+       test_cmp expect foo &&
+       test_cmp expect.log actual.log
+'
+
+test_expect_success '--continue after resolving conflicts and committing' '
        pristine_detach initial &&
        test_must_fail git cherry-pick base..anotherpick &&
        echo "c" >foo &&
@@ -270,6 +343,29 @@ test_expect_success '--continue continues after conflicts are resolved' '
        test_cmp expect actual
 '
 
+test_expect_success '--continue asks for help after resolving patch to nil' '
+       pristine_detach conflicting &&
+       test_must_fail git cherry-pick initial..picked &&
+
+       test_cmp_rev unrelatedpick CHERRY_PICK_HEAD &&
+       git checkout HEAD -- unrelated &&
+       test_must_fail git cherry-pick --continue 2>msg &&
+       test_i18ngrep "The previous cherry-pick is now empty" msg
+'
+
+test_expect_success 'follow advice and skip nil patch' '
+       pristine_detach conflicting &&
+       test_must_fail git cherry-pick initial..picked &&
+
+       git checkout HEAD -- unrelated &&
+       test_must_fail git cherry-pick --continue &&
+       git reset &&
+       git cherry-pick --continue &&
+
+       git rev-list initial..HEAD >commits &&
+       test_line_count = 3 commits
+'
+
 test_expect_success '--continue respects opts' '
        pristine_detach initial &&
        test_must_fail git cherry-pick -x base..anotherpick &&
@@ -288,6 +384,29 @@ test_expect_success '--continue respects opts' '
        grep "cherry picked from" anotherpick_msg
 '
 
+test_expect_success '--continue of single-pick respects -x' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -x picked &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       git cat-file commit HEAD >msg &&
+       grep "cherry picked from" msg
+'
+
+test_expect_success '--continue respects -x in first commit in multi-pick' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -x picked anotherpick &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+       test_path_is_missing .git/sequencer &&
+       git cat-file commit HEAD^ >msg &&
+       picked=$(git rev-parse --verify picked) &&
+       grep "cherry picked from.*$picked" msg
+'
+
 test_expect_success '--signoff is not automatically propagated to resolved conflict' '
        pristine_detach initial &&
        test_must_fail git cherry-pick --signoff base..anotherpick &&
@@ -306,6 +425,32 @@ test_expect_success '--signoff is not automatically propagated to resolved confl
        grep "Signed-off-by:" anotherpick_msg
 '
 
+test_expect_success '--signoff dropped for implicit commit of resolution, multi-pick case' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -s picked anotherpick &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+
+       git diff --exit-code HEAD &&
+       test_cmp_rev initial HEAD^^ &&
+       git cat-file commit HEAD^ >msg &&
+       ! grep Signed-off-by: msg
+'
+
+test_expect_success 'sign-off needs to be reaffirmed after conflict resolution, single-pick case' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick -s picked &&
+       echo c >foo &&
+       git add foo &&
+       git cherry-pick --continue &&
+
+       git diff --exit-code HEAD &&
+       test_cmp_rev initial HEAD^ &&
+       git cat-file commit HEAD >msg &&
+       ! grep Signed-off-by: msg
+'
+
 test_expect_success 'malformed instruction sheet 1' '
        pristine_detach initial &&
        test_must_fail git cherry-pick base..anotherpick &&
@@ -328,4 +473,9 @@ test_expect_success 'malformed instruction sheet 2' '
        test_must_fail git cherry-pick --continue
 '
 
+test_expect_success 'empty commit set' '
+       pristine_detach initial &&
+       test_expect_code 128 git cherry-pick base..base
+'
+
 test_done
index 1f62c151b0aa63b4c85f9bc76f501d53967b0260..d48a7c002d622ffac5087be9a7998f781a242731 100755 (executable)
@@ -34,6 +34,12 @@ test_expect_success 'no encoding header for base case' '
        test z = "z$E"
 '
 
+test_expect_failure 'UTF-16 refused because of NULs' '
+       echo UTF-16 >F &&
+       git commit -a -F "$TEST_DIRECTORY"/t3900/UTF-16.txt
+'
+
+
 for H in ISO8859-1 eucJP ISO-2022-JP
 do
        test_expect_success "$H setup" '
index 94373ca9a0c20a4465a8788eef3dd7596f6e6c2d..b1361ce54693a07486f5837b0fe477f828b80a4e 100755 (executable)
@@ -11,7 +11,7 @@ test_expect_success 'setup' '
        test_commit 1 &&
        test_commit 2 &&
        mkdir sub &&
-       test_commit 3 sub/3 &&
+       test_commit 3 sub/3.t &&
        test_commit 4
 '
 
diff --git a/t/t4136-apply-check.sh b/t/t4136-apply-check.sh
new file mode 100755 (executable)
index 0000000..a321f7c
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+test_description='git apply should exit non-zero with unrecognized input.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit 1
+'
+
+test_expect_success 'apply --check exits non-zero with unrecognized input' '
+       test_must_fail git apply --check - <<-\EOF
+       I am not a patch
+       I look nothing like a patch
+       git apply must fail
+       EOF
+'
+
+test_done
index d9068981f8475c37d30e584bc6b795075a2f3063..527c9e7548517d4109a9da077cff804e072e38f1 100755 (executable)
@@ -96,7 +96,7 @@ test_expect_success 'git archive with --output' \
     'git archive --output=b4.tar HEAD &&
     test_cmp b.tar b4.tar'
 
-test_expect_success NOT_MINGW 'git archive --remote' \
+test_expect_success 'git archive --remote' \
     'git archive --remote=. HEAD >b5.tar &&
     test_cmp b.tar b5.tar'
 
@@ -242,6 +242,14 @@ test_expect_success \
     'git archive --list outside of a git repo' \
     'GIT_DIR=some/non-existing/directory git archive --list'
 
+test_expect_success 'clients cannot access unreachable commits' '
+       test_commit unreachable &&
+       sha1=`git rev-parse HEAD` &&
+       git reset --hard HEAD^ &&
+       git archive $sha1 >remote.tar &&
+       test_must_fail git archive --remote=. $sha1 >remote.tar
+'
+
 test_expect_success 'git-archive --prefix=olde-' '
        git archive --prefix=olde- >h.tar HEAD &&
        (
@@ -266,7 +274,7 @@ test_expect_success 'archive --list mentions user filter' '
        grep "^bar\$" output
 '
 
-test_expect_success NOT_MINGW 'archive --list shows only enabled remote filters' '
+test_expect_success 'archive --list shows only enabled remote filters' '
        git archive --list --remote=. >output &&
        ! grep "^tar\.foo\$" output &&
        grep "^bar\$" output
@@ -298,7 +306,7 @@ test_expect_success 'extension matching requires dot' '
        test_cmp b.tar config-implicittar.foo
 '
 
-test_expect_success NOT_MINGW 'only enabled filters are available remotely' '
+test_expect_success 'only enabled filters are available remotely' '
        test_must_fail git archive --remote=. --format=tar.foo HEAD \
                >remote.tar.foo &&
        git archive --remote=. --format=bar >remote.bar HEAD &&
@@ -341,12 +349,12 @@ test_expect_success GZIP,GUNZIP 'extract tgz file' '
        test_cmp b.tar j.tar
 '
 
-test_expect_success GZIP,NOT_MINGW 'remote tar.gz is allowed by default' '
+test_expect_success GZIP 'remote tar.gz is allowed by default' '
        git archive --remote=. --format=tar.gz HEAD >remote.tar.gz &&
        test_cmp j.tgz remote.tar.gz
 '
 
-test_expect_success GZIP,NOT_MINGW 'remote tar.gz can be disabled' '
+test_expect_success GZIP 'remote tar.gz can be disabled' '
        git config tar.tar.gz.remote false &&
        test_must_fail git archive --remote=. --format=tar.gz HEAD \
                >remote.tar.gz
index ea6f692bafa9f2c4f81985a50ed79a1c5016b493..da25bc2d1fb3a68b1d29b1a5b3f5d18f6c0ffdca 100755 (executable)
@@ -67,9 +67,11 @@ test_expect_success 'setup: two scripts for reading pull requests' '
 
        cat <<-\EOT >read-request.sed &&
        #!/bin/sed -nf
+       # Note that a request could ask for "tag $tagname"
        / in the git repository at:$/!d
        n
        /^$/ n
+       s/ tag \([^ ]*\)$/ tag--\1/
        s/^[    ]*\(.*\) \([^ ]*\)/please pull\
        \1\
        \2/p
@@ -178,6 +180,7 @@ test_expect_success 'request names an appropriate branch' '
                read branch
        } <digest &&
        {
+               test "$branch" = full ||
                test "$branch" = master ||
                test "$branch" = for-upstream
        }
index bafcca765e4fea92f430e7127506a2370e062ec7..9bf69e9a0f0bff632932929b5ba41f767baf3fca 100755 (executable)
@@ -97,7 +97,7 @@ test_expect_success 'setup' '
        git symbolic-ref HEAD refs/heads/B
 '
 
-pull_to_client 1st "A" $((11*3))
+pull_to_client 1st "refs/heads/B refs/heads/A" $((11*3))
 
 test_expect_success 'post 1st pull setup' '
        add A11 $A10 &&
@@ -110,9 +110,9 @@ test_expect_success 'post 1st pull setup' '
        done
 '
 
-pull_to_client 2nd "B" $((64*3))
+pull_to_client 2nd "refs/heads/B" $((64*3))
 
-pull_to_client 3rd "A" $((1*3))
+pull_to_client 3rd "refs/heads/A" $((1*3))
 
 test_expect_success 'clone shallow' '
        git clone --depth 2 "file://$(pwd)/." shallow
diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh
new file mode 100755 (executable)
index 0000000..edea9f9
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+test_description='test fetching of oddly-named refs'
+. ./test-lib.sh
+
+# afterwards we will have:
+#  HEAD - two
+#  refs/for/refs/heads/master - one
+#  refs/heads/master - three
+test_expect_success 'setup repo with odd suffix ref' '
+       echo content >file &&
+       git add . &&
+       git commit -m one &&
+       git update-ref refs/for/refs/heads/master HEAD &&
+       echo content >>file &&
+       git commit -a -m two &&
+       echo content >>file &&
+       git commit -a -m three &&
+       git checkout HEAD^
+'
+
+test_expect_success 'suffix ref is ignored during fetch' '
+       git clone --bare file://"$PWD" suffix &&
+       echo three >expect &&
+       git --git-dir=suffix log -1 --format=%s refs/heads/master >actual &&
+       test_cmp expect actual
+'
+
+test_done
index 64767d87055c9ad65a225432b5193497c9fad405..1eea64765608dc4a2e4b19eec4155890306e5d59 100755 (executable)
@@ -40,6 +40,22 @@ test_expect_success 'setup remote repository' '
        mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
 '
 
+test_expect_success 'create password-protected repository' '
+       mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb" &&
+       cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
+              "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git"
+'
+
+test_expect_success 'setup askpass helper' '
+       cat >askpass <<-\EOF &&
+       #!/bin/sh
+       echo user@host
+       EOF
+       chmod +x askpass &&
+       GIT_ASKPASS="$PWD/askpass" &&
+       export GIT_ASKPASS
+'
+
 test_expect_success 'clone remote repository' '
        cd "$ROOT_PATH" &&
        git clone $HTTPD_URL/dumb/test_repo.git test_repo_clone
@@ -144,6 +160,24 @@ test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
 test_http_push_nonff "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \
        "$ROOT_PATH"/test_repo_clone master
 
+test_expect_success 'push to password-protected repository (user in URL)' '
+       test_commit pw-user &&
+       git push "$HTTPD_URL_USER/auth/dumb/test_repo.git" HEAD &&
+       git rev-parse --verify HEAD >expect &&
+       git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" \
+               rev-parse --verify HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_failure 'push to password-protected repository (no user in URL)' '
+       test_commit pw-nouser &&
+       git push "$HTTPD_URL/auth/dumb/test_repo.git" HEAD &&
+       git rev-parse --verify HEAD >expect &&
+       git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/test_repo.git" \
+               rev-parse --verify HEAD >actual &&
+       test_cmp expect actual
+'
+
 stop_httpd
 
 test_done
index a73c82635ff6fba6fcef4f139ff09205c1b9b6de..9b85d420c39a0f2192b6b829a6c7912ae28d8e92 100755 (executable)
@@ -154,5 +154,37 @@ test_expect_success 'push (chunked)' '
         test $HEAD = $(git rev-parse --verify HEAD))
 '
 
+test_expect_success 'push --all can push to empty repo' '
+       d=$HTTPD_DOCUMENT_ROOT_PATH/empty-all.git &&
+       git init --bare "$d" &&
+       git --git-dir="$d" config http.receivepack true &&
+       git push --all "$HTTPD_URL"/smart/empty-all.git
+'
+
+test_expect_success 'push --mirror can push to empty repo' '
+       d=$HTTPD_DOCUMENT_ROOT_PATH/empty-mirror.git &&
+       git init --bare "$d" &&
+       git --git-dir="$d" config http.receivepack true &&
+       git push --mirror "$HTTPD_URL"/smart/empty-mirror.git
+'
+
+test_expect_success 'push --all to repo with alternates' '
+       s=$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git &&
+       d=$HTTPD_DOCUMENT_ROOT_PATH/alternates-all.git &&
+       git clone --bare --shared "$s" "$d" &&
+       git --git-dir="$d" config http.receivepack true &&
+       git --git-dir="$d" repack -adl &&
+       git push --all "$HTTPD_URL"/smart/alternates-all.git
+'
+
+test_expect_success 'push --mirror to repo with alternates' '
+       s=$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git &&
+       d=$HTTPD_DOCUMENT_ROOT_PATH/alternates-mirror.git &&
+       git clone --bare --shared "$s" "$d" &&
+       git --git-dir="$d" config http.receivepack true &&
+       git --git-dir="$d" repack -adl &&
+       git push --mirror "$HTTPD_URL"/smart/alternates-mirror.git
+'
+
 stop_httpd
 test_done
index 311a33ca84f693b9410bab9e282ad0ebcae87441..95a133d697204a3b5dc469d188939198727e183a 100755 (executable)
@@ -49,40 +49,84 @@ test_expect_success 'setup askpass helpers' '
        EOF
        chmod +x askpass &&
        GIT_ASKPASS="$PWD/askpass" &&
-       export GIT_ASKPASS &&
-       >askpass-expect-none &&
-       echo "askpass: Password for '\''$HTTPD_DEST'\'': " >askpass-expect-pass &&
-       { echo "askpass: Username for '\''$HTTPD_DEST'\'': " &&
-         cat askpass-expect-pass
-       } >askpass-expect-both
-'
+       export GIT_ASKPASS
+'
+
+expect_askpass() {
+       dest=$HTTPD_DEST
+       {
+               case "$1" in
+               none)
+                       ;;
+               pass)
+                       echo "askpass: Password for 'http://$2@$dest': "
+                       ;;
+               both)
+                       echo "askpass: Username for 'http://$dest': "
+                       echo "askpass: Password for 'http://$2@$dest': "
+                       ;;
+               *)
+                       false
+                       ;;
+               esac
+       } >askpass-expect &&
+       test_cmp askpass-expect askpass-query
+}
 
 test_expect_success 'cloning password-protected repository can fail' '
        >askpass-query &&
        echo wrong >askpass-response &&
        test_must_fail git clone "$HTTPD_URL/auth/repo.git" clone-auth-fail &&
-       test_cmp askpass-expect-both askpass-query
+       expect_askpass both wrong
 '
 
 test_expect_success 'http auth can use user/pass in URL' '
        >askpass-query &&
-       echo wrong >askpass-reponse &&
+       echo wrong >askpass-response &&
        git clone "$HTTPD_URL_USER_PASS/auth/repo.git" clone-auth-none &&
-       test_cmp askpass-expect-none askpass-query
+       expect_askpass none
 '
 
 test_expect_success 'http auth can use just user in URL' '
        >askpass-query &&
        echo user@host >askpass-response &&
        git clone "$HTTPD_URL_USER/auth/repo.git" clone-auth-pass &&
-       test_cmp askpass-expect-pass askpass-query
+       expect_askpass pass user@host
 '
 
 test_expect_success 'http auth can request both user and pass' '
        >askpass-query &&
        echo user@host >askpass-response &&
        git clone "$HTTPD_URL/auth/repo.git" clone-auth-both &&
-       test_cmp askpass-expect-both askpass-query
+       expect_askpass both user@host
+'
+
+test_expect_success 'http auth respects credential helper config' '
+       test_config_global credential.helper "!f() {
+               cat >/dev/null
+               echo username=user@host
+               echo password=user@host
+       }; f" &&
+       >askpass-query &&
+       echo wrong >askpass-response &&
+       git clone "$HTTPD_URL/auth/repo.git" clone-auth-helper &&
+       expect_askpass none
+'
+
+test_expect_success 'http auth can get username from config' '
+       test_config_global "credential.$HTTPD_URL.username" user@host &&
+       >askpass-query &&
+       echo user@host >askpass-response &&
+       git clone "$HTTPD_URL/auth/repo.git" clone-auth-user &&
+       expect_askpass pass user@host
+'
+
+test_expect_success 'configured username does not override URL' '
+       test_config_global "credential.$HTTPD_URL.username" wrong &&
+       >askpass-query &&
+       echo user@host >askpass-response &&
+       git clone "$HTTPD_URL_USER/auth/repo.git" clone-auth-user2 &&
+       expect_askpass pass user@host
 '
 
 test_expect_success 'fetch changes via http' '
index d918cc02d090157485d404d34d49005e50cd9f1a..444279077e803ca96e48281ae956ea6536317608 100755 (executable)
@@ -267,6 +267,12 @@ test_expect_success '%gd shortens ref name' '
        test_cmp expect.gd-short actual.gd-short
 '
 
+test_expect_success 'reflog identity' '
+       echo "C O Mitter:committer@example.com" >expect &&
+       git log -g -1 --format="%gn:%ge" >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'oneline with empty message' '
        git commit -m "dummy" --allow-empty &&
        git commit -m "dummy" --allow-empty &&
index 320e1d1dbe62c81e29186d7a32b73447d2f998b8..ff2590849de960cf6ec751f83904f1792862f626 100755 (executable)
@@ -6,11 +6,6 @@ test_description='Test automatic use of a pager.'
 . "$TEST_DIRECTORY"/lib-pager.sh
 . "$TEST_DIRECTORY"/lib-terminal.sh
 
-cleanup_fail() {
-       echo >&2 cleanup failed
-       (exit 1)
-}
-
 test_expect_success 'setup' '
        sane_unset GIT_PAGER GIT_PAGER_IN_USE &&
        test_unconfig core.pager &&
@@ -22,9 +17,7 @@ test_expect_success 'setup' '
 '
 
 test_expect_success TTY 'some commands use a pager' '
-       rm -f paginated.out ||
-       cleanup_fail &&
-
+       rm -f paginated.out &&
        test_terminal git log &&
        test -e paginated.out
 '
@@ -45,49 +38,37 @@ test_expect_failure TTY 'pager runs from subdir' '
 '
 
 test_expect_success TTY 'some commands do not use a pager' '
-       rm -f paginated.out ||
-       cleanup_fail &&
-
+       rm -f paginated.out &&
        test_terminal git rev-list HEAD &&
        ! test -e paginated.out
 '
 
 test_expect_success 'no pager when stdout is a pipe' '
-       rm -f paginated.out ||
-       cleanup_fail &&
-
+       rm -f paginated.out &&
        git log | cat &&
        ! test -e paginated.out
 '
 
 test_expect_success 'no pager when stdout is a regular file' '
-       rm -f paginated.out ||
-       cleanup_fail &&
-
+       rm -f paginated.out &&
        git log >file &&
        ! test -e paginated.out
 '
 
 test_expect_success TTY 'git --paginate rev-list uses a pager' '
-       rm -f paginated.out ||
-       cleanup_fail &&
-
+       rm -f paginated.out &&
        test_terminal git --paginate rev-list HEAD &&
        test -e paginated.out
 '
 
 test_expect_success 'no pager even with --paginate when stdout is a pipe' '
-       rm -f file paginated.out ||
-       cleanup_fail &&
-
+       rm -f file paginated.out &&
        git --paginate log | cat &&
        ! test -e paginated.out
 '
 
 test_expect_success TTY 'no pager with --no-pager' '
-       rm -f paginated.out ||
-       cleanup_fail &&
-
+       rm -f paginated.out &&
        test_terminal git --no-pager log &&
        ! test -e paginated.out
 '
@@ -136,9 +117,7 @@ colorful() {
 }
 
 test_expect_success 'tests can detect color' '
-       rm -f colorful.log colorless.log ||
-       cleanup_fail &&
-
+       rm -f colorful.log colorless.log &&
        git log --no-color >colorless.log &&
        git log --color >colorful.log &&
        ! colorful colorless.log &&
@@ -147,18 +126,14 @@ test_expect_success 'tests can detect color' '
 
 test_expect_success 'no color when stdout is a regular file' '
        rm -f colorless.log &&
-       test_config color.ui auto ||
-       cleanup_fail &&
-
+       test_config color.ui auto &&
        git log >colorless.log &&
        ! colorful colorless.log
 '
 
 test_expect_success TTY 'color when writing to a pager' '
        rm -f paginated.out &&
-       test_config color.ui auto ||
-       cleanup_fail &&
-
+       test_config color.ui auto &&
        (
                TERM=vt100 &&
                export TERM &&
@@ -181,9 +156,7 @@ test_expect_success TTY 'colors are suppressed by color.pager' '
 
 test_expect_success 'color when writing to a file intended for a pager' '
        rm -f colorful.log &&
-       test_config color.ui auto ||
-       cleanup_fail &&
-
+       test_config color.ui auto &&
        (
                TERM=vt100 &&
                GIT_PAGER_IN_USE=true &&
@@ -242,9 +215,7 @@ test_default_pager() {
        $test_expectation SIMPLEPAGER,TTY "$cmd - default pager is used by default" "
                sane_unset PAGER GIT_PAGER &&
                test_unconfig core.pager &&
-               rm -f default_pager_used ||
-               cleanup_fail &&
-
+               rm -f default_pager_used &&
                cat >\$less <<-\EOF &&
                #!/bin/sh
                wc >default_pager_used
@@ -265,9 +236,7 @@ test_PAGER_overrides() {
        $test_expectation TTY "$cmd - PAGER overrides default pager" "
                sane_unset GIT_PAGER &&
                test_unconfig core.pager &&
-               rm -f PAGER_used ||
-               cleanup_fail &&
-
+               rm -f PAGER_used &&
                PAGER='wc >PAGER_used' &&
                export PAGER &&
                $full_command &&
@@ -292,9 +261,7 @@ test_core_pager() {
 
        $test_expectation TTY "$cmd - repository-local core.pager setting $used_if_wanted" "
                sane_unset GIT_PAGER &&
-               rm -f core.pager_used ||
-               cleanup_fail &&
-
+               rm -f core.pager_used &&
                PAGER=wc &&
                export PAGER &&
                test_config core.pager 'wc >core.pager_used' &&
@@ -321,9 +288,7 @@ test_pager_subdir_helper() {
        $test_expectation TTY "$cmd - core.pager $used_if_wanted from subdirectory" "
                sane_unset GIT_PAGER &&
                rm -f core.pager_used &&
-               rm -fr sub ||
-               cleanup_fail &&
-
+               rm -fr sub &&
                PAGER=wc &&
                stampname=\$(pwd)/core.pager_used &&
                export PAGER stampname &&
@@ -341,9 +306,7 @@ test_GIT_PAGER_overrides() {
        parse_args "$@"
 
        $test_expectation TTY "$cmd - GIT_PAGER overrides core.pager" "
-               rm -f GIT_PAGER_used ||
-               cleanup_fail &&
-
+               rm -f GIT_PAGER_used &&
                test_config core.pager wc &&
                GIT_PAGER='wc >GIT_PAGER_used' &&
                export GIT_PAGER &&
@@ -356,9 +319,7 @@ test_doesnt_paginate() {
        parse_args "$@"
 
        $test_expectation TTY "no pager for '$cmd'" "
-               rm -f GIT_PAGER_used ||
-               cleanup_fail &&
-
+               rm -f GIT_PAGER_used &&
                GIT_PAGER='wc >GIT_PAGER_used' &&
                export GIT_PAGER &&
                $full_command &&
diff --git a/t/t7106-reset-sequence.sh b/t/t7106-reset-sequence.sh
deleted file mode 100755 (executable)
index 83f7ea5..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/bin/sh
-
-test_description='Test interaction of reset --hard with sequencer
-
-  + anotherpick: rewrites foo to d
-  + picked: rewrites foo to c
-  + unrelatedpick: rewrites unrelated to reallyunrelated
-  + base: rewrites foo to b
-  + initial: writes foo as a, unrelated as unrelated
-'
-
-. ./test-lib.sh
-
-pristine_detach () {
-       git cherry-pick --quit &&
-       git checkout -f "$1^0" &&
-       git read-tree -u --reset HEAD &&
-       git clean -d -f -f -q -x
-}
-
-test_expect_success setup '
-       echo unrelated >unrelated &&
-       git add unrelated &&
-       test_commit initial foo a &&
-       test_commit base foo b &&
-       test_commit unrelatedpick unrelated reallyunrelated &&
-       test_commit picked foo c &&
-       test_commit anotherpick foo d &&
-       git config advice.detachedhead false
-
-'
-
-test_expect_success 'reset --hard cleans up sequencer state, providing one-level undo' '
-       pristine_detach initial &&
-       test_must_fail git cherry-pick base..anotherpick &&
-       test_path_is_dir .git/sequencer &&
-       git reset --hard &&
-       test_path_is_missing .git/sequencer &&
-       test_path_is_dir .git/sequencer-old &&
-       git reset --hard &&
-       test_path_is_missing .git/sequencer-old
-'
-
-test_expect_success 'cherry-pick --abort does not leave sequencer-old dir' '
-       pristine_detach initial &&
-       test_must_fail git cherry-pick base..anotherpick &&
-       git cherry-pick --abort &&
-       test_path_is_missing .git/sequencer &&
-       test_path_is_missing .git/sequencer-old
-'
-
-test_done
index 3ad04363b5920497617f806a18a3a5a0083ac1b9..8bb38337a9796142bc091c2b108f7f9e79b0f377 100755 (executable)
@@ -8,39 +8,39 @@
 
 test_description='git commit'
 . ./test-lib.sh
+. "$TEST_DIRECTORY/diff-lib.sh"
 
-test_tick
+author='The Real Author <someguy@his.email.org>'
 
-test_expect_success \
-       "initial status" \
-       "echo 'bongo bongo' >file &&
-        git add file"
+test_tick
 
-test_expect_success "Constructing initial commit" '
+test_expect_success 'initial status' '
+       echo bongo bongo >file &&
+       git add file &&
        git status >actual &&
        test_i18ngrep "Initial commit" actual
 '
 
-test_expect_success \
-       "fail initial amend" \
-       "test_must_fail git commit --amend"
+test_expect_success 'fail initial amend' '
+       test_must_fail git commit --amend
+'
 
-test_expect_success \
-       "initial commit" \
-       "git commit -m initial"
+test_expect_success 'setup: initial commit' '
+       git commit -m initial
+'
 
-test_expect_success \
-       "invalid options 1" \
-       "test_must_fail git commit -m foo -m bar -F file"
+test_expect_success '-m and -F do not mix' '
+       test_must_fail git commit -m foo -m bar -F file
+'
 
-test_expect_success \
-       "invalid options 2" \
-       "test_must_fail git commit -C HEAD -m illegal"
+test_expect_success '-m and -C do not mix' '
+       test_must_fail git commit -C HEAD -m illegal
+'
 
-test_expect_success \
-       "using paths with -a" \
-       "echo King of the bongo >file &&
-       test_must_fail git commit -m foo -a file"
+test_expect_success 'paths and -a do not mix' '
+       echo King of the bongo >file &&
+       test_must_fail git commit -m foo -a file
+'
 
 test_expect_success PERL 'can use paths with --interactive' '
        echo bong-o-bong >file &&
@@ -50,139 +50,163 @@ test_expect_success PERL 'can use paths with --interactive' '
        git reset --hard HEAD^
 '
 
-test_expect_success \
-       "using invalid commit with -C" \
-       "test_must_fail git commit -C bogus"
+test_expect_success 'using invalid commit with -C' '
+       test_must_fail git commit -C bogus
+'
 
-test_expect_success \
-       "testing nothing to commit" \
-       "test_must_fail git commit -m initial"
+test_expect_success 'nothing to commit' '
+       test_must_fail git commit -m initial
+'
 
-test_expect_success \
-       "next commit" \
-       "echo 'bongo bongo bongo' >file \
-        git commit -m next -a"
+test_expect_success 'setup: non-initial commit' '
+       echo bongo bongo bongo >file &&
+       git commit -m next -a
+'
 
-test_expect_success \
-       "commit message from non-existing file" \
-       "echo 'more bongo: bongo bongo bongo bongo' >file && \
-        test_must_fail git commit -F gah -a"
+test_expect_success 'commit message from non-existing file' '
+       echo more bongo: bongo bongo bongo bongo >file &&
+       test_must_fail git commit -F gah -a
+'
 
-# Empty except stray tabs and spaces on a few lines.
-sed -e 's/@$//' >msg <<EOF
-               @
+test_expect_success 'empty commit message' '
+       # Empty except stray tabs and spaces on a few lines.
+       sed -e "s/@//g" >msg <<-\EOF &&
+               @               @
+               @@
+               @  @
+               @Signed-off-by: hula@
+       EOF
+       test_must_fail git commit -F msg -a
+'
 
-  @
-Signed-off-by: hula
-EOF
-test_expect_success \
-       "empty commit message" \
-       "test_must_fail git commit -F msg -a"
+test_expect_success 'setup: commit message from file' '
+       echo this is the commit message, coming from a file >msg &&
+       git commit -F msg -a
+'
 
-test_expect_success \
-       "commit message from file" \
-       "echo 'this is the commit message, coming from a file' >msg && \
-        git commit -F msg -a"
+test_expect_success 'amend commit' '
+       cat >editor <<-\EOF &&
+       #!/bin/sh
+       sed -e "s/a file/an amend commit/g" < "$1" > "$1-"
+       mv "$1-" "$1"
+       EOF
+       chmod 755 editor &&
+       EDITOR=./editor git commit --amend
+'
 
-cat >editor <<\EOF
-#!/bin/sh
-sed -e "s/a file/an amend commit/g" < "$1" > "$1-"
-mv "$1-" "$1"
-EOF
-chmod 755 editor
+test_expect_success 'set up editor' '
+       cat >editor <<-\EOF &&
+       #!/bin/sh
+       sed -e "s/unamended/amended/g" <"$1" >"$1-"
+       mv "$1-" "$1"
+       EOF
+       chmod 755 editor
+'
 
-test_expect_success \
-       "amend commit" \
-       "EDITOR=./editor git commit --amend"
+test_expect_success 'amend without launching editor' '
+       echo unamended >expect &&
+       git commit --allow-empty -m "unamended" &&
+       echo needs more bongo >file &&
+       git add file &&
+       EDITOR=./editor git commit --no-edit --amend &&
+       git diff --exit-code HEAD -- file &&
+       git diff-tree -s --format=%s HEAD >msg &&
+       test_cmp expect msg
+'
 
-test_expect_success \
-       "passing -m and -F" \
-       "echo 'enough with the bongos' >file && \
-        test_must_fail git commit -F msg -m amending ."
+test_expect_success '--amend --edit' '
+       echo amended >expect &&
+       git commit --allow-empty -m "unamended" &&
+       echo bongo again >file &&
+       git add file &&
+       EDITOR=./editor git commit --edit --amend &&
+       git diff-tree -s --format=%s HEAD >msg &&
+       test_cmp expect msg
+'
 
-test_expect_success \
-       "using message from other commit" \
-       "git commit -C HEAD^ ."
+test_expect_success '-m --edit' '
+       echo amended >expect &&
+       git commit --allow-empty -m buffer &&
+       echo bongo bongo >file &&
+       git add file &&
+       EDITOR=./editor git commit -m unamended --edit &&
+       git diff-tree -s  --format=%s HEAD >msg &&
+       test_cmp expect msg
+'
 
-cat >editor <<\EOF
-#!/bin/sh
-sed -e "s/amend/older/g"  < "$1" > "$1-"
-mv "$1-" "$1"
-EOF
-chmod 755 editor
-
-test_expect_success \
-       "editing message from other commit" \
-       "echo 'hula hula' >file && \
-        EDITOR=./editor git commit -c HEAD^ -a"
-
-test_expect_success \
-       "message from stdin" \
-       "echo 'silly new contents' >file && \
-        echo commit message from stdin | git commit -F - -a"
-
-test_expect_success \
-       "overriding author from command line" \
-       "echo 'gak' >file && \
-        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" \
-       "echo 7 | git commit --interactive | grep 'What now'"
-
-test_expect_success PERL \
-       "commit --interactive doesn't change index if editor aborts" \
-       "echo zoo >file &&
+test_expect_success '-m and -F do not mix' '
+       echo enough with the bongos >file &&
+       test_must_fail git commit -F msg -m amending .
+'
+
+test_expect_success 'using message from other commit' '
+       git commit -C HEAD^ .
+'
+
+test_expect_success 'editing message from other commit' '
+       cat >editor <<-\EOF &&
+       #!/bin/sh
+       sed -e "s/amend/older/g"  < "$1" > "$1-"
+       mv "$1-" "$1"
+       EOF
+       chmod 755 editor &&
+       echo hula hula >file &&
+       EDITOR=./editor git commit -c HEAD^ -a
+'
+
+test_expect_success 'message from stdin' '
+       echo silly new contents >file &&
+       echo commit message from stdin |
+       git commit -F - -a
+'
+
+test_expect_success 'overriding author from command line' '
+       echo gak >file &&
+       git commit -m author \
+               --author "Rubber Duck <rduck@convoy.org>" -a >output 2>&1 &&
+       grep Rubber.Duck output
+'
+
+test_expect_success PERL 'interactive add' '
+       echo 7 |
+       git commit --interactive |
+       grep "What now"
+'
+
+test_expect_success PERL "commit --interactive doesn't change index if editor aborts" '
+       echo zoo >file &&
        test_must_fail git diff --exit-code >diff1 &&
-       (echo u ; echo '*' ; echo q) |
-       (EDITOR=: && export EDITOR &&
-        test_must_fail git commit --interactive) &&
+       (echo u ; echo "*" ; echo q) |
+       (
+               EDITOR=: &&
+               export EDITOR &&
+               test_must_fail git commit --interactive
+       ) &&
        git diff >diff2 &&
-       test_cmp diff1 diff2"
-
-test_expect_success \
-       "showing committed revisions" \
-       "git rev-list HEAD >current"
+       compare_diff_patch diff1 diff2
+'
 
-cat >editor <<\EOF
-#!/bin/sh
-sed -e "s/good/bad/g" < "$1" > "$1-"
-mv "$1-" "$1"
-EOF
-chmod 755 editor
-
-cat >msg <<EOF
-A good commit message.
-EOF
-
-test_expect_success \
-       'editor not invoked if -F is given' '
-        echo "moo" >file &&
-        EDITOR=./editor git commit -a -F msg &&
-        git show -s --pretty=format:"%s" | grep -q good &&
-        echo "quack" >file &&
-        echo "Another good message." | EDITOR=./editor git commit -a -F - &&
-        git show -s --pretty=format:"%s" | grep -q good
-        '
-# We could just check the head sha1, but checking each commit makes it
-# easier to isolate bugs.
-
-cat >expected <<\EOF
-72c0dc9855b0c9dadcbfd5a31cab072e0cb774ca
-9b88fc14ce6b32e3d9ee021531a54f18a5cf38a2
-3536bbb352c3a1ef9a420f5b4242d48578b92aa7
-d381ac431806e53f3dd7ac2f1ae0534f36d738b9
-4fd44095ad6334f3ef72e4c5ec8ddf108174b54a
-402702b49136e7587daa9280e91e4bb7cb2179f7
-EOF
-
-test_expect_success \
-    'validate git rev-list output.' \
-    'test_cmp expected current'
+test_expect_success 'editor not invoked if -F is given' '
+       cat >editor <<-\EOF &&
+       #!/bin/sh
+       sed -e s/good/bad/g <"$1" >"$1-"
+       mv "$1-" "$1"
+       EOF
+       chmod 755 editor &&
+
+       echo A good commit message. >msg &&
+       echo moo >file &&
+
+       EDITOR=./editor git commit -a -F msg &&
+       git show -s --pretty=format:%s >subject &&
+       grep -q good subject &&
+
+       echo quack >file &&
+       echo Another good message. |
+       EDITOR=./editor git commit -a -F - &&
+       git show -s --pretty=format:%s >subject &&
+       grep -q good subject
+'
 
 test_expect_success 'partial commit that involves removal (1)' '
 
@@ -216,7 +240,6 @@ test_expect_success 'partial commit that involves removal (3)' '
 
 '
 
-author="The Real Author <someguy@his.email.org>"
 test_expect_success 'amend commit to fix author' '
 
        oldtick=$GIT_AUTHOR_DATE &&
@@ -345,7 +368,6 @@ test_expect_success 'multiple -m' '
 
 '
 
-author="The Real Author <someguy@his.email.org>"
 test_expect_success 'amend commit to fix author' '
 
        oldtick=$GIT_AUTHOR_DATE &&
@@ -372,15 +394,8 @@ test_expect_success 'git commit <file> with dirty index' '
 
 test_expect_success 'same tree (single parent)' '
 
-       git reset --hard
-
-       if git commit -m empty
-       then
-               echo oops -- should have complained
-               false
-       else
-               : happy
-       fi
+       git reset --hard &&
+       test_must_fail git commit -m empty
 
 '
 
index 463254c72734beaf74948b6c424367ef4fea9d1a..83acf68bc3c19770eb690ece139892ca75329216 100755 (executable)
@@ -505,9 +505,63 @@ test_expect_success 'verify that non-notes are untouched by a fanout change' '
        test_cmp expect_non-note3 actual
 
 '
+
+# Change the notes for the three top commits
+test_tick
+cat >input <<INPUT_END
+commit refs/notes/many_notes
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+changing notes for the top three commits
+COMMIT
+from refs/notes/many_notes^0
+INPUT_END
+
+rm expect
+i=$num_commits
+j=0
+while test $j -lt 3
+do
+       cat >>input <<INPUT_END
+N inline refs/heads/many_commits~$j
+data <<EOF
+changed note for commit #$i
+EOF
+INPUT_END
+       cat >>expect <<EXPECT_END
+    commit #$i
+    changed note for commit #$i
+EXPECT_END
+       i=$(($i - 1))
+       j=$(($j + 1))
+done
+
+test_expect_success 'change a few existing notes' '
+
+       git fast-import <input &&
+       GIT_NOTES_REF=refs/notes/many_notes git log -n3 refs/heads/many_commits |
+           grep "^    " > actual &&
+       test_cmp expect actual
+
+'
+
+test_expect_success 'verify that changing notes respect existing fanout' '
+
+       # 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
+
+'
+
 remaining_notes=10
 test_tick
-cat >>input <<INPUT_END
+cat >input <<INPUT_END
 commit refs/notes/many_notes
 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 data <<COMMIT
@@ -516,12 +570,11 @@ COMMIT
 from refs/notes/many_notes^0
 INPUT_END
 
-i=$remaining_notes
-while test $i -lt $num_commits
+i=$(($num_commits - $remaining_notes))
+for sha1 in $(git rev-list -n $i refs/heads/many_commits)
 do
-       i=$(($i + 1))
        cat >>input <<INPUT_END
-N 0000000000000000000000000000000000000000 :$i
+N 0000000000000000000000000000000000000000 $sha1
 INPUT_END
 done
 
index 53297156a314a5b5e03385a3ad887eff959359c4..ab249178123653943bb087ccc40f2b420cc1e67c 100755 (executable)
@@ -273,6 +273,53 @@ test_expect_success \
        'commitdiff(2): directory becomes symlink' \
        'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=foo-symlinked-to-bar"'
 
+# ----------------------------------------------------------------------
+# commitdiff testing (incomplete lines)
+
+test_expect_success 'setup incomplete lines' '
+       cat >file<<-\EOF &&
+       Dominus regit me,
+       et nihil mihi deerit.
+       In loco pascuae ibi me collocavit,
+       super aquam refectionis educavit me;
+       animam meam convertit,
+       deduxit me super semitas jusitiae,
+       propter nomen suum.
+       CHANGE_ME
+       EOF
+       git commit -a -m "Preparing for incomplete lines" &&
+       echo "incomplete" | tr -d "\\012" >>file &&
+       git commit -a -m "Add incomplete line" &&
+       git tag incomplete_lines_add &&
+       sed -e s/CHANGE_ME/change_me/ <file >file+ &&
+       mv -f file+ file &&
+       git commit -a -m "Incomplete context line" &&
+       git tag incomplete_lines_ctx &&
+       echo "Dominus regit me," >file &&
+       echo "incomplete line" | tr -d "\\012" >>file &&
+       git commit -a -m "Change incomplete line" &&
+       git tag incomplete_lines_chg
+       echo "Dominus regit me," >file &&
+       git commit -a -m "Remove incomplete line" &&
+       git tag incomplete_lines_rem
+'
+
+test_expect_success 'commitdiff(1): addition of incomplete line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_add"
+'
+
+test_expect_success 'commitdiff(1): incomplete line as context line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_ctx"
+'
+
+test_expect_success 'commitdiff(1): change incomplete line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_chg"
+'
+
+test_expect_success 'commitdiff(1): removal of incomplete line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_rem"
+'
+
 # ----------------------------------------------------------------------
 # commit, commitdiff: merge, large
 test_expect_success \
@@ -282,7 +329,8 @@ test_expect_success \
         git add b &&
         git commit -a -m "On branch" &&
         git checkout master &&
-        git pull . b'
+        git pull . b &&
+        git tag merge_commit'
 
 test_expect_success \
        'commit(0): merge commit' \
@@ -331,6 +379,29 @@ test_expect_success \
        'commitdiff(1): large commit' \
        'gitweb_run "p=.git;a=commitdiff;h=b"'
 
+# ----------------------------------------------------------------------
+# side-by-side diff
+
+test_expect_success 'side-by-side: addition of incomplete line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_add;ds=sidebyside"
+'
+
+test_expect_success 'side-by-side: incomplete line as context line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_ctx;ds=sidebyside"
+'
+
+test_expect_success 'side-by-side: changed incomplete line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_chg;ds=sidebyside"
+'
+
+test_expect_success 'side-by-side: removal of incomplete line' '
+       gitweb_run "p=.git;a=commitdiff;h=incomplete_lines_rem;ds=sidebyside"
+'
+
+test_expect_success 'side-by-side: merge commit' '
+       gitweb_run "p=.git;a=commitdiff;h=merge_commit;ds=sidebyside"
+'
+
 # ----------------------------------------------------------------------
 # tags testing
 
index 734ccf2fb9f3b01c1dffdd6c4858320d634cae03..df929e05558bbe84d78a35cedc273cb77b2d2c29 100755 (executable)
@@ -38,7 +38,7 @@ test_expect_success 'no config, unedited, say no' '
                cd "$git" &&
                echo line >>file1 &&
                git commit -a -m "change 3 (not really)" &&
-               printf "bad response\nn\n" | "$GITP4" submit
+               printf "bad response\nn\n" | "$GITP4" submit &&
                p4 changes //depot/... >wc &&
                test_line_count = 2 wc
        )
@@ -74,6 +74,28 @@ test_expect_success 'skipSubmitEditCheck' '
        )
 '
 
+# check the normal case, where the template really is edited
+test_expect_success 'no config, edited' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       ed="$TRASH_DIRECTORY/ed.sh" &&
+       test_when_finished "rm \"$ed\"" &&
+       cat >"$ed" <<-EOF &&
+               #!$SHELL_PATH
+               sleep 1
+               touch "\$1"
+               exit 0
+       EOF
+       chmod 755 "$ed" &&
+       (
+               cd "$git" &&
+               echo line >>file1 &&
+               git commit -a -m "change 5" &&
+               EDITOR="\"$ed\"" "$GITP4" submit &&
+               p4 changes //depot/... >wc &&
+               test_line_count = 5 wc
+       )
+'
 
 test_expect_success 'kill p4d' '
        kill_p4d
diff --git a/t/t9807-submit.sh b/t/t9807-submit.sh
new file mode 100755 (executable)
index 0000000..2cb724e
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+test_description='git-p4 submit'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "change 1"
+       )
+'
+
+test_expect_success 'submit with no client dir' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo file2 >file2 &&
+               git add file2 &&
+               git commit -m "git commit 2" &&
+               rm -rf "$cli" &&
+               git config git-p4.skipSubmitEdit true &&
+               "$GITP4" submit
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9808-chdir.sh b/t/t9808-chdir.sh
new file mode 100755 (executable)
index 0000000..eb8cc95
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description='git-p4 relative chdir'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "change 1"
+       )
+'
+
+# P4 reads from P4CONFIG file to find its server params, if the
+# environment variable is set
+test_expect_success 'P4CONFIG and absolute dir clone' '
+       printf "P4PORT=$P4PORT\nP4CLIENT=$P4CLIENT\n" >p4config &&
+       test_when_finished "rm \"$TRASH_DIRECTORY/p4config\"" &&
+       test_when_finished cleanup_git &&
+       (
+               P4CONFIG=p4config && export P4CONFIG &&
+               unset P4PORT P4CLIENT &&
+               "$GITP4" clone --verbose --dest="$git" //depot
+       )
+'
+
+# same thing, but with relative directory name, note missing $ on --dest
+test_expect_success 'P4CONFIG and relative dir clone' '
+       printf "P4PORT=$P4PORT\nP4CLIENT=$P4CLIENT\n" >p4config &&
+       test_when_finished "rm \"$TRASH_DIRECTORY/p4config\"" &&
+       test_when_finished cleanup_git &&
+       (
+               P4CONFIG=p4config && export P4CONFIG &&
+               unset P4PORT P4CLIENT &&
+               "$GITP4" clone --verbose --dest="git" //depot
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
index bdd9513b84301275330d3dd7e49af05081ef9cd7..a65dfc7ea93b3ec95e6ab7cd5b758029fe599d22 100644 (file)
@@ -44,6 +44,7 @@ export LANG LC_ALL PAGER TERM TZ
 EDITOR=:
 unset VISUAL
 unset EMAIL
+unset LANGUAGE
 unset $(perl -e '
        my @env = keys %ENV;
        my $ok = join("|", qw(
@@ -191,6 +192,7 @@ then
 fi
 
 exec 5>&1
+exec 6<&0
 if test "$verbose" = "t"
 then
        exec 4>&2 3>&1
@@ -379,6 +381,11 @@ test_config () {
        git config "$@"
 }
 
+test_config_global () {
+       test_when_finished "test_unconfig --global '$1'" &&
+       git config --global "$@"
+}
+
 # Use test_set_prereq to tell that a particular prerequisite is available.
 # The prerequisite can later be checked for in two ways:
 #
@@ -469,7 +476,7 @@ test_debug () {
 test_eval_ () {
        # This is a separate function because some tests use
        # "return" to end a test_expect_success block early.
-       eval >&3 2>&4 "$*"
+       eval </dev/null >&3 2>&4 "$*"
 }
 
 test_run_ () {
@@ -1113,12 +1120,14 @@ esac
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PYTHON" && test_set_prereq PYTHON
 test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE
+test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
 
 # Can we rely on git's output in the C locale?
 if test -n "$GETTEXT_POISON"
 then
        GIT_GETTEXT_POISON=YesPlease
        export GIT_GETTEXT_POISON
+       test_set_prereq GETTEXT_POISON
 else
        test_set_prereq C_LOCALE_OUTPUT
 fi
diff --git a/test-credential.c b/test-credential.c
new file mode 100644 (file)
index 0000000..dee200e
--- /dev/null
@@ -0,0 +1,38 @@
+#include "cache.h"
+#include "credential.h"
+#include "string-list.h"
+
+static const char usage_msg[] =
+"test-credential <fill|approve|reject> [helper...]";
+
+int main(int argc, const char **argv)
+{
+       const char *op;
+       struct credential c = CREDENTIAL_INIT;
+       int i;
+
+       op = argv[1];
+       if (!op)
+               usage(usage_msg);
+       for (i = 2; i < argc; i++)
+               string_list_append(&c.helpers, argv[i]);
+
+       if (credential_read(&c, stdin) < 0)
+               die("unable to read credential from stdin");
+
+       if (!strcmp(op, "fill")) {
+               credential_fill(&c);
+               if (c.username)
+                       printf("username=%s\n", c.username);
+               if (c.password)
+                       printf("password=%s\n", c.password);
+       }
+       else if (!strcmp(op, "approve"))
+               credential_approve(&c);
+       else if (!strcmp(op, "reject"))
+               credential_reject(&c);
+       else
+               usage(usage_msg);
+
+       return 0;
+}
index 1f73f1ea7dfa6a14dedf384c99751e86c8121ff4..e6c292385f9492ab8a58a693e854025a11b9b045 100644 (file)
@@ -59,6 +59,6 @@ int main(int ac, char **av)
        struct cache_tree *another = cache_tree();
        if (read_cache() < 0)
                die("unable to read index file");
-       cache_tree_update(another, active_cache, active_nr, 0, 1);
+       cache_tree_update(another, active_cache, active_nr, 0, 1, 0);
        return dump_cache_tree(active_cache_tree, another, "");
 }
diff --git a/test-scrap-cache-tree.c b/test-scrap-cache-tree.c
new file mode 100644 (file)
index 0000000..4728013
--- /dev/null
@@ -0,0 +1,17 @@
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+static struct lock_file index_lock;
+
+int main(int ac, char **av)
+{
+       int fd = hold_locked_index(&index_lock, 1);
+       if (read_cache() < 0)
+               die("unable to read index file");
+       active_cache_tree = NULL;
+       if (write_cache(fd, active_cache, active_nr)
+           || commit_lock_file(&index_lock))
+               die("unable to write index file");
+       return 0;
+}
index 51814b5da3064b617fa268a6b2bc6b3d6cb0b110..63f38e3940516d9fd39371b4072ee296100307a7 100644 (file)
@@ -163,7 +163,7 @@ static void set_upstreams(struct transport *transport, struct ref *refs,
                /* Follow symbolic refs (mainly for HEAD). */
                localname = ref->peer_ref->name;
                remotename = ref->name;
-               tmp = resolve_ref(localname, sha, 1, &flag);
+               tmp = resolve_ref_unsafe(localname, sha, 1, &flag);
                if (tmp && flag & REF_ISSYMREF &&
                        !prefixcmp(tmp, "refs/heads/"))
                        localname = tmp;
@@ -502,7 +502,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
        struct ref *refs;
 
        connect_setup(transport, for_push, 0);
-       get_remote_heads(data->fd[0], &refs, 0, NULL,
+       get_remote_heads(data->fd[0], &refs,
                         for_push ? REF_NORMAL : 0, &data->extra_have);
        data->got_remote_heads = 1;
 
@@ -537,7 +537,7 @@ static int fetch_refs_via_pack(struct transport *transport,
 
        if (!data->got_remote_heads) {
                connect_setup(transport, 0, 0);
-               get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);
+               get_remote_heads(data->fd[0], &refs_tmp, 0, NULL);
                data->got_remote_heads = 1;
        }
 
@@ -772,8 +772,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
                struct ref *tmp_refs;
                connect_setup(transport, 1, 0);
 
-               get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL,
-                                NULL);
+               get_remote_heads(data->fd[0], &tmp_refs, REF_NORMAL, NULL);
                data->got_remote_heads = 1;
        }
 
index 7a51d091b5a6babed91f60b9ff7079308afa4e6f..28ad6db9ffa854c3ef9185ba85108e95edc44d51 100644 (file)
@@ -209,6 +209,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diff_opts.single_follow = opt->pathspec.raw[0];
        diff_opts.break_opt = opt->break_opt;
+       diff_opts.rename_score = opt->rename_score;
        paths[0] = NULL;
        diff_tree_setup_paths(paths, &diff_opts);
        if (diff_setup_done(&diff_opts) < 0)
diff --git a/unix-socket.c b/unix-socket.c
new file mode 100644 (file)
index 0000000..84b1509
--- /dev/null
@@ -0,0 +1,56 @@
+#include "cache.h"
+#include "unix-socket.h"
+
+static int unix_stream_socket(void)
+{
+       int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+       if (fd < 0)
+               die_errno("unable to create socket");
+       return fd;
+}
+
+static void unix_sockaddr_init(struct sockaddr_un *sa, const char *path)
+{
+       int size = strlen(path) + 1;
+       if (size > sizeof(sa->sun_path))
+               die("socket path is too long to fit in sockaddr");
+       memset(sa, 0, sizeof(*sa));
+       sa->sun_family = AF_UNIX;
+       memcpy(sa->sun_path, path, size);
+}
+
+int unix_stream_connect(const char *path)
+{
+       int fd;
+       struct sockaddr_un sa;
+
+       unix_sockaddr_init(&sa, path);
+       fd = unix_stream_socket();
+       if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
+               close(fd);
+               return -1;
+       }
+       return fd;
+}
+
+int unix_stream_listen(const char *path)
+{
+       int fd;
+       struct sockaddr_un sa;
+
+       unix_sockaddr_init(&sa, path);
+       fd = unix_stream_socket();
+
+       unlink(path);
+       if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
+               close(fd);
+               return -1;
+       }
+
+       if (listen(fd, 5) < 0) {
+               close(fd);
+               return -1;
+       }
+
+       return fd;
+}
diff --git a/unix-socket.h b/unix-socket.h
new file mode 100644 (file)
index 0000000..e271aee
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef UNIX_SOCKET_H
+#define UNIX_SOCKET_H
+
+int unix_stream_connect(const char *path);
+int unix_stream_listen(const char *path);
+
+#endif /* UNIX_SOCKET_H */
index 470cffd7c14a9f28010423a44327084219598a35..6f36f6255c3f4e3db4cdc7ca9b9dcca56846c72d 100644 (file)
@@ -784,6 +784,8 @@ int main(int argc, char **argv)
        int i;
        int strict = 0;
 
+       git_setup_gettext();
+
        packet_trace_identity("upload-pack");
        git_extract_argv0_path(argv[0]);
        read_replace_refs = 0;
index 7c983c14ff2f4bc2aac6e632ccf982ea9589c6e3..76109da4bcb7abc26ed20508692051a40e8addd7 100644 (file)
@@ -118,7 +118,7 @@ PATTERNS("cpp",
         /* Jump targets or access declarations */
         "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n"
         /* C/++ functions/methods at top level */
-        "^([A-Za-z_][A-Za-z_0-9]*([ \t]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n"
+        "^([A-Za-z_][A-Za-z_0-9]*([ \t*]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n"
         /* compound type at top level */
         "^((struct|class|enum)[^;]*)$",
         /* -- */
index 09feb1f7373367866668f3ac7c121e49bdde8096..53a8dd0a3f9a123eae16415d6bcd50b1fbcba6a4 100644 (file)
@@ -15,7 +15,8 @@ else
        export GIT_TEMPLATE_DIR
 fi
 GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib'
+GIT_TEXTDOMAINDIR='@@BUILD_DIR@@/po/build/locale'
 PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH"
-export GIT_EXEC_PATH GITPERLLIB PATH
+export GIT_EXEC_PATH GITPERLLIB PATH GIT_TEXTDOMAINDIR
 
 exec "${GIT_EXEC_PATH}/@@PROG@@" "$@"
index 70fdb76ff2b5100c6d20f9f3b758e1ac8df314e1..9ffc535f1ab0296fd0a3330b1f136d69f9ac9bb2 100644 (file)
@@ -111,7 +111,6 @@ void status_printf_more(struct wt_status *s, const char *color,
 void wt_status_prepare(struct wt_status *s)
 {
        unsigned char sha1[20];
-       const char *head;
 
        memset(s, 0, sizeof(*s));
        memcpy(s->color_palette, default_wt_status_colors,
@@ -119,8 +118,7 @@ void wt_status_prepare(struct wt_status *s)
        s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
        s->use_color = -1;
        s->relative_paths = 1;
-       head = resolve_ref("HEAD", sha1, 0, NULL);
-       s->branch = head ? xstrdup(head) : NULL;
+       s->branch = resolve_refdup("HEAD", sha1, 0, NULL);
        s->reference = "HEAD";
        s->fp = stdout;
        s->index_file = get_index_file();
diff --git a/zlib.c b/zlib.c
index 3c63d480c75e9939fb3a047f595b032e9509d681..2b2c0c780e3fca6217c688298053a01aad724505 100644 (file)
--- a/zlib.c
+++ b/zlib.c
@@ -188,13 +188,20 @@ void git_deflate_init_gzip(git_zstream *strm, int level)
            strm->z.msg ? strm->z.msg : "no message");
 }
 
-void git_deflate_end(git_zstream *strm)
+int git_deflate_abort(git_zstream *strm)
 {
        int status;
 
        zlib_pre_call(strm);
        status = deflateEnd(&strm->z);
        zlib_post_call(strm);
+       return status;
+}
+
+void git_deflate_end(git_zstream *strm)
+{
+       int status = git_deflate_abort(strm);
+
        if (status == Z_OK)
                return;
        error("deflateEnd: %s (%s)", zerr_to_string(status),