【问题标题】:How can I manually remove a blob object from a tree in Git?如何从 Git 中的树中手动删除 blob 对象?
【发布时间】:2012-03-20 16:16:05
【问题描述】:

说我运行git ls-tree -r master时有类似的情况:

100644 blob a450cb6b6371494ab4b3da450f6e7d543bfe3493    FooBar/readme.txt
100644 blob a339338d7ad5113740244e7f7d3cbb236cb47115    Foobar/readme.txt

如何从这个树对象中删除第二个 blob?

我假设这可以通过 git rm Foobar/readme.txt 在 POSIX 系统上完成。我如何在 Windows 上做同样的事情?

【问题讨论】:

  • 显然您遇到了区分大小写的问题。这里的问题是,这是 POSIX 系统上的两个不同文件,但在您的 Windows 系统上是“相同”的文件。这意味着这与stackoverflow.com/questions/2528589/… 相同
  • 类似,但我也在寻找一般意义上的解决方案。我认为诀窍就是能够手动修改 git 中的树对象。
  • 你不能真正“修改树”,但你可以构造一个指向 new 树(指向另一棵树等)的新提交没有多余的东西。到目前为止,最简单的方法是将 repo 克隆到 POSIX 系统,只需使用常规 git 命令,进行提交,然后将提交获取到 Windows repo。
  • 我正在寻找更多类似git read-treegit write-tree 的东西。 progit.org/book/ch9-2.html
  • 我意识到我可以在我的 Mac 上模拟 Windows 行为,默认情况下它具有保留大小写但不区分大小写的文件系统。我可以做git rm --cached FooBar/readme.txtgit rm --cached Foobar/readme.txt 并且可以正确删除其中一个。 Windows 上的操作是否不同?

标签: git


【解决方案1】:

带有 --index-filter 的 git filter-branch 可能会起作用,因为您是在索引上而不是在工作树上操作。尝试类似:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch Foobar/readme.txt' HEAD

【讨论】:

  • 不幸的是,它会尝试删除它认为是当前的那个。因此,即使您尝试git rm -f Foobar/readme.txt,它也只会删除 FooBar/readme.txt。
  • 啊,当然。 Windoze 和它的文件系统很糟糕。试图为您找到更好的答案,但我觉得这会相当棘手。你甚至可以毫无问题地将该存储库克隆到 Windows 上吗?
【解决方案2】:

好的,所以,我花了一点时间和精力在 MacOS 上进行了测试,它在折叠情况下也有类似的问题。

我不知道所有版本的 git 是否“足够相同”和/或 Windows git 的工作方式是否相同,但这个脚本确实可以解决问题,无需比ls-tree -r 更深入地了解 git 管道和cat-filerm --cached

该脚本也只是经过轻微测试。 (注意:标签被打碎了,cmd-C/cmd-V 粘贴了标签,但我不得不缩进stackoverflow。所以文件缩进在下面搞砸了......懒得在这里修复。)

#! /bin/bash

usage()
{
cat << EOF
usage: $0 [-h] [-r] [branch]

-h: print usage help
-r: rename ALL colliding files to their hashes
EOF
}

DO_RENAME=false
while getopts "hr" opt; do
case $opt in
h) usage; exit 0;;
r) DO_RENAME=true;;
*) usage 1>&2; exit 1;;
esac
done
shift $(($OPTIND - 1))

case $# in
0) branch=HEAD;;
1) branch=$1;;
*) usage
esac

# literal tab, so that it's easily distinguished from spaces
TAB=$(printf \\t)

branch=$(git rev-parse --symbolic $branch) || exit

tempfile=$(mktemp -t git-casecoll)
trap "rm -f $tempfile; exit 0" 0
trap "rm -f $tempfile; exit 1" 1 2 3 15

# First, let's find out whether there *are* any file name
# case collisions in the tree.
git ls-tree -r $branch > $tempfile
nfiles=$(wc -l < $tempfile | sed 's/  *//g')
n2=$(sort "-t$TAB" -k2 -f -u $tempfile | wc -l | sed 's/  *//g')
if [ $nfiles -eq $n2 ]; then
echo no collisions found
exit 0
fi
echo "$(($nfiles - $n2)) collision(s) found"

# functions needed below

