Git可以将ZIP文件视为目录,将ZIP中的文件视为blob吗?

vcudknz3  于 2023-04-04  发布在  Git
关注(0)|答案(8)|浏览(157)

场景

想象一下,我被迫处理一些总是存储在.zip文件中的文件。ZIP文件中的一些文件是小文本文件,经常更改,而其他文件则较大,但幸运的是相当静态(例如图像)。
如果我想把这些ZIP文件放在Git仓库中,每个ZIP文件都被视为一个blob,所以每当我提交时,仓库就会按ZIP文件的大小增长......即使里面只有一个小文本文件发生了变化!

为什么这是现实的

Microsoft Word 2007/2010 .docx和Excel .xlsx文件是ZIP文件...
"我想要的"
有没有一种方法可以告诉Git不要把ZIP文件当作文件,而是当作目录,把它们的内容当作文件?

优点

我意识到,如果没有额外的元数据,这将导致一些歧义:在git checkout上,Git必须决定是否将foo.zip/bar.txt创建为常规目录中的文件或ZIP文件。然而,我认为这可以通过配置选项来解决。

两个想法如何可以做到(如果它还不存在)

  • 使用Git内部的minizipIO::Compress::Zip等库
  • 以某种方式添加文件系统层,这样Git实际上会将ZIP文件视为开始的目录
wvyml7n5

wvyml7n51#

这是不存在的,但它很容易在当前的框架中存在。就像Git在执行diff时显示二进制或ASCII文件的行为不同一样,它可以通过配置界面被告知对某些文件类型提供特殊处理。
如果你不想改变代码库(虽然这是一个很酷的想法),你也可以自己编写脚本,使用pre-commit and post-checkout hooks解压并存储文件,然后在 checkout 时将它们返回到.zip状态。你必须将操作限制在git add指定的那些文件blob/索引上。
无论哪种方式都需要做一些工作--这只是一个问题,即其他Git命令是否知道发生了什么并能很好地运行。

vddsk6oq

vddsk6oq2#

