From: Mark Wooding <mdw@xxxxxxxxxxxxxxxx> Some words, e.g., `match', are special to expr(1), and cause strange parsing effects. Track down all uses of expr and mangle the arguments so that this isn't a problem. Signed-off-by: Mark Wooding <mdw@xxxxxxxxxxxxxxxx> --- Amusing one, this. I hacked on one of my projects, messing with a simple glob matching function. Being uncreative, I called my topic branch `match'. When I was ready, I switched back to my master branch and said $ git pull . match Already up-to-date. Oh. I checked. Nope, not up-to-date. I tried $ git merge fast HEAD match, and that and that did the right thing. But I was puzzled. I fired up the git-bisect machinery and tried to find a good version to no avail. And then, comparing `sh -x' traces of git-fetch, I noticed what had gone wrong. There's a line in git-parse-remote.sh, in canon_refs_list_for_fetch, which says expr "$ref" : '.*:' >/dev/null || ref="${ref}:" In my case, $ref is `match', so this expands to expr match : '.*:' >... Unfortunately, GNU expr has a magic keyword `match'. So what this does is compare `:' to the regexp `.*:', which /succeeds/, even though POSIX expr without the `match' keyword would do the right thing and fail. So $ref never has a `:' appended, which makes the later parsing fail, and all sorts of strange things happen. This patch puts magical extra characters in expr regexp calls throughout the shell bits of GIT, to robustify them against this kind of crapness. There's a small chance I got something wrong while making this fix. I was fairly careful, though, and ran the test suite without any problems. I also checked Cogito, though that has no truck with expr. --- git-cherry.sh | 2 +- git-clone.sh | 6 +++--- git-commit.sh | 4 ++-- git-fetch.sh | 18 +++++++++--------- git-format-patch.sh | 4 ++-- git-merge-one-file.sh | 2 +- git-parse-remote.sh | 20 ++++++++++---------- git-rebase.sh | 2 +- git-tag.sh | 2 +- 9 files changed, 30 insertions(+), 30 deletions(-) diff --git a/git-cherry.sh b/git-cherry.sh index 1a62320..f0e8831 100755 --- a/git-cherry.sh +++ b/git-cherry.sh @@ -20,7 +20,7 @@ case "$1" in -v) verbose=t; shift ;; esa case "$#,$1" in 1,*..*) - upstream=$(expr "$1" : '\(.*\)\.\.') ours=$(expr "$1" : '.*\.\.\(.*\)$') + upstream=$(expr "z$1" : 'z\(.*\)\.\.') ours=$(expr "z$1" : '.*\.\.\(.*\)$') set x "$upstream" "$ours" shift ;; esac diff --git a/git-clone.sh b/git-clone.sh index c013e48..0805168 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -38,12 +38,12 @@ Perhaps git-update-server-info needs to } while read sha1 refname do - name=`expr "$refname" : 'refs/\(.*\)'` && + name=`expr "z$refname" : 'zrefs/\(.*\)'` && case "$name" in *^*) continue;; esac if test -n "$use_separate_remote" && - branch_name=`expr "$name" : 'heads/\(.*\)'` + branch_name=`expr "z$name" : 'zheads/\(.*\)'` then tname="remotes/$origin/$branch_name" else @@ -346,7 +346,7 @@ then # new style repository with a symref HEAD). # Ideally we should skip the guesswork but for now # opt for minimum change. - head_sha1=`expr "$head_sha1" : 'ref: refs/heads/\(.*\)'` + head_sha1=`expr "z$head_sha1" : 'zref: refs/heads/\(.*\)'` head_sha1=`cat "$GIT_DIR/$remote_top/$head_sha1"` ;; esac diff --git a/git-commit.sh b/git-commit.sh index bd3dc71..01c73bd 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -549,8 +549,8 @@ fi >>"$GIT_DIR"/COMMIT_EDITMSG # Author if test '' != "$force_author" then - GIT_AUTHOR_NAME=`expr "$force_author" : '\(.*[^ ]\) *<.*'` && - GIT_AUTHOR_EMAIL=`expr "$force_author" : '.*\(<.*\)'` && + GIT_AUTHOR_NAME=`expr "z$force_author" : 'z\(.*[^ ]\) *<.*'` && + GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` && test '' != "$GIT_AUTHOR_NAME" && test '' != "$GIT_AUTHOR_EMAIL" || die "malformatted --author parameter" diff --git a/git-fetch.sh b/git-fetch.sh index 954901d..711650f 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -112,7 +112,7 @@ append_fetch_head () { *) note_="$remote_name of " ;; esac - remote_1_=$(expr "$remote_" : '\(.*\)\.git/*$') && + remote_1_=$(expr "z$remote_" : 'z\(.*\)\.git/*$') && remote_="$remote_1_" note_="$note_$remote_" @@ -245,22 +245,22 @@ fetch_main () { # These are relative path from $GIT_DIR, typically starting at refs/ # but may be HEAD - if expr "$ref" : '\.' >/dev/null + if expr "z$ref" : 'z\.' >/dev/null then not_for_merge=t - ref=$(expr "$ref" : '\.\(.*\)') + ref=$(expr "z$ref" : 'z\.\(.*\)') else not_for_merge= fi - if expr "$ref" : '\+' >/dev/null + if expr "z$ref" : 'z\+' >/dev/null then single_force=t - ref=$(expr "$ref" : '\+\(.*\)') + ref=$(expr "z$ref" : 'z\+\(.*\)') else single_force= fi - remote_name=$(expr "$ref" : '\([^:]*\):') - local_name=$(expr "$ref" : '[^:]*:\(.*\)') + remote_name=$(expr "z$ref" : 'z\([^:]*\):') + local_name=$(expr "z$ref" : 'z[^:]*:\(.*\)') rref="$rref$LF$remote_name" @@ -276,7 +276,7 @@ fetch_main () { print "$u"; ' "$remote_name") head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") && - expr "$head" : "$_x40\$" >/dev/null || + expr "z$head" : "z$_x40\$" >/dev/null || die "Failed to fetch $remote_name from $remote" echo >&2 Fetching "$remote_name from $remote" using http git-http-fetch -v -a "$head" "$remote/" || exit @@ -362,7 +362,7 @@ fetch_main () { break ;; esac done - local_name=$(expr "$found" : '[^:]*:\(.*\)') + local_name=$(expr "z$found" : 'z[^:]*:\(.*\)') append_fetch_head "$sha1" "$remote" \ "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" done diff --git a/git-format-patch.sh b/git-format-patch.sh index 2ebf7e8..c7133bc 100755 --- a/git-format-patch.sh +++ b/git-format-patch.sh @@ -126,8 +126,8 @@ for revpair do case "$revpair" in ?*..?*) - rev1=`expr "$revpair" : '\(.*\)\.\.'` - rev2=`expr "$revpair" : '.*\.\.\(.*\)'` + rev1=`expr "z$revpair" : 'z\(.*\)\.\.'` + rev2=`expr "z$revpair" : 'z.*\.\.\(.*\)'` ;; *) rev1="$revpair^" diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index 5349a1c..5619409 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -26,7 +26,7 @@ # fi if test -f "$4"; then rm -f -- "$4" && - rmdir -p "$(expr "$4" : '\(.*\)/')" 2>/dev/null || : + rmdir -p "$(expr "z$4" : 'z\(.*\)/')" 2>/dev/null || : fi && exec git-update-index --remove -- "$4" ;; diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 63f2281..65c66d5 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -8,8 +8,8 @@ get_data_source () { case "$1" in */*) # Not so fast. This could be the partial URL shorthand... - token=$(expr "$1" : '\([^/]*\)/') - remainder=$(expr "$1" : '[^/]*/\(.*\)') + token=$(expr "z$1" : 'z\([^/]*\)/') + remainder=$(expr "z$1" : 'z[^/]*/\(.*\)') if test -f "$GIT_DIR/branches/$token" then echo branches-partial @@ -43,8 +43,8 @@ get_remote_url () { branches) sed -e 's/#.*//' "$GIT_DIR/branches/$1" ;; branches-partial) - token=$(expr "$1" : '\([^/]*\)/') - remainder=$(expr "$1" : '[^/]*/\(.*\)') + token=$(expr "z$1" : 'z\([^/]*\)/') + remainder=$(expr "z$1" : 'z[^/]*/\(.*\)') url=$(sed -e 's/#.*//' "$GIT_DIR/branches/$token") echo "$url/$remainder" ;; @@ -77,13 +77,13 @@ canon_refs_list_for_fetch () { force= case "$ref" in +*) - ref=$(expr "$ref" : '\+\(.*\)') + ref=$(expr "z$ref" : 'z\+\(.*\)') force=+ ;; esac - expr "$ref" : '.*:' >/dev/null || ref="${ref}:" - remote=$(expr "$ref" : '\([^:]*\):') - local=$(expr "$ref" : '[^:]*:\(.*\)') + expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:" + remote=$(expr "z$ref" : 'z\([^:]*\):') + local=$(expr "z$ref" : 'z[^:]*:\(.*\)') case "$remote" in '') remote=HEAD ;; refs/heads/* | refs/tags/* | refs/remotes/*) ;; @@ -97,7 +97,7 @@ canon_refs_list_for_fetch () { *) local="refs/heads/$local" ;; esac - if local_ref_name=$(expr "$local" : 'refs/\(.*\)') + if local_ref_name=$(expr "z$local" : 'zrefs/\(.*\)') then git-check-ref-format "$local_ref_name" || die "* refusing to create funny ref '$local_ref_name' locally" @@ -171,7 +171,7 @@ get_remote_refs_for_fetch () { resolve_alternates () { # original URL (xxx.git) - top_=`expr "$1" : '\([^:]*:/*[^/]*\)/'` + top_=`expr "z$1" : 'z\([^:]*:/*[^/]*\)/'` while read path do case "$path" in diff --git a/git-rebase.sh b/git-rebase.sh index 5956f06..86dfe9c 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -94,7 +94,7 @@ case "$#" in ;; *) branch_name=`git symbolic-ref HEAD` || die "No current branch" - branch_name=`expr "$branch_name" : 'refs/heads/\(.*\)'` + branch_name=`expr "z$branch_name" : 'zrefs/heads/\(.*\)'` ;; esac branch=$(git-rev-parse --verify "${branch_name}^0") || exit diff --git a/git-tag.sh b/git-tag.sh index 76e51ed..dc6aa95 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -75,7 +75,7 @@ git-check-ref-format "tags/$name" || object=$(git-rev-parse --verify --default HEAD "$@") || exit 1 type=$(git-cat-file -t $object) || exit 1 tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1 -: ${username:=$(expr "$tagger" : '\(.*>\)')} +: ${username:=$(expr "z$tagger" : 'z\(.*>\)')} trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0 -- [mdw] - : send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html