# decode git escapes in pathnames
decode_git_pathname()
{
local path="$1"
case "$path" in
\"*\")
    # strip off leading and trailing double quotes
    path=${path#\"}
    path=${path%\"}
    # change % into %%
    path=${path/\%/%%}
    # and then interpret backslashes with printf
    printf -- "$path";;
*)
    # not encoded, just print it as is
    printf %s "$path";;
esac
}

show_or_do_rename()
{
local mode=$1 path="$(decode_git_pathname "$2")" sha1=$3
local renamed_to="$(dirname "$path")/$sha1"
local ftype=${mode:0:2}

if [ $ftype != 10 ]; then
    echo "WARNING: I don't handle $ftype files ($mode $path) yet"
    return 1
fi
if $DO_RENAME; then
    # git mv does not work, but git rm --cached does
    git rm --cached --quiet "$path"
    rm -f "$path"
    git cat-file -p $sha1 > "$renamed_to"
    chmod ${mode:2} "$renamed_to"
    git add "$renamed_to"
    echo "renamed: $path => $renamed_to"
else
    if [ $ftype != 10 ]; then
    echo "# I don't handle extracting a $ftype file ($mode) yet"
    else
    echo will: mv "$path" "$renamed_to"
    fi
fi
}

# Now we have to find which ones they were, which is more difficult.
# We still want the sorted ones with case folded, but we don't want
# to remove repeats, instead we want to detect them as we go.
#
# Note that Dir/file collides with both dir/file and dir/File,
# so if we're doing rename ops, we'll rename all three.  We also
# don't know if we're in a collision-group until we hit the second
# entry, so the first time we start doing a collision-group, we
# must rename two files, and from then on (in the same group) we
# only rename one.
prevpath=""
prevlow=""
prevsha=
in_coll=false
sort -f $tempfile |
while IFS="$TAB" read -r info git_path; do
    set -- $info
    mode=$1
    # otype=$2  -- we don't care about the object type?
    # it should always be "blob"
    sha1=$3
    lowered="$(printf %s "$git_path" | tr '[:upper:]' '[:lower:]')"
    if [ "$prevlow" = "$lowered" ]; then
    if $in_coll; then
        echo "      and: $prevpath vs $git_path"
        show_or_do_rename $mode "$git_path" $sha1
    else
        echo "collision: $prevpath vs $git_path"
        show_or_do_rename $mode "$prevpath" $prevsha
        show_or_do_rename $mode "$git_path" $sha1
        in_coll=true
    fi
    else
    prevlow="$lowered"
    prevpath="$git_path"
    prevsha=$sha1
    in_coll=false
    fi
done

这是一个示例运行。我在 Linux 机器上创建了一个“对 Windows 不利”的存储库,然后将其克隆到 Mac。

$ git clone ...
Initialized empty Git repository in /private/tmp/caseissues/.git/
remote: Counting objects: 16, done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 16 (delta 1), reused 0 (delta 0)
Receiving objects: 100% (16/16), done.
Resolving deltas: 100% (1/1), done.
$ cd caseissues
$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   FooBar/readme.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git-casecoll.sh 
1 collision(s) found
collision: FooBar/readme.txt vs Foobar/readme.txt
will: mv FooBar/readme.txt FooBar/31892d33f4a57bff0acd064be4bb5a01143dc519
will: mv Foobar/readme.txt Foobar/591415e1e03bd429318f4d119b33cb76dc334772
$ git-casecoll.sh -r
1 collision(s) found
collision: FooBar/readme.txt vs Foobar/readme.txt
renamed: FooBar/readme.txt => FooBar/31892d33f4a57bff0acd064be4bb5a01143dc519
renamed: Foobar/readme.txt => Foobar/591415e1e03bd429318f4d119b33cb76dc334772
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    FooBar/readme.txt -> FooBar/31892d33f4a57bff0acd064be4bb5a01143dc519
#   renamed:    Foobar/readme.txt -> Foobar/591415e1e03bd429318f4d119b33cb76dc334772
#

(此时我选择我自己的名字来修复这些问题——注意,我让它自动完成,并且不得不再次尝试,手动将 FooBar 中的 b 小写,因为大小写怪异)

$ git mv FooBar/31892d33f4a57bff0acd064be4bb5a01143dc519 FooBar/readme_A.txt
$ git mv FooBar/591415e1e03bd429318f4d119b33cb76dc334772 FooBar/readme_B.txt
fatal: not under version control, source=FooBar/591415e1e03bd429318f4d119b33cb76dc334772, destination=FooBar/readme_B.txt
$ git mv Foobar/591415e1e03bd429318f4d119b33cb76dc334772 FooBar/readme_B.txt
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    FooBar/readme.txt -> FooBar/readme_A.txt
#   renamed:    Foobar/readme.txt -> FooBar/readme_B.txt
#
$ git commit -m 'fix file name case issue'
[master 4ef3a55] fix file name case issue
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename FooBar/{readme.txt => readme_A.txt} (100%)
 rename Foobar/readme.txt => FooBar/readme_B.txt (100%)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-01-02
    • 2019-02-06
    • 1970-01-01
    • 2011-05-17
    • 1970-01-01
    • 2010-12-26
    • 2011-11-17
    • 1970-01-01
    相关资源
    最近更新 更多