使用**bup(详见GitMinutes #24
它是唯一一个被设计用来处理大型(甚至是非常非常大)文件的类git系统,这意味着每个版本的zip文件只会从它的delta增加repo(而不是一个完整的额外副本)
结果是一个实际的git repo,一个普通的Git命令可以读取。
我在“git with large files”中详细介绍了
bup与Git的区别。
任何其他解决方法(如
git-annex**)都不完全令人满意,详见“git-annex with large files”。

bvjxkvbb

bvjxkvbb3#

Zippey -使用 *Git文件过滤器 * 的解决方案

我的解决方案是使用过滤器将ZIP文件“扁平化”为一个整体的,扩展的(可能是巨大的)文本文件。在git add/commit过程中,ZIP文件将自动扩展为这种文本格式,以进行正常的文本区分,并在结帐过程中,它会自动再次压缩。
文本文件是由记录组成的,每个记录代表ZIP文件中的一个文件。因此,您可以认为此文本文件是原始ZIP文件的基于文本的图像。如果ZIP文件中的文件确实是文本,则复制到文本文件中;否则,在复制到文本格式文件之前,它是Base64编码的。这使文本文件始终是文本文件。
虽然这个过滤器并没有把ZIP文件中的每个文件都变成一个blob,但是文本文件被Map为一行到一行--这是diff的单位--而二进制文件的更改可以通过其对应的Base64的更新来表示。我认为这与OP所想象的是等价的。
有关详细信息和原型代码,您可以阅读以下链接:
Zippey Git file filter
此外,感谢启发我这个解决方案的地方:Description of how file filter works

a0zr77ik

a0zr77ik4#

来自 * Managing ZIP-based file formats in git *:
注意:根据Ruben的评论,这只是关于获得适当的diff,而不是关于提交解压缩文件。
打开您的~/.gitconfig文件(如果还不存在则创建)并添加以下节:

[diff "zip"]
textconv = unzip -c -a

它使用“unzip -c -a FILENAME”将zip文件转换为ASCII文本(unzip -c unzips to STDOUT)。

*.pptx diff=zip

它告诉git使用配置文件中的zip-diffing描述来匹配给定的掩码(在本例中,所有文件都以.pptx结尾)。现在git diff会自动解压缩文件并区分ASCII输出,这比“二进制文件不同”好一点。另一方面,对于pptx文件对应的XML的复杂混乱,它没有太大的帮助,但是对于包含文本的ZIP文件(例如源代码归档),这实际上是非常方便的。

pb3skfrl

pb3skfrl5#

java工具ReZipDoc,类似于Zippey by sippey,允许使用Git以更好的方式处理ZIP文件。

工作原理

当添加/提交一个基于ZIP的文件时,Rezip在将其添加到索引/提交之前,将其解压缩并重新压缩。在未压缩的ZIP文件中,归档文件在其内容中显示为 * 原样 *(在每个文件之前都有一些二进制 meta信息)。如果这些归档文件是纯文本文件,则此方法将与Git配合使用。

福利

Rezip优于Zippey的主要优点是,存储在存储库中的实际文件仍然是ZIP文件。因此,在许多情况下,它仍然可以与相应的应用程序(例如Open Office)一起工作 * as-is *,即使它是在没有经过重新打包压缩过滤器的情况下获得的。

如何使用

在系统上安装过滤器:

mkdir -p ~/bin
cd ~/bin

# Download the filer executable
wget https://github.com/costerwi/rezip/blob/master/Rezip.class

# Install the add/commit filter
git config --global --replace-all filter.rezip.clean "java -cp ~/bin Rezip --store"

# (optionally) Install the checkout filter
    git config --global --add filter.rezip.smudge "java -cp ~/bin Rezip"

在您的存储库中使用过滤器,方法是向<repo-root>/.gitattributes文件中添加如下行:

[attr]textual     diff merge text
[attr]rezip       filter=rezip textual

# Microsoft Office
*.docx  rezip
*.xlsx  rezip
*.pptx  rezip
# OpenOffice
*.odt   rezip
*.ods   rezip
*.odp   rezip
# Misc
*.mcdx  rezip
*.slx   rezip

textual部分是这样的,这些文件实际上在差异中显示为文本文件。

5t7ly7z5

5t7ly7z56#

以下是我的方法:

  • 使用Git diff过滤器将归档文件替换为内容摘要
git config filter.zip.clean "unzip -v %f | tail -n +4 | head -n -2 | awk '{ print \$7,\$8 }' | grep -vE /$ | LC_ALL=C sort -sfk 2,2"
git config filter.zip.smudge "cat"
git config filter.zip.required true
  • 使用 pre-commit 钩子提取和添加归档内容:
#!/bin/sh
#
# Git archive extraction pre commit hook
#
# Created: 2021 by Vivien Richter <vivien-richter@outlook.de>
# License: CC-BY-4.0
# Version: 1.0.2

# Configuration
ARCHIVE_EXTENSIONS=$(cat .gitattributes | grep "zip" | tr -d [][:upper:] | cut -d " " -f1 | cut -d. -f2 | head -c -1 | tr "\n" "|")

# Processing
for STAGED_FILE in $(git diff --name-only --cached | grep -iE "\.($ARCHIVE_EXTENSIONS)$")
do
    # Deletes the old archive content
    rm -rf ".$(basename $STAGED_FILE).content"
    # Extracts the archive content, if the archive itself is not removed
    if [ -f "$STAGED_FILE" ]; then
        unzip -o $STAGED_FILE -d "$(dirname $STAGED_FILE)/.$(basename $STAGED_FILE).content"
    fi
    # Adds extracted or deleted archive content to the stage
    git add "$(dirname $STAGED_FILE)/.$(basename $STAGED_FILE).content"
done
  • 使用 post-checkout 钩子再次打包存档以供使用:
#!/bin/sh
#
# Git archive packing post checkout hook
#
# Created: 2021 by Vivien Richter <vivien-richter@outlook.de>
# License: CC-BY-4.0
# Version: 1.0.0

# Configuration
ARCHIVE_EXTENSIONS=$(cat .gitattributes | grep "zip" | tr -d [][:upper:] | cut -d " " -f1 | cut -d. -f2 | head -c -1 | tr "\n" "|")

# Processing
for EXTRACTED_ARCHIVE in $(git ls-tree -dr --full-tree --name-only HEAD | grep -iE "\.($ARCHIVE_EXTENSIONS)\.content$")
do
    # Gets filename
    FILENAME=$(dirname $EXTRACTED_ARCHIVE)/$(basename $EXTRACTED_ARCHIVE | cut -d. -f2- | awk -F '.content' '{ print $1 }')
    # Removes the dummy archive file
    rm $FILENAME
    # Jumps into the extracted archive
    cd $EXTRACTED_ARCHIVE
    # Creates the real archive file
    zip -r9 ../"$FILENAME" $(find . -type f)
    # Jumps back
    cd ..
done
  • .gitattributes文件中应用过滤器:
# Macro for all file types that should be treated as ZIP archives.
[attr]zip text filter=zip

# Forces `LF` as line endings for text based files inside ZIP archives.
**/*.content/** text=auto eol=lf

# OpenDocument
*.[oO][dD][tT] zip
*.[oO][dD][sS] zip
*.[oO][dD][gG] zip
*.[oO][dD][pP] zip
*.[oO][dD][mM] zip

# Krita
*.[kK][rR][aA] zip

# VRoid Studio
*.[vV][rR][oO][iI][dD] zip
*.[fF][vV][pP] zip
  • .gitattributes文件添加一些二进制处理:
# Macro for all binary files that should use Git LFS.
[attr]bin -text filter=lfs diff=lfs merge=lfs lockable

# Images
*.[jJ][pP][gG] bin
*.[jJ][pP][eE][gG] bin
*.[pP][nN][gG] bin
*.[aA][pP][nN][gG] bin
*.[gG][iI][fF] bin
*.[bB][mM][pP] bin
*.[tT][gG][aA] bin
*.[tT][iI][fF] bin
*.[tT][iI][fF][fF] bin
*.[sS][vV][gG][zZ] bin
  • .gitignore文件中添加一些内容:
# Auto generated LFS hooks
.githooks/pre-push

# Temporary files
*~

1.一些配置:
1.安装Git LFS
1.通过发出一次git lfs install命令来准备LFS。
1.设置Git过滤器。
1.通过发出git config core.hooksPath .githooks命令安装钩子。
1.通过发出命令.githooks/post-checkout应用checkout钩子一次。
1.通过发出命令git add -A应用过滤器一次。
有关示例,请参见此处:* 一个月一次 *

已知问题

wgxvkvu9

wgxvkvu97#

应用程序的预压缩文件经常会有问题,因为他们希望ZIP压缩方法和文件顺序是他们选择的。我相信OpenOffice.odf文件有这个问题。
也就是说,如果你只是使用任何旧的ZIP文件作为一种方法来保持东西在一起,你应该能够创建一些简单的别名,这些别名将在需要时解压缩和重新压缩。最新的MSysGit(又名Git for Windows)现在在shell代码端有zip和unzip,所以你可以在别名中使用它们。
我目前正在做的项目使用ZIP文件作为主要的本地版本控制/存档,所以我也在尝试获得一组可行的别名,用于将这数百个ZIP文件吸入Git(并再次将它们取出;(2)让同事们开心。

piok6c0g

piok6c0g8#

在Bash中有@callegar的Rezip实现。
我将过滤器添加到仓库的配置中:

git config --replace-all filter.rezip.clean 'bash "$(git rev-parse --show-toplevel)/tools/rezip.sh" -p ODF_UNCOMPRESS2'
git config --replace-all filter.rezip.smudge 'bash "$(git rev-parse --show-toplevel)/tools/rezip.sh" -p ODF_COMPRESS2'

注意到zip工具不适用于普通的Git-for-Windows安装,尽管unzip是。所以我用perl写了一个替代品:

# zip() {
#   perl -e '
    use v5.35;
    use IO::Compress::Zip q/:all/;
    use File::Find;
    use Date::Parse qw/str2time/;

    {
      # needed a monkey-patch to clear file mtimes
      no warnings "redefine";
      my $zip_epoch = str2time("1980-01-01T00:00:00");
      my $t = IO::Compress::Zip::_unixToDosTime($zip_epoch);
      *IO::Compress::Zip::_unixToDosTime = sub { $t };
    }

    my $method = scalar(grep { /-0/ } @ARGV) ? ZIP_CM_STORE : ZIP_CM_DEFLATE;
    my $out = $ARGV[$#ARGV - 1];
    my $in = $ARGV[$#ARGV];

    my @files;
    find({ no_chdir => 1, wanted => sub { push @files, $_ if -f } }, $in);
    @files = sort @files;

    zip \@files, $out, Method => $method, CanonicalName => 1, Efs => 1, Minimal => 1;
#  ' -- $*;
# }

它非常慢,但还算可以工作。我想有人可以在此基础上用perl重新实现整个rezip

相关问题