{"thread":{"id":"24312","subject":"[PATCH] rebase -i: use 'read -r' to avoid backslash acting as an escape character","startedAt":"2010-07-06T22:55:22Z","lastAt":"2010-07-09T16:30:43Z","messageCount":16,"participants":["Miklos Vajna","Junio C Hamano","Santi Béjar","Jonathan Nieder","Andreas Schwab","Sverre Rabbelier","Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"144967","messageId":"20100706225522.GA31048@genesis.frugalware.org","threadId":"24312","inReplyTo":null,"subject":"[PATCH] rebase -i: use 'read -r' to avoid backslash acting as an escape character","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2010-07-06T22:55:22Z","receivedAt":"2010-07-06T22:55:22Z","isPatch":true,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"The last backslash in the commit message will make 'read' read two lines\nwithout '-r', loosing the next commit, so use it.\n\nSigned-off-by: Miklos Vajna <vmiklos@frugalware.org>\n---\n git-rebase--interactive.sh    |    4 ++--\n t/t3404-rebase-interactive.sh |   14 ++++++++++++++\n 2 files changed, 16 insertions(+), 2 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 6b86abc..4b822e3 100755\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -591,7 +591,7 @@ do_rest () {\n # skip picking commits whose parents are unchanged\n skip_unnecessary_picks () {\n \tfd=3\n-\twhile read command sha1 rest\n+\twhile read -r command sha1 rest\n \tdo\n \t\t# fd=3 means we skip the command\n \t\tcase \"$fd,$command,$(git rev-parse --verify --quiet $sha1^)\" in\n@@ -890,7 +890,7 @@ first and then run 'git rebase --continue' again.\"\n \t\tgit rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \\\n \t\t\t--abbrev=7 --reverse --left-right --topo-order \\\n \t\t\t$REVISIONS | \\\n-\t\t\tsed -n \"s/^>//p\" | while read shortsha1 rest\n+\t\t\tsed -n \"s/^>//p\" | while read -r shortsha1 rest\n \t\tdo\n \t\t\tif test t != \"$PRESERVE_MERGES\"\n \t\t\tthen\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ee9a1b2..ddfa790 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -630,4 +630,18 @@ test_expect_success 'always cherry-pick with --no-ff' '\n \ttest_cmp empty out\n '\n \n+test_expect_success 'commit message with backslash at the end' '\n+\techo a > file &&\n+\tgit add file &&\n+\tgit commit -m 1st &&\n+\techo b >> file &&\n+\tgit commit -a -m \"escape \\\\t, \\\\{, \\\\} and \\\\\" &&\n+\techo c >> file &&\n+\tgit commit -a -m 3rd &&\n+\torig=$(git rev-parse HEAD) &&\n+\tgit rebase -i HEAD~2 &&\n+\tgit diff $orig HEAD > out &&\n+\ttest_cmp empty out\n+'\n+\n test_done\n-- \n1.7.1\n"},{"id":"144997","messageId":"7vpqyz278o.fsf@alter.siamese.dyndns.org","threadId":"24312","inReplyTo":"20100706225522.GA31048@genesis.frugalware.org","subject":"Re: [PATCH] rebase -i: use 'read -r' to avoid backslash acting as an escape character","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-07T05:25:43Z","receivedAt":"2010-07-07T05:25:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Why do I feel a deja-vu for this patch?\n"},{"id":"145010","messageId":"20100707091633.GB31048@genesis.frugalware.org","threadId":"24312","inReplyTo":"7vpqyz278o.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] rebase -i: use 'read -r' to avoid backslash acting as an escape character","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2010-07-07T09:16:33Z","receivedAt":"2010-07-07T09:16:33Z","isPatch":true,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"On Tue, Jul 06, 2010 at 10:25:43PM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> Why do I feel a deja-vu for this patch?\n> \n\nMaybe this one:\n\nhttp://thread.gmane.org/gmane.comp.version-control.git/146999\n\nBut the sed hack used there isn't a clean solution, IMHO a more elegant\nsolution is to use read -r, what my patch does.\n"},{"id":"145011","messageId":"AANLkTinFu08juNtz5eYyjzxuW6zbyGyn5f4S3wh0TI0E@mail.gmail.com","threadId":"24312","inReplyTo":"20100707091633.GB31048@genesis.frugalware.org","subject":"Re: [PATCH] rebase -i: use 'read -r' to avoid backslash acting as an escape character","fromName":"Santi Béjar","fromEmail":"santi@agolina.net","sentAt":"2010-07-07T09:22:29Z","receivedAt":"2010-07-07T09:22:29Z","isPatch":true,"sender":{"key":"santi@agolina.net","avatar":null},"body":"On Wed, Jul 7, 2010 at 11:16 AM, Miklos Vajna <vmiklos@frugalware.org> wrote:\n> On Tue, Jul 06, 2010 at 10:25:43PM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n>> Why do I feel a deja-vu for this patch?\n>>\n>\n> Maybe this one:\n>\n> http://thread.gmane.org/gmane.comp.version-control.git/146999\n\nNo. This one:\n\nhttp://article.gmane.org/gmane.comp.version-control.git/150328\n\n:-)\n\nSanti\n"},{"id":"145012","messageId":"20100707094620.GC31048@genesis.frugalware.org","threadId":"24312","inReplyTo":"AANLkTinFu08juNtz5eYyjzxuW6zbyGyn5f4S3wh0TI0E@mail.gmail.com","subject":"Re: [PATCH] rebase -i: use 'read -r' to avoid backslash acting as an escape character","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2010-07-07T09:46:20Z","receivedAt":"2010-07-07T09:46:20Z","isPatch":true,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"On Wed, Jul 07, 2010 at 11:22:29AM +0200, Santi Béjar <santi@agolina.net> wrote:\n> No. This one:\n> \n> http://article.gmane.org/gmane.comp.version-control.git/150328\n> \n> :-)\n\nHeh, then sorry for the noise. I noticed the problem on 2th, added the\n\"I should fix this\" to my TODO, then made the patch yesterday. I forgot\nto check the list archive again before sending the patch. ;-)\n"},{"id":"145070","messageId":"7v7hl6stna.fsf@alter.siamese.dyndns.org","threadId":"24312","inReplyTo":"20100707094620.GC31048@genesis.frugalware.org","subject":"Re: [PATCH] rebase -i: use 'read -r' to avoid backslash acting as an escape character","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-08T00:29:29Z","receivedAt":"2010-07-08T00:29:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Miklos Vajna <vmiklos@frugalware.org> writes:\n\n> On Wed, Jul 07, 2010 at 11:22:29AM +0200, Santi Béjar <santi@agolina.net> wrote:\n>> No. This one:\n>> \n>> http://article.gmane.org/gmane.comp.version-control.git/150328\n>> \n>> :-)\n>\n> Heh, then sorry for the noise. I noticed the problem on 2th, added the\n> \"I should fix this\" to my TODO, then made the patch yesterday. I forgot\n> to check the list archive again before sending the patch. ;-)\n\nThanks anyway.  It may have been a bit of duplicated effort, but served as\na sanity check as well.\n\nOur patches do seem to fix the reading side of the issue, but I am not\nsure if the writing side needs further fix.  If I recall, the script had\nliberal use of \"echo $var\"; some implementations of echo interprets\nbackslash sequence without being asked with an explicit \"-e\".\n"},{"id":"145108","messageId":"20100708103552.GF31048@genesis.frugalware.org","threadId":"24312","inReplyTo":"7v7hl6stna.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] rebase -i: use 'read -r' to avoid backslash acting as an escape character","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2010-07-08T10:35:52Z","receivedAt":"2010-07-08T10:35:52Z","isPatch":true,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"On Wed, Jul 07, 2010 at 05:29:29PM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n> Our patches do seem to fix the reading side of the issue, but I am not\n> sure if the writing side needs further fix.  If I recall, the script had\n> liberal use of \"echo $var\"; some implementations of echo interprets\n> backslash sequence without being asked with an explicit \"-e\".\n\nPossibly, I just have the bash builtin and the coreutils echo here, both\nare writing \\t properly without -e.\n"},{"id":"145138","messageId":"20100708171418.GA18229@burratino","threadId":"24312","inReplyTo":"20100708103552.GF31048@genesis.frugalware.org","subject":"[RFC] war on echo in scripts","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2010-07-08T17:14:18Z","receivedAt":"2010-07-08T17:14:18Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Some Posix-style shells (e.g., ash and dash) always interpret\nescape sequences with echo, even when not asked with -e.  This\ncan cause problems when printing arbitrary user-specified strings\n(e.g., commit messages with '\\n' in them); it is much safer to\nuse printf \"%s\\n\".\n\nThis patch changes uses of echo to use printf where they seem\nlikely to cause a problem.  Safe uses of echo are left un-converted\nto avoid unnecessary code churn.\n\nA few hyphens in git command names snuck away in the process.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nMiklos Vajna wrote:\n> On Wed, Jul 07, 2010 at 05:29:29PM -0700, Junio C Hamano <gitster@pobox.com> wrote:\n\n>> If I recall, the script had\n>> liberal use of \"echo $var\"; some implementations of echo interprets\n>> backslash sequence without being asked with an explicit \"-e\".\n>\n> Possibly, I just have the bash builtin and the coreutils echo here, both\n> are writing \\t properly without -e.\n\n‘dash’ might be good for testing.\n\nSpeaking of testing, the following is just a demonstration, not even\ntested as far as \"sh -n\".\n\nPatch applies to maint.  A series meant for application would probably\nconsist of more than one patch: first fix die() in git-sh-setup, then\nthe known problem in rebase --interactive, then the rest which is\nharder to test.\n\nThoughts?\n\n contrib/ciabot/ciabot.sh                   |    8 +++---\n contrib/examples/git-clone.sh              |   14 ++++++------\n contrib/examples/git-fetch.sh              |   12 +++++-----\n contrib/examples/git-merge.sh              |    7 +----\n contrib/examples/git-notes.sh              |    2 +-\n contrib/examples/git-resolve.sh            |    2 +-\n contrib/examples/git-revert.sh             |   10 ++++----\n contrib/fast-import/git-import.sh          |    4 +-\n contrib/git-resurrect.sh                   |    4 +-\n contrib/hooks/post-receive-email           |    4 +-\n contrib/remotes2config.sh                  |    2 +-\n contrib/thunderbird-patch-inline/appp.sh   |   24 ++++++++++----------\n contrib/workdir/git-new-workdir            |    4 +-\n git-am.sh                                  |   10 ++++----\n git-bisect.sh                              |    8 +++---\n git-filter-branch.sh                       |   15 ++++++-------\n git-instaweb.sh                            |   26 ++++++++++++----------\n git-merge-octopus.sh                       |   10 ++++----\n git-merge-one-file.sh                      |   24 ++++++++++----------\n git-mergetool--lib.sh                      |   31 ++++++++++++++-------------\n git-mergetool.sh                           |   16 +++++++-------\n git-parse-remote.sh                        |    4 +-\n git-quiltimport.sh                         |   26 ++++++++++++++---------\n git-rebase--interactive.sh                 |   22 ++++++++++----------\n git-rebase.sh                              |   24 ++++++++++----------\n git-repack.sh                              |    2 +-\n git-request-pull.sh                        |    9 ++-----\n git-sh-setup.sh                            |   10 ++++----\n git-stash.sh                               |    6 ++--\n git-submodule.sh                           |   24 ++++++++++++---------\n git-web--browse.sh                         |    6 ++--\n templates/hooks--commit-msg.sample         |    2 +-\n templates/hooks--prepare-commit-msg.sample |    2 +-\n test-sha1.sh                               |   14 ++++++------\n 34 files changed, 197 insertions(+), 191 deletions(-)\n\ndiff --git a/contrib/ciabot/ciabot.sh b/contrib/ciabot/ciabot.sh\nindex eb87bba..25961fd 100755\n--- a/contrib/ciabot/ciabot.sh\n+++ b/contrib/ciabot/ciabot.sh\n@@ -144,10 +144,10 @@ rev=$(git describe ${merged} 2>/dev/null)\n # for spammers' address harvesters - getting this wrong\n # would make the freenode #commits channel into harvester heaven.\n rawcommit=$(git cat-file commit ${merged})\n-author=$(echo \"$rawcommit\" | sed -n -e '/^author .*<\\([^@]*\\).*$/s--\\1-p')\n-logmessage=$(echo \"$rawcommit\" | sed -e '1,/^$/d' | head -n 1)\n-logmessage=$(echo \"$logmessage\" | sed 's/\\&/&amp\\;/g; s/</&lt\\;/g; s/>/&gt\\;/g')\n-ts=$(echo \"$rawcommit\" | sed -n -e '/^author .*> \\([0-9]\\+\\).*$/s--\\1-p')\n+author=$(printf \"%s\\n\" \"$rawcommit\" | sed -n -e '/^author .*<\\([^@]*\\).*$/s--\\1-p')\n+logmessage=$(printf \"%s\\n\" \"$rawcommit\" | sed -e '1,/^$/d' | head -n 1)\n+logmessage=$(printf \"%s\\n\" \"$logmessage\" | sed 's/\\&/&amp\\;/g; s/</&lt\\;/g; s/>/&gt\\;/g')\n+ts=$(printf \"%s\\n\" \"$rawcommit\" | sed -n -e '/^author .*> \\([0-9]\\+\\).*$/s--\\1-p')\n files=$(git diff-tree -r --name-only ${merged} | sed -e '1d' -e 's-.*-<file>&</file>-')\n \n out=\"\ndiff --git a/contrib/examples/git-clone.sh b/contrib/examples/git-clone.sh\nindex 547228e..e85ccda 100755\n--- a/contrib/examples/git-clone.sh\n+++ b/contrib/examples/git-clone.sh\n@@ -28,7 +28,7 @@ use-separate-remote  compatibility, do not use\n no-separate-remote   compatibility, do not use\"\n \n die() {\n-\techo >&2 \"$@\"\n+\tprintf >&2 '%s\\n' \"$@\"\n \texit 1\n }\n \n@@ -226,9 +226,9 @@ else\n \tif test -f \"$repo\"\n \tthen\n \t\t# Cloning from a bundle\n-\t\tdir=$(echo \"$repo\" | sed -e 's|/*\\.bundle$||' -e 's|.*/||g')\n+\t\tdir=$(printf '%s\\n' \"$repo\" | sed -e 's|/*\\.bundle$||' -e 's|.*/||g')\n \telse\n-\t\tdir=$(echo \"$repo\" |\n+\t\tdir=$(printf '%s\\n' \"$repo\" |\n \t\t\tsed -e 's|/$||' -e 's|:*/*\\.git$||' -e 's|.*[/:]||g')\n \tfi\n fi\n@@ -280,7 +280,7 @@ then\n \tif test -n \"$ref_git\"\n \tthen\n \t\tref_git=$(cd \"$ref_git\" && pwd)\n-\t\techo \"$ref_git/objects\" >\"$GIT_DIR/objects/info/alternates\"\n+\t\tprintf '%s/objects\\n' \"$ref_git\" >\"$GIT_DIR/objects/info/alternates\"\n \t\t(\n \t\t\tGIT_DIR=\"$ref_git\" git for-each-ref \\\n \t\t\t\t--format='%(objectname) %(*objectname)'\n@@ -308,7 +308,7 @@ yes)\n \tif test \"$local_shared\" = yes\n \tthen\n \t\tmkdir -p \"$GIT_DIR/objects/info\"\n-\t\techo \"$repo/objects\" >>\"$GIT_DIR/objects/info/alternates\"\n+\t\tprintf '%s/objects\\n' \"$repo\" >>\"$GIT_DIR/objects/info/alternates\"\n \telse\n \t\tcpio_quiet_flag=\"\"\n \t\tcpio --help 2>&1 | grep -- --quiet >/dev/null && \\\n@@ -329,7 +329,7 @@ yes)\n \t\t\t\tl=l\n \t\t\telif test -n \"$local_explicitly_asked_for\"\n \t\t\tthen\n-\t\t\t\techo >&2 \"Warning: -l asked but cannot hardlink to $repo\"\n+\t\t\t\tprintf >&2 'Warning: -l asked but cannot hardlink to %s\\n' \"$repo\"\n \t\t\tfi\n \t\tfi &&\n \t\tcd \"$repo\" &&\n@@ -366,7 +366,7 @@ yes)\n \t\t    do\n \t\t\tcase \"$alt\" in 'bad alternate: '*) die \"$alt\";; esac\n \t\t\tcase \"$quiet\" in\n-\t\t\t'')\techo >&2 \"Getting alternate: $alt\" ;;\n+\t\t\t'')\tprintf >&2 'Getting alternate: %s\\n' \"$alt\" ;;\n \t\t\tesac\n \t\t\trsync $quiet -av --ignore-existing  \\\n \t\t\t    --exclude info \"$alt\" \"$GIT_DIR/objects\" || exit\ndiff --git a/contrib/examples/git-fetch.sh b/contrib/examples/git-fetch.sh\nindex e44af2c..4b61edf 100755\n--- a/contrib/examples/git-fetch.sh\n+++ b/contrib/examples/git-fetch.sh\n@@ -163,19 +163,19 @@ fi\n \n fetch_all_at_once () {\n \n-  eval=$(echo \"$1\" | git fetch--tool parse-reflist \"-\")\n+  eval=$(printf '%s\\n' \"$1\" | git fetch--tool parse-reflist \"-\")\n   eval \"$eval\"\n \n     ( : subshell because we muck with IFS\n       IFS=\" \t$LF\"\n       (\n \tif test \"$remote\" = . ; then\n-\t    git show-ref $rref || echo failed \"$remote\"\n+\t    git show-ref $rref || printf 'failed %s\\n' \"$remote\"\n \telif test -f \"$remote\" ; then\n \t    test -n \"$shallow_depth\" &&\n \t\tdie \"shallow clone with bundle is not supported\"\n \t    git bundle unbundle \"$remote\" $rref ||\n-\t    echo failed \"$remote\"\n+\t    printf 'failed %s\\n' \"$remote\"\n \telse\n \t\tif\ttest -d \"$remote\" &&\n \n@@ -207,9 +207,9 @@ fetch_all_at_once () {\n \t\t\t    flags=\"-v\"\n \t\t\t    ;;\n \t\t\tesac\n-\t\t\tgit-fetch-pack --thin $exec $keep $shallow_depth \\\n+\t\t\tgit fetch-pack --thin $exec $keep $shallow_depth \\\n \t\t\t\t$quiet $no_progress $flags \"$remote\" $rref ||\n-\t\t\techo failed \"$remote\"\n+\t\t\tprintf 'failed %s\\n' \"$remote\"\n \t\tfi\n \tfi\n       ) |\n@@ -274,7 +274,7 @@ fetch_per_ref () {\n \t\tgit fetch--tool -s pick-rref \"$remote_name\" \"-\")\n \t  expr \"z$head\" : \"z$_x40\\$\" >/dev/null ||\n \t\tdie \"No such ref $remote_name at $remote\"\n-\t  echo >&2 \"Fetching $remote_name from $remote using $proto\"\n+\t  printf >&2 '%s\\n' \"Fetching $remote_name from $remote using $proto\"\n \t  case \"$quiet\" in '') v=-v ;; *) v= ;; esac\n \t  git-http-fetch $v -a \"$head\" \"$remote\" || exit\n \t  ;;\ndiff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh\nindex 8f617fc..16a86ca 100755\n--- a/contrib/examples/git-merge.sh\n+++ b/contrib/examples/git-merge.sh\n@@ -144,7 +144,7 @@ merge_name () {\n \t\tsed -e 's/\tnot-for-merge\t/\t\t/' -e 1q \\\n \t\t\t\"$GIT_DIR/FETCH_HEAD\"\n \telse\n-\t\techo \"$rh\t\tcommit '$remote'\"\n+\t\tprintf '%s\\n' \"$rh\t\tcommit '$remote'\"\n \tfi\n }\n \n@@ -529,10 +529,7 @@ if test \"$squash\" = t\n then\n \tfinish\n else\n-\tfor remote\n-\tdo\n-\t\techo $remote\n-\tdone >\"$GIT_DIR/MERGE_HEAD\"\n+\tprintf '%s\\n' \"$@\" >\"$GIT_DIR/MERGE_HEAD\"\n \tprintf '%s\\n' \"$merge_msg\" >\"$GIT_DIR/MERGE_MSG\"\n fi\n \ndiff --git a/contrib/examples/git-notes.sh b/contrib/examples/git-notes.sh\nindex e642e47..a2725c2 100755\n--- a/contrib/examples/git-notes.sh\n+++ b/contrib/examples/git-notes.sh\n@@ -88,7 +88,7 @@ edit)\n \t\tcore_editor=\"$(git config core.editor)\"\n \t\t${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} \"$MSG_FILE\"\n \telse\n-\t\techo \"$MESSAGE\" > \"$MSG_FILE\"\n+\t\tprintf '%s\\n' \"$MESSAGE\" >\"$MSG_FILE\"\n \tfi\n \n \tgrep -v ^# < \"$MSG_FILE\" | git stripspace > \"$MSG_FILE\".processed\ndiff --git a/contrib/examples/git-resolve.sh b/contrib/examples/git-resolve.sh\nindex 8f98142..89c675b 100755\n--- a/contrib/examples/git-resolve.sh\n+++ b/contrib/examples/git-resolve.sh\n@@ -104,7 +104,7 @@ if [ $? -ne 0 ]; then\n \tfi\n \tresult_tree=$(git write-tree) || exit 1\n fi\n-result_commit=$(echo \"$merge_msg\" | git commit-tree $result_tree -p $head -p $merge)\n+result_commit=$(printf '%s\\n' \"$merge_msg\" | git commit-tree $result_tree -p $head -p $merge)\n echo \"Committed merge $result_commit\"\n git update-ref -m \"resolve $merge_name: In-index merge\" \\\n \tHEAD \"$result_commit\" \"$head\"\ndiff --git a/contrib/examples/git-revert.sh b/contrib/examples/git-revert.sh\nindex 49f0032..4ed5e23 100755\n--- a/contrib/examples/git-revert.sh\n+++ b/contrib/examples/git-revert.sh\n@@ -100,7 +100,7 @@ revert)\n \techo\n \techo \"This reverts commit $commit.\"\n \ttest \"$rev\" = \"$commit\" ||\n-\techo \"(original 'git revert' arguments: $@)\"\n+\tprintf '(original 'git revert' arguments: %s)\\n' \"$*\"\n \tbase=$commit next=$prev\n \t;;\n \n@@ -127,20 +127,20 @@ cherry-pick)\n \t}'\n \n \tlogmsg=`git show -s --pretty=raw --encoding=\"$encoding\" \"$commit\"`\n-\tset_author_env=`echo \"$logmsg\" |\n+\tset_author_env=`printf '%s\\n' \"$logmsg\" |\n \tLANG=C LC_ALL=C sed -ne \"$pick_author_script\"`\n \teval \"$set_author_env\"\n \texport GIT_AUTHOR_NAME\n \texport GIT_AUTHOR_EMAIL\n \texport GIT_AUTHOR_DATE\n \n-\techo \"$logmsg\" |\n+\tprintf '%s\\n' \"$logmsg\" |\n \tsed -e '1,/^$/d' -e 's/^    //'\n \tcase \"$replay\" in\n \t'')\n \t\techo \"(cherry picked from commit $commit)\"\n \t\ttest \"$rev\" = \"$commit\" ||\n-\t\techo \"(original 'git cherry-pick' arguments: $@)\"\n+\t\tprintf '(original 'git cherry-pick' arguments: %s)\\n' \"$*\"\n \t\t;;\n \tesac\n \tbase=$prev next=$commit\n@@ -177,7 +177,7 @@ Conflicts:\n \tcherry-pick)\n \t\techo >&2 \"You may choose to use the following when making\"\n \t\techo >&2 \"the commit:\"\n-\t\techo >&2 \"$set_author_env\"\n+\t\tprintf >&2 '%s\\n' \"$set_author_env\"\n \tesac\n \texit 1\n }\ndiff --git a/contrib/fast-import/git-import.sh b/contrib/fast-import/git-import.sh\nindex 0ca7718..80b9ecf 100755\n--- a/contrib/fast-import/git-import.sh\n+++ b/contrib/fast-import/git-import.sh\n@@ -28,8 +28,8 @@ $2\n MSGEOF\n \n EOF\n-\tfind * -type f|while read i;do\n-\t\techo \"M 100644 inline $i\"\n+\tfind * -type f|while read -r i;do\n+\t\tprintf 'M 100644 inline %s\\n' \"$i\"\n \t\techo data $(stat -c '%s' \"$i\")\n \t\tcat \"$i\"\n \t\techo\ndiff --git a/contrib/git-resurrect.sh b/contrib/git-resurrect.sh\nindex c364dda..b3179a8 100755\n--- a/contrib/git-resurrect.sh\n+++ b/contrib/git-resurrect.sh\n@@ -159,7 +159,7 @@ if test -z \"$candidates\"; then\n \tdie \"no candidates for $branch found$hint\"\n fi\n \n-echo \"** Candidates for $branch **\"\n+printf '%s\\n' \"** Candidates for $branch **\"\n for cmt in $candidates; do\n \tgit --no-pager log --pretty=tformat:\"%ct:%h [%cr] %s\" --abbrev-commit -1 $cmt\n done \\\n@@ -176,5 +176,5 @@ elif ! git rev-parse --verify --quiet $new_name >/dev/null; then\n else\n \tprintf \"Most recent: \"\n \tgit --no-pager log -1 --pretty=tformat:\"%h %s\" $newest\n-\techo \"** $new_name already exists, doing nothing\"\n+\tprintf '%s\\n' \"** $new_name already exists, doing nothing\"\n fi\ndiff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email\nindex 30ae63d..85abd23 100755\n--- a/contrib/hooks/post-receive-email\n+++ b/contrib/hooks/post-receive-email\n@@ -491,8 +491,8 @@ generate_atag_email()\n \t\techo \"    length  $(git cat-file -s $tagobject) bytes\"\n \t\t;;\n \tesac\n-\techo \" tagged by  $tagger\"\n-\techo \"        on  $tagged\"\n+\tprintf '%s\\n' \" tagged by  $tagger\"\n+\tprintf '%s\\n' \"        on  $tagged\"\n \n \techo \"\"\n \techo $LOGBEGIN\ndiff --git a/contrib/remotes2config.sh b/contrib/remotes2config.sh\nindex 1cda19f..42f2cd3 100755\n--- a/contrib/remotes2config.sh\n+++ b/contrib/remotes2config.sh\n@@ -26,7 +26,7 @@ if [ -d \"$GIT_DIR\"/remotes ]; then\n \t\t\t\tmv \"$GIT_DIR\"/remotes \"$GIT_DIR\"/remotes.old\n \t\t\tfi ;;\n \t\t*)\n-\t\t\techo \"git config $key \"$value\" $regex\"\n+\t\t\tprintf '%s\\n' \"git config $key \"$value\" $regex\"\n \t\t\tgit config $key \"$value\" $regex || error=1 ;;\n \t\tesac\n \tdone\ndiff --git a/contrib/thunderbird-patch-inline/appp.sh b/contrib/thunderbird-patch-inline/appp.sh\nindex cc518f3..da70fb9 100755\n--- a/contrib/thunderbird-patch-inline/appp.sh\n+++ b/contrib/thunderbird-patch-inline/appp.sh\n@@ -31,25 +31,25 @@ BODY=`sed -e \"1,/${SEP}/d\" $1`\n CMT_MSG=`sed -e '1,/^$/d' -e '/^---$/,$d' \"${PATCH}\"`\n DIFF=`sed -e '1,/^---$/d' \"${PATCH}\"`\n \n-CCS=`echo -e \"$CMT_MSG\\n$HEADERS\" | sed -n -e 's/^Cc: \\(.*\\)$/\\1,/gp' \\\n+CCS=`printf '%s\\n%s' \"$CMT_MSG\" \"$HEADERS\" | sed -n -e 's/^Cc: \\(.*\\)$/\\1,/gp' \\\n \t-e 's/^Signed-off-by: \\(.*\\)/\\1,/gp'`\n \n-echo \"$SUBJECT\" > $1\n-echo \"Cc: $CCS\" >> $1\n-echo \"$HEADERS\" | sed -e '/^Subject: /d' -e '/^Cc: /d' >> $1\n-echo \"$SEP\" >> $1\n+printf '%s\\n' \"$SUBJECT\" > $1\n+printf 'Cc: %s\\n' \"$CCS\" >> $1\n+printf '%s\\n' \"$HEADERS\" | sed -e '/^Subject: /d' -e '/^Cc: /d' >> $1\n+printf '%s\\n' \"$SEP\" >> $1\n \n-echo \"$CMT_MSG\" >> $1\n-echo \"---\" >> $1\n+printf '%s\\n' \"$CMT_MSG\" >> $1\n+printf '---\\n' >> $1\n if [ \"x${BODY}x\" != \"xx\" ] ; then\n-\techo >> $1\n-\techo \"$BODY\" >> $1\n-\techo >> $1\n+\tprintf '\\n' >> $1\n+\tprintf '%s\\n' \"$BODY\" >> $1\n+\tprintf '\\n' >> $1\n fi\n-echo \"$DIFF\" >> $1\n+printf '%s\\n' \"$DIFF\" >> $1\n \n LAST_DIR=`dirname \"${PATCH}\"`\n \n grep -v \"^LAST_DIR=\" \"${CONFFILE}\" > \"${CONFFILE}_\"\n-echo \"LAST_DIR=${LAST_DIR}\" >> \"${CONFFILE}_\"\n+printf \"LAST_DIR=%s\\n' \"${LAST_DIR}\" >> \"${CONFFILE}_\"\n mv \"${CONFFILE}_\" \"${CONFFILE}\"\ndiff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir\nindex 993cacf..3ebe688 100755\n--- a/contrib/workdir/git-new-workdir\n+++ b/contrib/workdir/git-new-workdir\n@@ -1,12 +1,12 @@\n #!/bin/sh\n \n usage () {\n-\techo \"usage:\" $@\n+\tprintf '%s\\n' \"$*\"\n \texit 127\n }\n \n die () {\n-\techo $@\n+\tprintf '%s\\n' \"$*\"\n \texit 128\n }\n \ndiff --git a/git-am.sh b/git-am.sh\nindex 1df5b04..b711e0e 100755\n--- a/git-am.sh\n+++ b/git-am.sh\n@@ -147,7 +147,7 @@ It does not apply to blobs recorded in its index.\"\n }\n \n clean_abort () {\n-\ttest $# = 0 || echo >&2 \"$@\"\n+\ttest $# = 0 || printf >&2 '%s\\n' \"$*\"\n \trm -fr \"$dotest\"\n \texit 1\n }\n@@ -459,7 +459,7 @@ else\n \n \t# -i can and must be given when resuming; everything\n \t# else is kept\n-\techo \" $git_apply_opt\" >\"$dotest/apply-opt\"\n+\tprintf ' %s\\n' \"$git_apply_opt\" >\"$dotest/apply-opt\"\n \techo \"$threeway\" >\"$dotest/threeway\"\n \techo \"$sign\" >\"$dotest/sign\"\n \techo \"$utf8\" >\"$dotest/utf8\"\n@@ -467,7 +467,7 @@ else\n \techo \"$keepcr\" >\"$dotest/keepcr\"\n \techo \"$scissors\" >\"$dotest/scissors\"\n \techo \"$no_inbody_headers\" >\"$dotest/no_inbody_headers\"\n-\techo \"$GIT_QUIET\" >\"$dotest/quiet\"\n+\tprintf '%s\\n' \"$GIT_QUIET\" >\"$dotest/quiet\"\n \techo 1 >\"$dotest/next\"\n \tif test -n \"$rebasing\"\n \tthen\n@@ -643,7 +643,7 @@ do\n \t\tADD_SIGNOFF=`\n \t\t    test \"$LAST_SIGNED_OFF_BY\" = \"$SIGNOFF\" || {\n \t\t    test '' = \"$LAST_SIGNED_OFF_BY\" && echo\n-\t\t    echo \"$SIGNOFF\"\n+\t\t    printf '%s\\n' \"$SIGNOFF\"\n \t\t}`\n \t    else\n \t\tADD_SIGNOFF=\n@@ -655,7 +655,7 @@ do\n \t\tfi\n \t\tif test '' != \"$ADD_SIGNOFF\"\n \t\tthen\n-\t\t\techo \"$ADD_SIGNOFF\"\n+\t\t\tprintf '%s\\n' \"$ADD_SIGNOFF\"\n \t\tfi\n \t    } >\"$dotest/final-commit\"\n \t    ;;\ndiff --git a/git-bisect.sh b/git-bisect.sh\nindex 6e2acb8..26001c1 100755\n--- a/git-bisect.sh\n+++ b/git-bisect.sh\n@@ -139,7 +139,7 @@ bisect_start() {\n \techo \"$start_head\" >\"$GIT_DIR/BISECT_START\" &&\n \tgit rev-parse --sq-quote \"$@\" >\"$GIT_DIR/BISECT_NAMES\" &&\n \teval \"$eval\" &&\n-\techo \"git bisect start$orig_args\" >>\"$GIT_DIR/BISECT_LOG\" || exit\n+\tprintf 'git bisect start%s\\n' \"$orig_args\" >>\"$GIT_DIR/BISECT_LOG\" || exit\n \t#\n \t# Check if we can proceed to the next bisect state.\n \t#\n@@ -158,7 +158,7 @@ bisect_write() {\n \t\t*)\t\tdie \"Bad bisect_write argument: $state\" ;;\n \tesac\n \tgit update-ref \"refs/bisect/$tag\" \"$rev\" || exit\n-\techo \"# $state: $(git show-branch $rev)\" >>\"$GIT_DIR/BISECT_LOG\"\n+\tprintf '%s\\n' \"# $state: $(git show-branch $rev)\" >>\"$GIT_DIR/BISECT_LOG\"\n \ttest -n \"$nolog\" || echo \"git bisect $state $rev\" >>\"$GIT_DIR/BISECT_LOG\"\n }\n \n@@ -365,14 +365,14 @@ bisect_run () {\n \n     while true\n     do\n-      echo \"running $@\"\n+      printf 'running %s\\n' \"$*\"\n       \"$@\"\n       res=$?\n \n       # Check for really bad run error.\n       if [ $res -lt 0 -o $res -ge 128 ]; then\n \t  echo >&2 \"bisect run failed:\"\n-\t  echo >&2 \"exit code $res from '$@' is < 0 or >= 128\"\n+\t  printf >&2 \"exit code %d from '%s' is < 0 or >= 128\\n\" \"$res\" \"$*\"\n \t  exit $res\n       fi\n \ndiff --git a/git-filter-branch.sh b/git-filter-branch.sh\nindex 88fb0f0..34bca06 100755\n--- a/git-filter-branch.sh\n+++ b/git-filter-branch.sh\n@@ -12,7 +12,7 @@\n \n functions=$(cat << \\EOF\n warn () {\n-        echo \"$*\" >&2\n+        printf '%s\\n' \"$*\" >&2\n }\n \n map()\n@@ -22,7 +22,7 @@ map()\n \tthen\n \t\tcat \"$workdir/../map/$1\"\n \telse\n-\t\techo \"$1\"\n+\t\tprintf '%s\\n' \"$1\"\n \tfi\n }\n \n@@ -55,8 +55,7 @@ git_commit_non_empty_tree()\n \n die()\n {\n-\techo >&2\n-\techo \"$*\" >&2\n+\tprintf '\\n%s\\n' \"$*\" >&2\n \texit 1\n }\n EOF\n@@ -303,7 +302,7 @@ while read commit parents; do\n \t\t\tthen\n \t\t\t\trm -f \"$GIT_INDEX_FILE\"\n \t\t\telse\n-\t\t\t\techo >&2 \"$err\"\n+\t\t\t\tprintf >&2 '%s\\n' \"$err\"\n \t\t\t\tfalse\n \t\t\tfi\n \t\t}\n@@ -373,7 +372,7 @@ then\n \t\tsha1=$(git rev-parse \"$ref\"^0)\n \t\ttest -f \"$workdir\"/../map/$sha1 && continue\n \t\tancestor=$(git rev-list --simplify-merges -1 \"$ref\" \"$@\")\n-\t\ttest \"$ancestor\" && echo $(map $ancestor) >> \"$workdir\"/../map/$sha1\n+\t\ttest \"$ancestor\" && map $ancestor >> \"$workdir\"/../map/$sha1\n \tdone < \"$tempdir\"/heads\n fi\n \n@@ -396,12 +395,12 @@ do\n \n \tcase \"$rewritten\" in\n \t'')\n-\t\techo \"Ref '$ref' was deleted\"\n+\t\tprintf \"Ref '%s' was deleted\\n\" \"$ref\"\n \t\tgit update-ref -m \"filter-branch: delete\" -d \"$ref\" $sha1 ||\n \t\t\tdie \"Could not delete $ref\"\n \t;;\n \t$_x40)\n-\t\techo \"Ref '$ref' was rewritten\"\n+\t\tprintf \"Ref '%s' was rewritten\\n\" \"$ref\"\n \t\tif ! git update-ref -m \"filter-branch: rewrite\" \\\n \t\t\t\t\t\"$ref\" $rewritten $sha1 2>/dev/null; then\n \t\t\tif test $(git cat-file -t \"$ref\") = tag; then\ndiff --git a/git-instaweb.sh b/git-instaweb.sh\nindex f608014..abbd2bc 100755\n--- a/git-instaweb.sh\n+++ b/git-instaweb.sh\n@@ -41,14 +41,14 @@ resolve_full_httpd () {\n \tcase \"$httpd\" in\n \t*apache2*|*lighttpd*)\n \t\t# ensure that the apache2/lighttpd command ends with \"-f\"\n-\t\tif ! echo \"$httpd\" | sane_grep -- '-f *$' >/dev/null 2>&1\n+\t\tif ! printf '%s\\n' \"$httpd\" | sane_grep -- '-f *$' >/dev/null 2>&1\n \t\tthen\n \t\t\thttpd=\"$httpd -f\"\n \t\tfi\n \t\t;;\n \tesac\n \n-\thttpd_only=\"$(echo $httpd | cut -f1 -d' ')\"\n+\thttpd_only=\"$(printf '%s\\n' $httpd | cut -f1 -d' ')\"\n \tif case \"$httpd_only\" in /*) : ;; *) which $httpd_only >/dev/null 2>&1;; esac\n \tthen\n \t\tfull_httpd=$httpd\n@@ -66,7 +66,8 @@ resolve_full_httpd () {\n \t\t\tfi\n \t\tdone\n \n-\t\techo >&2 \"$httpd_only not found. Install $httpd_only or use\" \\\n+\t\tprintf >&2 '%s\\n' \\\n+\t\t     \"$httpd_only not found. Install $httpd_only or use\" \\\n \t\t     \"--httpd to specify another httpd daemon.\"\n \t\texit 1\n \tfi\n@@ -90,7 +91,7 @@ start_httpd () {\n \t\tpid=$!\n \n \t\tif test $? != 0; then\n-\t\t\techo \"Could not execute http daemon $httpd.\"\n+\t\t\tprintf 'Could not execute http daemon %s.\\n' \"$httpd\"\n \t\t\texit 1\n \t\tfi\n \n@@ -101,7 +102,7 @@ EOF\n \t*)\n \t\t$full_httpd \"$fqgitdir/gitweb/httpd.conf\"\n \t\tif test $? != 0; then\n-\t\t\techo \"Could not execute http daemon $httpd.\"\n+\t\t\tprintf 'Could not execute http daemon %s.\\n' \"$httpd\"\n \t\t\texit 1\n \t\tfi\n \t\t;;\n@@ -291,8 +292,8 @@ EOF\n \n \tfor mod in mime dir; do\n \t\tif test -e $module_path/mod_${mod}.so; then\n-\t\t\techo \"LoadModule ${mod}_module \" \\\n-\t\t\t     \"$module_path/mod_${mod}.so\" >> \"$conf\"\n+\t\t\tprintf 'LoadModule %s_module %s/mod_%s.so\\n' \\\n+\t\t\t\t\"$mod\" \"$module_path\" \"$mod\" >> \"$conf\"\n \t\tfi\n \tdone\n \tcat >> \"$conf\" <<EOF\n@@ -320,17 +321,18 @@ EOF\n \telse\n \t\t# plain-old CGI\n \t\tresolve_full_httpd\n-\t\tlist_mods=$(echo \"$full_httpd\" | sed 's/-f$/-l/')\n+\t\tlist_mods=$(printf '%s\\n' \"$full_httpd\" | sed 's/-f$/-l/')\n \t\t$list_mods | sane_grep 'mod_cgi\\.c' >/dev/null 2>&1 || \\\n \t\tif test -f \"$module_path/mod_cgi.so\"\n \t\tthen\n-\t\t\techo \"LoadModule cgi_module $module_path/mod_cgi.so\" >> \"$conf\"\n+\t\t\tprintf 'LoadModule cgi_module %s/mod_cgi.so\\n' \\\n+\t\t\t\t\"$module_path\" >> \"$conf\"\n \t\telse\n \t\t\t$list_mods | grep 'mod_cgid\\.c' >/dev/null 2>&1 || \\\n \t\t\tif test -f \"$module_path/mod_cgid.so\"\n \t\t\tthen\n-\t\t\t\techo \"LoadModule cgid_module $module_path/mod_cgid.so\" \\\n-\t\t\t\t\t>> \"$conf\"\n+\t\t\t\tprintf 'LoadModule cgid_module %s/mod_cgid.so\\n' \\\n+\t\t\t\t\t\"$module_path\" >> \"$conf\"\n \t\t\telse\n \t\t\t\techo \"You have no CGI support!\"\n \t\t\t\texit 2\n@@ -420,7 +422,7 @@ webrick)\n \tmongoose_conf\n \t;;\n *)\n-\techo \"Unknown httpd specified: $httpd\"\n+\tprintf 'Unknown httpd specified: %s\\n' \"$httpd\"\n \texit 1\n \t;;\n esac\ndiff --git a/git-merge-octopus.sh b/git-merge-octopus.sh\nindex 615753c..22aa379 100755\n--- a/git-merge-octopus.sh\n+++ b/git-merge-octopus.sh\n@@ -9,7 +9,7 @@ LF='\n '\n \n die () {\n-    echo >&2 \"$*\"\n+    printf >&2 '%s\\n' \"$*\"\n     exit 1\n }\n \n@@ -66,7 +66,7 @@ do\n \n \tcase \"$LF$common$LF\" in\n \t*\"$LF$SHA1$LF\"*)\n-\t\techo \"Already up-to-date with $pretty_name\"\n+\t\tprintf 'Already up-to-date with %s\\n' \"$pretty_name\"\n \t\tcontinue\n \t\t;;\n \tesac\n@@ -78,7 +78,7 @@ do\n \t\t# tree as the intermediate result of the merge.\n \t\t# We still need to count this as part of the parent set.\n \n-\t\techo \"Fast-forwarding to: $pretty_name\"\n+\t\tprintf 'Fast-forwarding to: %s\\n' \"$pretty_name\"\n \t\tgit read-tree -u -m $head $SHA1 || exit\n \t\tMRC=$SHA1 MRT=$(git write-tree)\n \t\tcontinue\n@@ -86,13 +86,13 @@ do\n \n \tNON_FF_MERGE=1\n \n-\techo \"Trying simple merge with $pretty_name\"\n+\tprintf 'Trying simple merge with %s\\n' \"$pretty_name\"\n \tgit read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2\n \tnext=$(git write-tree 2>/dev/null)\n \tif test $? -ne 0\n \tthen\n \t\techo \"Simple merge did not work, trying automatic merge.\"\n-\t\tgit-merge-index -o git-merge-one-file -a ||\n+\t\tgit merge-index -o git-merge-one-file -a ||\n \t\tOCTOPUS_FAILURE=1\n \t\tnext=$(git write-tree 2>/dev/null)\n \tfi\ndiff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\nindex d067894..0d449c6 100755\n--- a/git-merge-one-file.sh\n+++ b/git-merge-one-file.sh\n@@ -34,7 +34,7 @@ case \"${1:-.}${2:-.}${3:-.}\" in\n #\n \"$1..\" | \"$1.$1\" | \"$1$1.\")\n \tif [ \"$2\" ]; then\n-\t\techo \"Removing $4\"\n+\t\tprintf 'Removing %s\\n' \"$4\"\n \telse\n \t\t# read-tree checked that index matches HEAD already,\n \t\t# so we know we do not have this path tracked.\n@@ -59,10 +59,10 @@ case \"${1:-.}${2:-.}${3:-.}\" in\n \texec git update-index --add --cacheinfo \"$6\" \"$2\" \"$4\"\n \t;;\n \"..$3\")\n-\techo \"Adding $4\"\n+\tprintf 'Adding %s\\n' \"$4\"\n \tif test -f \"$4\"\n \tthen\n-\t\techo \"ERROR: untracked $4 is overwritten by the merge.\"\n+\t\tprintf 'ERROR: untracked %s is overwritten by the merge.\\n' \"$4\"\n \t\texit 1\n \tfi\n \tgit update-index --add --cacheinfo \"$7\" \"$3\" \"$4\" &&\n@@ -74,11 +74,11 @@ case \"${1:-.}${2:-.}${3:-.}\" in\n #\n \".$3$2\")\n \tif [ \"$6\" != \"$7\" ]; then\n-\t\techo \"ERROR: File $4 added identically in both branches,\"\n-\t\techo \"ERROR: but permissions conflict $6->$7.\"\n+\t\tprintf 'ERROR: File %s added identically in both branches.\\n' \"$4\"\n+\t\tprintf 'ERROR: but permissions conflict %s->%s.\\n' \"$6\" \"$7\"\n \t\texit 1\n \tfi\n-\techo \"Adding $4\"\n+\tprintf 'Adding %s\\n' \"$4\"\n \tgit update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n \t\texec git checkout-index -u -f -- \"$4\"\n \t;;\n@@ -90,11 +90,11 @@ case \"${1:-.}${2:-.}${3:-.}\" in\n \n \tcase \",$6,$7,\" in\n \t*,120000,*)\n-\t\techo \"ERROR: $4: Not merging symbolic link changes.\"\n+\t\tprintf 'ERROR: %s: Not merging symbolic link changes.\\n' \"$4\"\n \t\texit 1\n \t\t;;\n \t*,160000,*)\n-\t\techo \"ERROR: $4: Not merging conflicting submodule changes.\"\n+\t\tprintf 'ERROR: %s: Not merging conflicting submodule changes.\\n' \"$4\"\n \t\texit 1\n \t\t;;\n \tesac\n@@ -102,7 +102,7 @@ case \"${1:-.}${2:-.}${3:-.}\" in\n \tsrc2=`git-unpack-file $3`\n \tcase \"$1\" in\n \t'')\n-\t\techo \"Added $4 in both, but differently.\"\n+\t\tprintf 'Added %s in both, but differently.\\n' \"$4\"\n \t\t# This extracts OUR file in $orig, and uses git apply to\n \t\t# remove lines that are unique to ours.\n \t\torig=`git-unpack-file $2`\n@@ -115,7 +115,7 @@ case \"${1:-.}${2:-.}${3:-.}\" in\n \t\texpr \"$sz0\" \\< \"$sz1\" \\* 2 >/dev/null || : >$orig\n \t\t;;\n \t*)\n-\t\techo \"Auto-merging $4\"\n+\t\tprintf 'Auto-merging %s\\n' \"$4\"\n \t\torig=`git-unpack-file $1`\n \t\t;;\n \tesac\n@@ -147,14 +147,14 @@ case \"${1:-.}${2:-.}${3:-.}\" in\n \tfi\n \n \tif [ $ret -ne 0 ]; then\n-\t\techo \"ERROR: $msg in $4\"\n+\t\tprintf 'ERROR: %s in %s\\n' \"$msg\" \"$4\"\n \t\texit 1\n \tfi\n \texec git update-index -- \"$4\"\n \t;;\n \n *)\n-\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\"\n+\tprintf 'ERROR: %s: Not handling case %s -> %s -> %s\\n' \"$4\" \"$1\" \"$2\" \"$3\"\n \t;;\n esac\n exit 1\ndiff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh\nindex 51dd0d6..a960595 100644\n--- a/git-mergetool--lib.sh\n+++ b/git-mergetool--lib.sh\n@@ -23,7 +23,7 @@ translate_merge_tool_path () {\n \t\techo compare\n \t\t;;\n \t*)\n-\t\techo \"$1\"\n+\t\tprintf '%s\\n' \"$1\"\n \t\t;;\n \tesac\n }\n@@ -33,7 +33,7 @@ check_unchanged () {\n \t\tstatus=0\n \telse\n \t\twhile true; do\n-\t\t\techo \"$MERGED seems unchanged.\"\n+\t\t\tprintf '%s seems unchanged.\\n' \"$MERGED\"\n \t\t\tprintf \"Was the merge successful? [y/n] \"\n \t\t\tread answer < /dev/tty\n \t\t\tcase \"$answer\" in\n@@ -75,10 +75,10 @@ get_merge_tool_cmd () {\n \t\tmerge_tool=\"$(get_merge_tool)\"\n \tfi\n \tif diff_mode; then\n-\t\techo \"$(git config difftool.$merge_tool.cmd ||\n-\t\t        git config mergetool.$merge_tool.cmd)\"\n+\t\tgit config difftool.$merge_tool.cmd ||\n+\t\t        git config mergetool.$merge_tool.cmd\n \telse\n-\t\techo \"$(git config mergetool.$merge_tool.cmd)\"\n+\t\tgit config mergetool.$merge_tool.cmd\n \tfi\n }\n \n@@ -347,14 +347,14 @@ guess_merge_tool () {\n \t\ttools=\"$tools emerge vimdiff\"\n \t\t;;\n \tesac\n-\techo >&2 \"merge tool candidates: $tools\"\n+\tprintf >&2 'merge tool candidates: %s\\n' \"$tools\"\n \n \t# Loop over each candidate and stop when a valid merge tool is found.\n \tfor i in $tools\n \tdo\n \t\tmerge_tool_path=\"$(translate_merge_tool_path \"$i\")\"\n \t\tif type \"$merge_tool_path\" > /dev/null 2>&1; then\n-\t\t\techo \"$i\"\n+\t\t\tprintf '%s\\n' \"$i\"\n \t\t\treturn 0\n \t\tfi\n \tdone\n@@ -372,11 +372,12 @@ get_configured_merge_tool () {\n \t\tmerge_tool=$(git config merge.tool)\n \tfi\n \tif test -n \"$merge_tool\" && ! valid_tool \"$merge_tool\"; then\n-\t\techo >&2 \"git config option $TOOL_MODE.tool set to unknown tool: $merge_tool\"\n-\t\techo >&2 \"Resetting to default...\"\n+\t\tprintf >&2 'git config option %s.tool set to unknown tool: %s\\n' \\\n+\t\t\t\"$TOOL_MODE\" \"$merge_tool\"\n+\t\tprintf >&2 'Resetting to default...\\n'\n \t\treturn 1\n \tfi\n-\techo \"$merge_tool\"\n+\tprintf '%s\\n' \"$merge_tool\"\n }\n \n get_merge_tool_path () {\n@@ -387,7 +388,7 @@ get_merge_tool_path () {\n \t\tmerge_tool=\"$(get_merge_tool)\"\n \tfi\n \tif ! valid_tool \"$merge_tool\"; then\n-\t\techo >&2 \"Unknown merge tool $merge_tool\"\n+\t\tprintf >&2 'Unknown merge tool %s\\n' \"$merge_tool\"\n \t\texit 1\n \tfi\n \tif diff_mode; then\n@@ -401,11 +402,11 @@ get_merge_tool_path () {\n \tfi\n \tif test -z \"$(get_merge_tool_cmd \"$merge_tool\")\" &&\n \t! type \"$merge_tool_path\" > /dev/null 2>&1; then\n-\t\techo >&2 \"The $TOOL_MODE tool $merge_tool is not available as\"\\\n-\t\t         \"'$merge_tool_path'\"\n+\t\tprintf >&2 \"The %s tool %s is not available as '%s'\\n\" \\\n+\t\t\t\"$TOOL_MODE\" \"$merge_tool\" \"$merge_tool_path\"\n \t\texit 1\n \tfi\n-\techo \"$merge_tool_path\"\n+\tprintf '%s\\n' \"$merge_tool_path\"\n }\n \n get_merge_tool () {\n@@ -415,5 +416,5 @@ get_merge_tool () {\n \tif test -z \"$merge_tool\"; then\n \t\tmerge_tool=\"$(guess_merge_tool)\" || exit\n \tfi\n-\techo \"$merge_tool\"\n+\tprintf '%s\\n' \"$merge_tool\"\n }\ndiff --git a/git-mergetool.sh b/git-mergetool.sh\nindex b52a741..97442ce 100755\n--- a/git-mergetool.sh\n+++ b/git-mergetool.sh\n@@ -51,7 +51,7 @@ describe_file () {\n     if test -z \"$mode\"; then\n \techo \"deleted\"\n     elif is_symlink \"$mode\" ; then\n-\techo \"a symbolic link -> '$(cat \"$file\")'\"\n+\tprintf \"a symbolic link -> '%s'\\n\" \"$(cat \"$file\")\"\n     else\n \tif base_present; then\n \t    echo \"modified\"\n@@ -126,9 +126,9 @@ merge_file () {\n     f=$(git ls-files -u -- \"$MERGED\")\n     if test -z \"$f\" ; then\n \tif test ! -f \"$MERGED\" ; then\n-\t    echo \"$MERGED: file not found\"\n+\t    printf '%s: file not found\\n' \"$MERGED\"\n \telse\n-\t    echo \"$MERGED: file does not need merging\"\n+\t    printf '%s: file does not need merging\\n' \"$MERGED\"\n \tfi\n \treturn 1\n     fi\n@@ -151,7 +151,7 @@ merge_file () {\n     remote_present && checkout_staged_file 3 \"$MERGED\" \"$REMOTE\"\n \n     if test -z \"$local_mode\" -o -z \"$remote_mode\"; then\n-\techo \"Deleted merge conflict for '$MERGED':\"\n+\tprintf \"Deleted merge conflict for '%s':\\n\" \"$MERGED\"\n \tdescribe_file \"$local_mode\" \"local\" \"$LOCAL\"\n \tdescribe_file \"$remote_mode\" \"remote\" \"$REMOTE\"\n \tresolve_deleted_merge\n@@ -159,14 +159,14 @@ merge_file () {\n     fi\n \n     if is_symlink \"$local_mode\" || is_symlink \"$remote_mode\"; then\n-\techo \"Symbolic link merge conflict for '$MERGED':\"\n+\tprintf \"Symbolic link merge conflict for '%s':\\n\" \"$MERGED\"\n \tdescribe_file \"$local_mode\" \"local\" \"$LOCAL\"\n \tdescribe_file \"$remote_mode\" \"remote\" \"$REMOTE\"\n \tresolve_symlink_merge\n \treturn\n     fi\n \n-    echo \"Normal merge conflict for '$MERGED':\"\n+    printf \"Normal merge conflict for '%s':\\n\" \"$MERGED\"\n     describe_file \"$local_mode\" \"local\" \"$LOCAL\"\n     describe_file \"$remote_mode\" \"remote\" \"$REMOTE\"\n     if \"$prompt\" = true; then\n@@ -181,7 +181,7 @@ merge_file () {\n     fi\n \n     if ! run_merge_tool \"$merge_tool\" \"$present\"; then\n-\techo \"merge of $MERGED failed\" 1>&2\n+\tprintf \"merge of %s failed\\n\" \"MERGED\" 1>&2\n \tmv -- \"$BACKUP\" \"$MERGED\"\n \n \tif test \"$merge_keep_temporaries\" = \"false\"; then\n@@ -271,7 +271,7 @@ if test $# -eq 0 ; then\n \techo \"No files need merging\"\n \texit 0\n     fi\n-    echo Merging the files: \"$files\"\n+    printf 'Merging the files: %s\\n' \"$files\"\n     git ls-files -u |\n     sed -e 's/^[^\t]*\t//' |\n     sort -u |\ndiff --git a/git-parse-remote.sh b/git-parse-remote.sh\nindex 5f47b18..357392a 100644\n--- a/git-parse-remote.sh\n+++ b/git-parse-remote.sh\n@@ -32,10 +32,10 @@ get_remote_url () {\n \tdata_source=$(get_data_source \"$1\")\n \tcase \"$data_source\" in\n \t'')\n-\t\techo \"$1\"\n+\t\tprintf '%s\\n' \"$1\"\n \t\t;;\n \tself)\n-\t\techo \"$1\"\n+\t\tprintf '%s\\n' \"$1\"\n \t\t;;\n \tconfig)\n \t\tgit config --get \"remote.$1.url\"\ndiff --git a/git-quiltimport.sh b/git-quiltimport.sh\nindex 9a6ba2b..a4754d3 100755\n--- a/git-quiltimport.sh\n+++ b/git-quiltimport.sh\n@@ -48,7 +48,7 @@ fi\n # Quilt patch directory\n : ${QUILT_PATCHES:=patches}\n if ! [ -d \"$QUILT_PATCHES\" ] ; then\n-\techo \"The \\\"$QUILT_PATCHES\\\" directory does not exist.\"\n+\tprintf 'The \"%s\" directory does not exist.\\n' \"$QUILT_PATCHES\"\n \texit 1\n fi\n \n@@ -77,14 +77,14 @@ do\n \tcase \"$garbage\" in\n \t''|'#'*);;\n \t*)\n-\t\techo \"trailing garbage found in series file: $garbage\"\n+\t\tprintf 'trailing garbage found in series file: %s\\n' \"$garbage\"\n \t\texit 1;;\n \tesac\n \tif ! [ -f \"$QUILT_PATCHES/$patch_name\" ] ; then\n-\t\techo \"$patch_name doesn't exist. Skipping.\"\n+\t\tprintf '%s doesn't exist. Skipping.\\n' \"$patch_name\"\n \t\tcontinue\n \tfi\n-\techo $patch_name\n+\tprintf '%s\\n' \"$patch_name\"\n \tgit mailinfo \"$tmp_msg\" \"$tmp_patch\" \\\n \t\t<\"$QUILT_PATCHES/$patch_name\" >\"$tmp_info\" || exit 3\n \ttest -s \"$tmp_patch\" || {\n@@ -101,17 +101,17 @@ do\n \t\t\tGIT_AUTHOR_NAME=\"$quilt_author_name\";\n \t\t\tGIT_AUTHOR_EMAIL=\"$quilt_author_email\";\n \t\telif [ -n \"$dry_run\" ]; then\n-\t\t\techo \"No author found in $patch_name\" >&2;\n+\t\t\tprintf 'No author found in %s\\n' \"$patch_name\" >&2;\n \t\t\tGIT_AUTHOR_NAME=\"dry-run-not-found\";\n \t\t\tGIT_AUTHOR_EMAIL=\"dry-run-not-found\";\n \t\telse\n-\t\t\techo \"No author found in $patch_name\" >&2;\n+\t\t\tprintf 'No author found in %s\\n' \"$patch_name\" >&2;\n \t\t\techo \"---\"\n \t\t\tcat $tmp_msg\n \t\t\tprintf \"Author: \";\n-\t\t\tread patch_author\n+\t\t\tread -r patch_author\n \n-\t\t\techo \"$patch_author\"\n+\t\t\tprintf '%s\\n' \"$patch_author\"\n \n \t\t\tpatch_author_name=$(expr \"z$patch_author\" : 'z\\(.*[^ ]\\) *<.*') &&\n \t\t\tpatch_author_email=$(expr \"z$patch_author\" : '.*<\\([^>]*\\)') &&\n@@ -125,13 +125,19 @@ do\n \tSUBJECT=$(sed -ne 's/Subject: //p' \"$tmp_info\")\n \texport GIT_AUTHOR_DATE SUBJECT\n \tif [ -z \"$SUBJECT\" ] ; then\n-\t\tSUBJECT=$(echo $patch_name | sed -e 's/.patch$//')\n+\t\tSUBJECT=$(printf '%s\\n' \"$patch_name\" | sed -e 's/.patch$//')\n \tfi\n \n \tif [ -z \"$dry_run\" ] ; then\n \t\tgit apply --index -C1 ${level:+\"$level\"} \"$tmp_patch\" &&\n \t\ttree=$(git write-tree) &&\n-\t\tcommit=$( (echo \"$SUBJECT\"; echo; cat \"$tmp_msg\") | git commit-tree $tree -p $commit) &&\n+\t\tcommit=$(\n+\t\t\t\t{\n+\t\t\t\t\tprintf '%s\\n\\n' \"$SUBJECT\"\n+\t\t\t\t\tcat \"$tmp_msg\"\n+\t\t\t\t} |\n+\t\t\t\tgit commit-tree $tree -p $commit\n+\t\t\t) &&\n \t\tgit update-ref -m \"quiltimport: $patch_name\" HEAD $commit || exit 4\n \tfi\n done 3<\"$QUILT_PATCHES/series\"\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 6b86abc..67bbce9 100755\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -119,7 +119,7 @@ run 'git rebase --continue'\"\n export GIT_CHERRY_PICK_HELP\n \n warn () {\n-\techo \"$*\" >&2\n+\tprintf '%s\\n' \"$*\" >&2\n }\n \n output () {\n@@ -509,7 +509,7 @@ do_next () {\n \t\tmark_action_done\n \t\tupdate_squash_messages $squash_style $sha1\n \t\tauthor_script=$(get_author_ident_from_commit HEAD)\n-\t\techo \"$author_script\" > \"$AUTHOR_SCRIPT\"\n+\t\tprintf '%s\\n' \"$author_script\" > \"$AUTHOR_SCRIPT\"\n \t\teval \"$author_script\"\n \t\toutput git reset --soft HEAD^\n \t\tpick_one -n $sha1 || die_failed_squash $sha1 \"$rest\"\n@@ -606,7 +606,7 @@ skip_unnecessary_picks () {\n \t\t\tfd=1\n \t\t\t;;\n \t\tesac\n-\t\techo \"$command${sha1:+ }$sha1${rest:+ }$rest\" >&$fd\n+\t\tprintf '%s\\n' \"$command${sha1:+ }$sha1${rest:+ }$rest\" >&$fd\n \tdone <\"$TODO\" >\"$TODO.new\" 3>>\"$DONE\" &&\n \tmv -f \"$TODO\".new \"$TODO\" &&\n \tcase \"$(peek_next_command)\" in\n@@ -649,12 +649,12 @@ rearrange_squash () {\n \t\tcase \" $used\" in\n \t\t*\" $sha1 \"*) continue ;;\n \t\tesac\n-\t\techo \"$pick $sha1 $message\"\n+\t\tprintf '%s %s %s\\n' \"$pick\" \"$sha1\" \"$message\"\n \t\twhile read squash action msg\n \t\tdo\n \t\t\tcase \"$message\" in\n \t\t\t\"$msg\"*)\n-\t\t\t\techo \"$action $squash $action! $msg\"\n+\t\t\t\tprintf '%s\\n' \"$action $squash $action! $msg\"\n \t\t\t\tused=\"$used$squash \"\n \t\t\t\t;;\n \t\t\tesac\n@@ -676,7 +676,7 @@ parse_onto () {\n \t\t\t?*\"$LF\"?* | '')\n \t\t\t\texit 1 ;;\n \t\t\tesac\n-\t\t\techo \"$onto\"\n+\t\t\tprintf '%s\\n' \"$onto\"\n \t\t\texit 0\n \t\tfi\n \tesac\n@@ -848,7 +848,7 @@ first and then run 'git rebase --continue' again.\"\n \t\t*)\n \t\t\t: >\"$DOTEST\"/rebase-root ;;\n \t\tesac\n-\t\techo $ONTO > \"$DOTEST\"/onto\n+\t\tprintf '%s\\n' \"$ONTO\" > \"$DOTEST\"/onto\n \t\ttest -z \"$STRATEGY\" || echo \"$STRATEGY\" > \"$DOTEST\"/strategy\n \t\ttest t = \"$VERBOSE\" && : > \"$DOTEST\"/verbose\n \t\tif test t = \"$PRESERVE_MERGES\"\n@@ -858,12 +858,12 @@ first and then run 'git rebase --continue' again.\"\n \t\t\t\tmkdir \"$REWRITTEN\" &&\n \t\t\t\tfor c in $(git merge-base --all $HEAD $UPSTREAM)\n \t\t\t\tdo\n-\t\t\t\t\techo $ONTO > \"$REWRITTEN\"/$c ||\n+\t\t\t\t\tprintf '%s\\n' \"$ONTO\" > \"$REWRITTEN\"/$c ||\n \t\t\t\t\t\tdie \"Could not init rewritten commits\"\n \t\t\t\tdone\n \t\t\telse\n \t\t\t\tmkdir \"$REWRITTEN\" &&\n-\t\t\t\techo $ONTO > \"$REWRITTEN\"/root ||\n+\t\t\t\tprintf '%s\\n' \"$ONTO\" > \"$REWRITTEN\"/root ||\n \t\t\t\t\tdie \"Could not init rewritten commits\"\n \t\t\tfi\n \t\t\t# No cherry-pick because our first pass is to determine\n@@ -894,7 +894,7 @@ first and then run 'git rebase --continue' again.\"\n \t\tdo\n \t\t\tif test t != \"$PRESERVE_MERGES\"\n \t\t\tthen\n-\t\t\t\techo \"pick $shortsha1 $rest\" >> \"$TODO\"\n+\t\t\t\tprintf 'pick %s %s\\n' \"$shortsha1\" \"$rest\" >> \"$TODO\"\n \t\t\telse\n \t\t\t\tsha1=$(git rev-parse $shortsha1)\n \t\t\t\tif test -z \"$REBASE_ROOT\"\n@@ -913,7 +913,7 @@ first and then run 'git rebase --continue' again.\"\n \t\t\t\tif test f = \"$preserve\"\n \t\t\t\tthen\n \t\t\t\t\ttouch \"$REWRITTEN\"/$sha1\n-\t\t\t\t\techo \"pick $shortsha1 $rest\" >> \"$TODO\"\n+\t\t\t\t\tprintf 'pick %s %s\\n' \"$shortsha1\" \"$rest\" >> \"$TODO\"\n \t\t\t\tfi\n \t\t\tfi\n \t\tdone\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex 44f5c65..5867866 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -403,7 +403,7 @@ fi\n diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --)\n case \"$diff\" in\n ?*)\techo >&2 \"cannot rebase: your index contains uncommitted changes\"\n-\techo >&2 \"$diff\"\n+\tprintf >&2 '%s\\n' \"$diff\"\n \texit 1\n \t;;\n esac\n@@ -519,7 +519,7 @@ if test -n \"$diffstat\"\n then\n \tif test -n \"$verbose\"\n \tthen\n-\t\techo \"Changes from $mb to $onto:\"\n+\t\tprintf 'Changes from %s to %s:\\n' \"$mb\" \"$onto\"\n \tfi\n \t# We want color (if set), but no pager\n \tGIT_PAGER='' git diff --stat --summary \"$mb\" \"$onto\"\n@@ -549,10 +549,10 @@ then\n \tmove_to_original_branch\n \tret=$?\n \ttest 0 != $ret -a -d \"$GIT_DIR\"/rebase-apply &&\n-\t\techo $head_name > \"$GIT_DIR\"/rebase-apply/head-name &&\n-\t\techo $onto > \"$GIT_DIR\"/rebase-apply/onto &&\n-\t\techo $orig_head > \"$GIT_DIR\"/rebase-apply/orig-head &&\n-\t\techo \"$GIT_QUIET\" > \"$GIT_DIR\"/rebase-apply/quiet\n+\t\tprintf '%s\\n' \"$head_name\" > \"$GIT_DIR\"/rebase-apply/head-name &&\n+\t\tprintf '%s\\n' \"$onto\" > \"$GIT_DIR\"/rebase-apply/onto &&\n+\t\tprintf '%s\\n' \"$orig_head\" > \"$GIT_DIR\"/rebase-apply/orig-head &&\n+\t\tprintf '%s\\n' \"$GIT_QUIET\" > \"$GIT_DIR\"/rebase-apply/quiet\n \texit $ret\n fi\n \n@@ -560,13 +560,13 @@ fi\n # this is rename-aware if the recursive (default) strategy is used\n \n mkdir -p \"$dotest\"\n-echo \"$onto\" > \"$dotest/onto\"\n-echo \"$onto_name\" > \"$dotest/onto_name\"\n+printf '%s\\n' \"$onto\" > \"$dotest/onto\"\n+printf '%s\\n' \"$onto_name\" > \"$dotest/onto_name\"\n prev_head=$orig_head\n-echo \"$prev_head\" > \"$dotest/prev_head\"\n-echo \"$orig_head\" > \"$dotest/orig-head\"\n-echo \"$head_name\" > \"$dotest/head-name\"\n-echo \"$GIT_QUIET\" > \"$dotest/quiet\"\n+printf '%s\\n' \"$prev_head\" > \"$dotest/prev_head\"\n+printf '%s\\n' \"$orig_head\" > \"$dotest/orig-head\"\n+printf '%s\\n' \"$head_name\" > \"$dotest/head-name\"\n+printf '%s\\n' \"$GIT_QUIET\" > \"$dotest/quiet\"\n \n msgnum=0\n for cmt in `git rev-list --reverse --no-merges \"$revisions\"`\ndiff --git a/git-repack.sh b/git-repack.sh\nindex 1eb3bca..f1fb309 100755\n--- a/git-repack.sh\n+++ b/git-repack.sh\n@@ -132,7 +132,7 @@ then\n \t\techo >&2 \"WARNING: Please rename them in $PACKDIR manually:\"\n \t\tfor file in $rollback_failure\n \t\tdo\n-\t\t\techo >&2 \"WARNING:   old-$file -> $file\"\n+\t\t\tprintf >&2 'WARNING:   old-%s -> %s\\n' \"$file\"\n \t\tdone\n \tfi\n \texit 1\ndiff --git a/git-request-pull.sh b/git-request-pull.sh\nindex 8fd15f6..fd1fb31 100755\n--- a/git-request-pull.sh\n+++ b/git-request-pull.sh\n@@ -56,11 +56,9 @@ branch=$(git ls-remote \"$url\" \\\n \t}\")\n url=$(get_remote_url \"$url\")\n if [ -z \"$branch\" ]; then\n-\techo \"warn: No branch of $url is at:\" >&2\n+\tprintf 'warn: No branch of %s is at:\\n' \"$url\" >&2\n \tgit log --max-count=1 --pretty='tformat:warn:   %h: %s' $headrev >&2\n-\techo \"warn: Are you sure you pushed $head there?\" >&2\n-\techo >&2\n-\techo >&2\n+\tprintf 'warn: Are you sure you pushed %s there?\\n\\n\\n' \"$head\" >&2\n \tbranch=..BRANCH.NOT.VERIFIED..\n \tstatus=1\n fi\n@@ -70,8 +68,7 @@ git show -s --format='The following changes since commit %H:\n   %s (%ci)\n \n are available in the git repository at:' $baserev\n-echo \"  $url $branch\"\n-echo\n+printf '  %s %s\\n\\n' \"$url\" \"$branch\"\n \n git shortlog ^$baserev $headrev\n git diff -M --stat --summary $patch $merge_base..$headrev\ndiff --git a/git-sh-setup.sh b/git-sh-setup.sh\nindex 6131670..97e08cd 100644\n--- a/git-sh-setup.sh\n+++ b/git-sh-setup.sh\n@@ -31,7 +31,7 @@ git_broken_path_fix () {\n \t\t\t\tpath=\"$path$sep$elem\"\n \t\t\t\tsep=:\n \t\t\tdone\n-\t\t\techo \"$path\"\n+\t\t\tprintf '%s\\n' \"$path\"\n \t\t)\n \t\t;;\n \tesac\n@@ -40,7 +40,7 @@ git_broken_path_fix () {\n # @@BROKEN_PATH_FIX@@\n \n die() {\n-\techo >&2 \"$@\"\n+\tprintf >&2 '%s\\n' \"$*\"\n \texit 1\n }\n \n@@ -64,7 +64,7 @@ if test -n \"$OPTIONS_SPEC\"; then\n \t\tparseopt_extra=\"--keep-dashdash\"\n \n \teval \"$(\n-\t\techo \"$OPTIONS_SPEC\" |\n+\t\tprintf '%s\\n' \"$OPTIONS_SPEC\" |\n \t\t\tgit rev-parse --parseopt $parseopt_extra -- \"$@\" ||\n \t\techo exit $?\n \t)\"\n@@ -85,7 +85,7 @@ $LONG_USAGE\"\n \n \tcase \"$1\" in\n \t\t-h|--h|--he|--hel|--help)\n-\t\techo \"$LONG_USAGE\"\n+\t\tprintf '%s\\n' \"$LONG_USAGE\"\n \t\texit\n \tesac\n fi\n@@ -135,7 +135,7 @@ is_bare_repository () {\n cd_to_toplevel () {\n \tcdup=$(git rev-parse --show-toplevel) &&\n \tcd \"$cdup\" || {\n-\t\techo >&2 \"Cannot chdir to $cdup, the toplevel of the working tree\"\n+\t\tprintf >&2 'Cannot chdir to %s, the toplevel of the working tree\\n\" \"$cdup\"\n \t\texit 1\n \t}\n }\ndiff --git a/git-stash.sh b/git-stash.sh\nindex 0f858d3..afe6351 100755\n--- a/git-stash.sh\n+++ b/git-stash.sh\n@@ -150,8 +150,8 @@ save_stash () {\n \t\t\tbreak\n \t\t\t;;\n \t\t-*)\n-\t\t\techo \"error: unknown option for 'stash save': $1\"\n-\t\t\techo \"       To provide a message, use git stash save -- '$1'\"\n+\t\t\tprintf \"error: unknown option for 'stash save': %s\\n\" \"$1\"\n+\t\t\tprintf \"       To provide a message, use git stash save -- '%s'\\n\" \"$1\"\n \t\t\tusage\n \t\t\t;;\n \t\t*)\n@@ -414,7 +414,7 @@ create)\n \tthen\n \t\tshift\n \tfi\n-\tcreate_stash \"$*\" && echo \"$w_commit\"\n+\tcreate_stash \"$*\" && printf '%s\\n' \"$w_commit\"\n \t;;\n drop)\n \tshift\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 3319b83..8efbc9b 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -50,7 +50,7 @@ resolve_relative_url ()\n \t\t\tbreak;;\n \t\tesac\n \tdone\n-\techo \"$remoteurl/${url%/}\"\n+\tprintf '%s\\n' \"$remoteurl/${url%/}\"\n }\n \n #\n@@ -75,7 +75,7 @@ module_name()\n \t\tsed -n -e 's|^submodule\\.\\(.*\\)\\.path '\"$re\"'$|\\1|p' )\n        test -z \"$name\" &&\n        die \"No submodule mapping found in .gitmodules for path '$path'\"\n-       echo \"$name\"\n+       printf '%s\\n' \"$name\"\n }\n \n #\n@@ -163,7 +163,7 @@ cmd_add()\n \tpath=$2\n \n \tif test -z \"$path\"; then\n-\t\tpath=$(echo \"$repo\" |\n+\t\tpath=$(printf '%s\\n' \"$repo\" |\n \t\t\tsed -e 's|/$||' -e 's|:*/*\\.git$||' -e 's|.*[/:]||g')\n \tfi\n \n@@ -206,7 +206,8 @@ cmd_add()\n \tthen\n \t\tif test -d \"$path\"/.git -o -f \"$path\"/.git\n \t\tthen\n-\t\t\techo \"Adding existing repo at '$path' to the index\"\n+\t\t\tprintf \"Adding existing repo at '%s' to the index\\n\" \\\n+\t\t\t\t\"$path\"\n \t\telse\n \t\t\tdie \"'$path' already exists and is not a valid git repo\"\n \t\tfi\n@@ -583,10 +584,10 @@ cmd_summary() {\n \t\twhile read mod_src mod_dst sha1_src sha1_dst status name\n \t\tdo\n \t\t\t# Always show modules deleted or type-changed (blob<->module)\n-\t\t\ttest $status = D -o $status = T && echo \"$name\" && continue\n+\t\t\ttest $status = D -o $status = T && printf '%s\\n' \"$name\" && continue\n \t\t\t# Also show added or modified modules which are checked out\n \t\t\tGIT_DIR=\"$name/.git\" git-rev-parse --git-dir >/dev/null 2>&1 &&\n-\t\t\techo \"$name\"\n+\t\t\tprintf '%s\\n' \"$name\"\n \t\tdone\n \t)\n \n@@ -662,18 +663,21 @@ cmd_summary() {\n \t\tthen\n \t\t\tif test $mod_dst = 160000\n \t\t\tthen\n-\t\t\t\techo \"* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:\"\n+\t\t\t\tprintf '* %s %s(blob)->%s(submodule)%d:\\n' \\\n+\t\t\t\t\t\"$name\" \"$sha1_abbr_src\" \"$sha1_abbr_dst\" \"$total_commits\"\n \t\t\telse\n-\t\t\t\techo \"* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:\"\n+\t\t\t\tprintf '* %s %s(submodule)->%s(blob)%d:\\n' \\\n+\t\t\t\t\t\"$name\" \"$sha1_abbr_src\" \"$sha1_abbr_dst\" \"$total_commits\"\n \t\t\tfi\n \t\telse\n-\t\t\techo \"* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:\"\n+\t\t\tprintf '* %s %s->%s%d:\\n' \\\n+\t\t\t\t\"$name\" \"$sha1_abbr_src\" \"$sha1_abbr_dst\" \"$total_commits\"\n \t\tfi\n \t\tif test -n \"$errmsg\"\n \t\tthen\n \t\t\t# Don't give error msg for modification whose dst is not submodule\n \t\t\t# i.e. deleted or changed to blob\n-\t\t\ttest $mod_dst = 160000 && echo \"$errmsg\"\n+\t\t\ttest $mod_dst = 160000 && printf '%s\\n' \"$errmsg\"\n \t\telse\n \t\t\tif test $mod_src = 160000 -a $mod_dst = 160000\n \t\t\tthen\ndiff --git a/git-web--browse.sh b/git-web--browse.sh\nindex a578c3a..72b11db 100755\n--- a/git-web--browse.sh\n+++ b/git-web--browse.sh\n@@ -95,8 +95,8 @@ then\n \ttest -z \"$browser\" || break\n     done\n     if test -n \"$browser\" && ! valid_tool \"$browser\"; then\n-\techo >&2 \"git config option $opt set to unknown browser: $browser\"\n-\techo >&2 \"Resetting to default...\"\n+\tprintf >&2 'git config option %s set to unknown browser: %s\\n' \"$opt\" \"$browser\"\n+\tprintf >&2 'Resetting to default...\\n'\n \tunset browser\n     fi\n fi\n@@ -150,7 +150,7 @@ case \"$browser\" in\n \tcase \"$(basename \"$browser_path\")\" in\n \t    konqueror)\n \t\t# It's simpler to use kfmclient to open a new tab in konqueror.\n-\t\tbrowser_path=\"$(echo \"$browser_path\" | sed -e 's/konqueror$/kfmclient/')\"\n+\t\tbrowser_path=\"$(printf '%s\\n' \"$browser_path\" | sed -e 's/konqueror$/kfmclient/')\"\n \t\ttype \"$browser_path\" > /dev/null 2>&1 || die \"No '$browser_path' found.\"\n \t\teval \"$browser_path\" newTab \"$@\"\n \t\t;;\ndiff --git a/templates/hooks--commit-msg.sample b/templates/hooks--commit-msg.sample\nindex b58d118..b462353 100755\n--- a/templates/hooks--commit-msg.sample\n+++ b/templates/hooks--commit-msg.sample\n@@ -13,7 +13,7 @@\n # hook is more suited to it.\n #\n # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\\(.*>\\).*$/Signed-off-by: \\1/p')\n-# grep -qs \"^$SOB\" \"$1\" || echo \"$SOB\" >> \"$1\"\n+# grep -qs \"^$SOB\" \"$1\" || printf '%s\\n' \"$SOB\" >> \"$1\"\n \n # This example catches duplicate Signed-off-by lines.\n \ndiff --git a/templates/hooks--prepare-commit-msg.sample b/templates/hooks--prepare-commit-msg.sample\nindex 86b8f22..9a79995 100755\n--- a/templates/hooks--prepare-commit-msg.sample\n+++ b/templates/hooks--prepare-commit-msg.sample\n@@ -33,4 +33,4 @@ case \"$2,$3\" in\n esac\n \n # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\\(.*>\\).*$/Signed-off-by: \\1/p')\n-# grep -qs \"^$SOB\" \"$1\" || echo \"$SOB\" >> \"$1\"\n+# grep -qs \"^$SOB\" \"$1\" || printf '%s\\n' \"$SOB\" >> \"$1\"\ndiff --git a/test-sha1.sh b/test-sha1.sh\nindex 0f0bc5d..4843d77 100755\n--- a/test-sha1.sh\n+++ b/test-sha1.sh\n@@ -8,18 +8,18 @@ do\n \tcase \"$expect\" in '#'*) continue ;; esac\n \tactual=`\n \t\t{\n-\t\t\ttest -z \"$pfx\" || echo \"$pfx\"\n+\t\t\ttest -z \"$pfx\" || printf '%s\\n' \"$pfx\"\n \t\t\tdd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null |\n \t\t\tperl -pe 'y/\\000/g/'\n \t\t} | ./test-sha1 $cnt\n \t`\n \tif test \"$expect\" = \"$actual\"\n \tthen\n-\t\techo \"OK: $expect $cnt $pfx\"\n+\t\tprintf 'OK: %s %d %s\\n' \"$expect\" \"$cnt\" \"$pfx\"\n \telse\n-\t\techo >&2 \"OOPS: $cnt\"\n-\t\techo >&2 \"expect: $expect\"\n-\t\techo >&2 \"actual: $actual\"\n+\t\tprintf >&2 'OOPS: %d\\n' \"$cnt\"\n+\t\tprintf >&2 'expect: %s\\n' \"$expect\"\n+\t\tprintf >&2 'actual: %s\\n' \"$actual\"\n \t\texit 1\n \tfi\n done <<EOF\n@@ -53,13 +53,13 @@ while read cnt pfx\n do\n \tactual=`\n \t\t{\n-\t\t\ttest -z \"$pfx\" || echo \"$pfx\"\n+\t\t\ttest -z \"$pfx\" || printf '%s\\n' \"$pfx\"\n \t\t\tdd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null |\n \t\t\tperl -pe 'y/\\000/g/'\n \t\t} | sha1sum |\n \t\tsed -e 's/ .*//'\n \t`\n-\techo \"$actual $cnt $pfx\"\n+\tprintf '%s %d %s\\n' \"$actual $cnt $pfx\"\n done <<EOF\n 0\n 0 a\n"},{"id":"145168","messageId":"m2k4p58tum.fsf@igel.home","threadId":"24312","inReplyTo":"20100708171418.GA18229@burratino","subject":"Re: [RFC] war on echo in scripts","fromName":"Andreas Schwab","fromEmail":"schwab@linux-m68k.org","sentAt":"2010-07-08T22:57:37Z","receivedAt":"2010-07-08T22:57:37Z","isPatch":false,"sender":{"key":"schwab@linux-m68k.org","avatar":"https://avatars.githubusercontent.com/u/2175493?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> diff --git a/contrib/examples/git-clone.sh b/contrib/examples/git-clone.sh\n> index 547228e..e85ccda 100755\n> --- a/contrib/examples/git-clone.sh\n> +++ b/contrib/examples/git-clone.sh\n> @@ -28,7 +28,7 @@ use-separate-remote  compatibility, do not use\n>  no-separate-remote   compatibility, do not use\"\n>  \n>  die() {\n> -\techo >&2 \"$@\"\n> +\tprintf >&2 '%s\\n' \"$@\"\n\nYou probably want to use \"$*\" here, though it wouldn't matter for the\nuses of die in this file.\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n"},{"id":"145186","messageId":"20100709024633.GA13739@dert.cs.uchicago.edu","threadId":"24312","inReplyTo":"m2k4p58tum.fsf@igel.home","subject":"Re: [RFC] war on echo in scripts","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2010-07-09T02:46:33Z","receivedAt":"2010-07-09T02:46:33Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Andreas Schwab wrote:\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>>  die() {\n>> -\techo >&2 \"$@\"\n>> +\tprintf >&2 '%s\\n' \"$@\"\n>\n> You probably want to use \"$*\" here, though it wouldn't matter for the\n> uses of die in this file.\n\nHmm, maybe something like this would be easier.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\ndiff --git a/git-sh-setup.sh b/git-sh-setup.sh\nindex 6131670..b38b40b 100644\n--- a/git-sh-setup.sh\n+++ b/git-sh-setup.sh\n@@ -39,6 +39,10 @@ git_broken_path_fix () {\n \n # @@BROKEN_PATH_FIX@@\n \n+echo() {\n+\tprintf '%s\\n' \"$*\"\n+}\n+\n die() {\n \techo >&2 \"$@\"\n \texit 1\n-- \n"},{"id":"145190","messageId":"AANLkTimt16IbvGii3tP4LisxIALuUQM6BPemmCRjvYrN@mail.gmail.com","threadId":"24312","inReplyTo":"20100709024633.GA13739@dert.cs.uchicago.edu","subject":"Re: [RFC] war on echo in scripts","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-07-09T03:47:39Z","receivedAt":"2010-07-09T03:47:39Z","isPatch":false,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Thu, Jul 8, 2010 at 21:46, Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Hmm, maybe something like this would be easier.\n\nYou mean instead of the fairly huge patch you sent earlier? Sounds\nlike a good deal to me ;).\n\n-- \nCheers,\n\nSverre Rabbelier\n"},{"id":"145191","messageId":"7vocehmfe9.fsf@alter.siamese.dyndns.org","threadId":"24312","inReplyTo":"20100709024633.GA13739@dert.cs.uchicago.edu","subject":"Re: [RFC] war on echo in scripts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-09T04:46:06Z","receivedAt":"2010-07-09T04:46:06Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> Hmm, maybe something like this would be easier.\n>\n> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>\n> ---\n> diff --git a/git-sh-setup.sh b/git-sh-setup.sh\n> index 6131670..b38b40b 100644\n> --- a/git-sh-setup.sh\n> +++ b/git-sh-setup.sh\n> @@ -39,6 +39,10 @@ git_broken_path_fix () {\n>  \n>  # @@BROKEN_PATH_FIX@@\n>  \n> +echo() {\n> +\tprintf '%s\\n' \"$*\"\n> +}\n\nAs long as we don't have \"echo -n\" and \"echo -e\", this should\nwork, but...\n"},{"id":"145205","messageId":"AANLkTint6AfhwKdeizt-YVt4O6AZbLG9A5h6ipJyQyIQ@mail.gmail.com","threadId":"24312","inReplyTo":"20100709024633.GA13739@dert.cs.uchicago.edu","subject":"Re: [RFC] war on echo in scripts","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2010-07-09T11:52:35Z","receivedAt":"2010-07-09T11:52:35Z","isPatch":false,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"On Fri, Jul 9, 2010 at 02:46, Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Andreas Schwab wrote:\n>> Jonathan Nieder <jrnieder@gmail.com> writes:\n>\n>>>  die() {\n>>> -    echo >&2 \"$@\"\n>>> +    printf >&2 '%s\\n' \"$@\"\n>>\n>> You probably want to use \"$*\" here, though it wouldn't matter for the\n>> uses of die in this file.\n>\n> Hmm, maybe something like this would be easier.\n> [...]\n> +echo() {\n> +       printf '%s\\n' \"$*\"\n> +}\n> +\n\nOverriding builtins like this isn't portable.\n\n(Via H.Merijn Brand): This fails on the old HP-UX 10.20 bourne shell:\n\n    > cat xx.sh\n    #!/bin/sh\n\n    echo ()\n    {\n    perl -le'print \"Done!\"'\n    }\n\n    echo 1\n    echo Klaar\n    > sh xx.sh\n    xx.sh[3]: The operation is not allowed in a restricted shell.:\necho is a shell builtin.\n    1\n    Klaar\n"},{"id":"145220","messageId":"20100709141011.GA1543@burratino","threadId":"24312","inReplyTo":"AANLkTint6AfhwKdeizt-YVt4O6AZbLG9A5h6ipJyQyIQ@mail.gmail.com","subject":"Re: [RFC] war on echo in scripts","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2010-07-09T14:10:11Z","receivedAt":"2010-07-09T14:10:11Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n\n> (Via H.Merijn Brand): This fails on the old HP-UX 10.20 bourne shell:\n\nGit commands require a Posix-style shell (with support for $() among\nother things), which I suspect would rule out shells like the one\nyou’re talking about.  Still, portability is something to worry\nabout...\n\n>     > cat xx.sh\n>     #!/bin/sh\n> \n>     echo ()\n>     {\n>     perl -le'print \"Done!\"'\n>     }\n> \n>     echo 1\n>     echo Klaar\n>     > sh xx.sh\n>     xx.sh[3]: The operation is not allowed in a restricted shell.:\n> echo is a shell builtin.\n>     1\n>     Klaar\n\nThat’s weird.  Where did “restricted shell” come into it?\n\nThanks for the example,\nJonathan\n"},{"id":"145223","messageId":"20100709143011.GA1980@burratino","threadId":"24312","inReplyTo":"7vocehmfe9.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC] war on echo in scripts","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2010-07-09T14:30:11Z","receivedAt":"2010-07-09T14:30:11Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Junio C Hamano wrote:\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>> +echo() {\n>> +\tprintf '%s\\n' \"$*\"\n>> +}\n>\n> As long as we don't have \"echo -n\" and \"echo -e\", this should\n> work, but...\n\nYes, it’s ugly.  And it could make for surprises in user scripts.\n\n\"echo -n\" has been disallowed in core git since v0.99.9m^2~52 (Use\nprintf rather than echo -n, 2005-12-06).  Some echos seem to\nexpect '\\c' instead.\n\n\"echo -e\" is not allowed either, to support the Almquist shell\nand its descendents; see v1.2.0~4 (avoid echo -e, there are systems\nwhere it does not work, 2006-02-12).\n\nMaybe we can avoid impacting user scripts by making this opt-in?\ni.e., something like\n\n if test -n \"$GIT_FIX_ECHO\"\n then\n\techo() {\n\t\tprintf '%s\\n' \"$*\"\n\t}\n fi\n\nI am still not sure about the portability of overriding builtins.\n"},{"id":"145229","messageId":"7vk4p4mxcc.fsf@alter.siamese.dyndns.org","threadId":"24312","inReplyTo":"20100709143011.GA1980@burratino","subject":"Re: [RFC] war on echo in scripts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-09T16:30:43Z","receivedAt":"2010-07-09T16:30:43Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> Junio C Hamano wrote:\n>> Jonathan Nieder <jrnieder@gmail.com> writes:\n>\n>>> +echo() {\n>>> +\tprintf '%s\\n' \"$*\"\n>>> +}\n>>\n>> As long as we don't have \"echo -n\" and \"echo -e\", this should\n>> work, but...\n>\n> Yes, it’s ugly.  And it could make for surprises in user scripts.\n\nThe latter could be avoided with \"s/echo/sane_echo/\"; we do that already\nfor grep don't we?\n"}]}