以前跟踪的文件的 git diff

问题描述 投票:0回答:1

有一次我添加了一个几乎包含二进制文件的巨大目录,并使用 LFS 跟踪它。 同时,我决定取消跟踪目录并显式跟踪二进制文件。 因此,一些文本和配置文件又恢复为未跟踪(这是有意的)。

但是,由于 git 不知道这种情况,现在将使用以前的指针文件显示差异。是否可以使用一些更智能的 diff 来识别和解析指针文件,即使它们当前未被跟踪?

我发现了类似的讨论here,但我不明白所有细节。但有人提到了这个例子,它看起来与我的想法非常相似。

git git-lfs
1个回答
0
投票

git lfs
跟踪文件时,会创建 2 个对象。一个是指针文件,另一个是文件的真实内容。指针文件存储在
.git/objects
中,实际内容存储在
.git/lfs/objects
中。

当文件未被

git lfs
跟踪时,仅创建 1 个对象,其真实内容保存在
.git/objects
中。

这是 bash 中的示例。为了每次都能重现相同的对象,我使用假名字、电子邮件和日期设置了 git 环境变量。我在 Windows 10 上的 git-bash 中测试了以下脚本和命令。

#!/bin/bash

export GIT_AUTHOR_NAME=StackOverflow
export [email protected]
export GIT_AUTHOR_DATE="Thu May 9 16:55:28 2024 +0800"
export GIT_COMMITTER_NAME=StackOverflow
export [email protected]
export GIT_COMMITTER_DATE="Thu May 9 16:55:28 2024 +0800"

rm -rf foo
git init foo
cd foo
git lfs track "*.txt"
echo hello > a.txt
git add .gitattributes a.txt
git commit -m "hello in lfs"
> .gitattributes
git commit -a -m"disable lfs on txt"
echo world >> a.txt
git commit -a -m "world without lfs"
git log

unset GIT_AUTHOR_NAME
unset GIT_AUTHOR_EMAIL
unset GIT_AUTHOR_DATE
unset GIT_COMMITTER_NAME
unset GIT_COMMITTER_EMAIL
unset GIT_COMMITTER_DATE

最后打印日志。

commit 5541d7cb72e38236ed76378a6cc39fd120edb03e (HEAD -> master)
Author: StackOverflow <[email protected]>
Date:   Thu May 9 16:55:28 2024 +0800

    world without lfs

commit 1bb4202c14f8d29d8e49afe8cf8be72cff510940
Author: StackOverflow <[email protected]>
Date:   Thu May 9 16:55:28 2024 +0800

    disable lfs on txt

commit f8587ce7a3f0eb3825c215f5b67bcb4e7f11fc7d
Author: StackOverflow <[email protected]>
Date:   Thu May 9 16:55:28 2024 +0800

    hello in lfs

f8587ce7a3f0eb3825c215f5b67bcb4e7f11fc7d
中,
a.txt
git lfs
跟踪。
f8587ce7a3f0eb3825c215f5b67bcb4e7f11fc7d:a.txt
是一个指针文件。

git rev-parse f8587ce7a3f0eb3825c215f5b67bcb4e7f11fc7d:a.txt
, 我们可以发现这个指针文件的git sha1sum是
fa61e1ef71c9908c0926776a4aa3504b3a27f159
。它存储为
.git/objects/fa/61e1ef71c9908c0926776a4aa3504b3a27f159
。如果我们使用
cat .git/objects/fa/61e1ef71c9908c0926776a4aa3504b3a27f159
显示其内容,我们将看到它已被编码。
file .git/objects/fa/61e1ef71c9908c0926776a4aa3504b3a27f159
报告
zlib compressed data

使用

git show f8587ce7a3f0eb3825c215f5b67bcb4e7f11fc7d:a.txt
git show fa61e1ef71c9908c0926776a4aa3504b3a27f159
,打印指针文件的内容。

version https://git-lfs.github.com/spec/v1
oid sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
size 6

f8587ce7a3f0eb3825c215f5b67bcb4e7f11fc7d:a.txt
的真实内容存储在对象
5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
中,该对象存储在
.git/lfs/objects/58/91/5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03

通过

cat .git/lfs/objects/58/91/5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
,我们可以看到它的内容。

hello

5541d7cb72e38236ed76378a6cc39fd120edb03e
HEAD
中,
a.txt
不再被
git lfs
跟踪。同样,我们可以发现它的 sha1sum 是
94954abda49de8615a048f8d2e64b5de848e27a1
,它存储在
.git/objects/94/954abda49de8615a048f8d2e64b5de848e27a1

通过

git show HEAD:a.txt
,我们可以看到它的内容。

hello
world

当我们使用

git diff HEAD~2 HEAD a.txt
时,它会比较对象
fa61e1ef71c9908c0926776a4aa3504b3a27f159
94954abda49de8615a048f8d2e64b5de848e27a1
。结果是出乎意料的,而且毫无意义。

diff --git a/a.txt b/a.txt
index fa61e1e..94954ab 100644
--- a/a.txt
+++ b/a.txt
@@ -1,3 +1,2 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
-size 6
+hello
+world

