有一次我添加了一个几乎包含二进制文件的巨大目录,并使用 LFS 跟踪它。 同时,我决定取消跟踪目录并显式跟踪二进制文件。 因此,一些文本和配置文件又恢复为未跟踪(这是有意的)。
但是,由于 git 不知道这种情况,现在将使用以前的指针文件显示差异。是否可以使用一些更智能的 diff 来识别和解析指针文件,即使它们当前未被跟踪?
当
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