预期输出是两个版本实际内容的差异。所以,首先我们需要获取真实的内容。

在问题示例的git-lfs-diff.sh中,该函数获取保存真实内容的路径。

object() {
    Rev=$1
    File=$2
    Object=""

    Oid=$(git show $Rev:$File 2> /dev/null | grep "sha256" | cut -d ":" -f 2)
    if [ "$Oid" != "" ]; then
        Oid12=$(echo $Oid | cut -b 1-2)
        Oid34=$(echo $Oid | cut -b 3-4)
        Object=.git/lfs/objects/$Oid12/$Oid34/$Oid
        if [ ! -e "$Object" ] ; then
            echo "Missing file $File at revision $Rev"
            exit 2
        fi
    fi

    echo "$Object"
}

但是,它假设该文件由

git lfs
跟踪。如果文件未被
git lfs
跟踪,则无法找到路径,因为它不存在。

修改脚本(未经过充分测试,可能有错误)。

object() {
    Rev=$1
    File=$2
    Object=""

    Oid=$(git show $Rev:$File 2> /dev/null | grep "sha256" | cut -d ":" -f 2)
    if [ "$Oid" != "" ]; then
        Oid12=$(echo $Oid | cut -b 1-2)
        Oid34=$(echo $Oid | cut -b 3-4)
        Object=.git/lfs/objects/$Oid12/$Oid34/$Oid
        if [ ! -e "$Object" ] ; then
            echo "Missing file $File at revision $Rev"
            exit 2
        fi
    else
        # If it's not a pointer file, write the content to a temp file
        Object=$(mktemp --suffix=.tobedeleted)
        git show $Rev:$File > $Object
    fi

    echo "$Object"
}

diff -urN "$ObjectA" "$ObjectB"
# Remove the temp files
if [[ "$ObjectA" =~ tobedeleted$ ]];then
    rm -f $ObjectA
fi
if [[ "$ObjectB" =~ tobedeleted$ ]];then
    rm -f $ObjectB
fi

您可以将脚本命名为

git-lfs-diff
(无扩展名),使其可执行并将其放在
$PATH
中的任何路径下,如
/usr/bin
。运行
git lfs-diff HEAD~2 HEAD a.txt
,我们可以得到预期的输出。

--- .git/lfs/objects/58/91/5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03     2024-05-09 17:23:00.945185000 +0800
+++ /tmp/tmp.avSyUswsbX.tobedeleted     2024-05-09 18:02:14.896286200 +0800
@@ -1 +1,2 @@
 hello
+world

diff -urN
可以替换为
git diff --no-index
,以便生成更熟悉的 diff 输出。

diff --git a/D:/Users/ElpieKay/AppData/Local/Temp/tmp.XZZSA2BDW0.tobedeleted b/D:/Users/ElpieKay/AppData/Local/Temp/tmp.SLQbWhYzfm.tobedeleted
index ce01362..94954ab 100644
--- a/D:/Users/ElpieKay/AppData/Local/Temp/tmp.XZZSA2BDW0.tobedeleted
+++ b/D:/Users/ElpieKay/AppData/Local/Temp/tmp.SLQbWhYzfm.tobedeleted
@@ -1 +1,2 @@
 hello
+world

我不确定 lfs 对象是否会像 git 对象一样打包。当 git 对象被打包时,就像通过

git gc
一样,它们不再直接位于
.git/objects
下。它们将存储在
.git/objects/*.pack
下的包文件中。如果 lfs 对象也可以打包,那么像
.git/lfs/objects/58/91/5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
这样的路径就不再可靠了。我们可以借助
git lfs smudge
从指针文件中获取真实内容,然后将其写入临时文件。

git show f8587ce7a3f0eb3825c215f5b67bcb4e7f11fc7d:a.txt | git lfs smudge

hello

稍微修改一下功能

object()
未完全测试,可能有错误)。

object() {
    Rev=$1
    File=$2
    Object=""

    Oid=$(git show $Rev:$File 2> /dev/null | grep "sha256" | cut -d ":" -f 2)
    # Dump the real content to a temp file
    if [ "$Oid" != "" ]; then
        Object=$(mktemp --suffix=.tobedeleted)
        git show $Rev:$File | git lfs smudge > $Object
    else
        Object=$(mktemp --suffix=.tobedeleted)
        git show $Rev:$File > $Object
    fi

    echo "$Object"
}

diff 输出仍然存在问题。比较文件的路径是临时文件而不是

a.txt
。查看差异是可以的,但将其作为补丁应用时会引发错误。目前我还不知道如何解决。

另一个问题是该脚本仅比较两个跟踪的版本。如果您想将跟踪版本与非跟踪版本进行比较,该函数需要额外的代码。我们总是可以先将内容转储到一个文件中,然后比较这两个文件。仅供参考,要引用索引中的版本,我们可以使用

:0:a.txt

# Compare the version in HEAD~2 with the version in the index
git lfs-diff HEAD~2 :0 a.txt
© www.soinside.com 2019 - 2024. All rights reserved.