在 CLI 中获取两个目录的二进制增量编码递归差异作为单个补丁文件(和在线使用)

在 CLI 中获取两个目录的二进制增量编码递归差异作为单个补丁文件(和在线使用)

本质上,我正在寻找一个 GNU/Linux 工具,它可以递归遍历两个目录,查找文件更改/添加/删除;并针对所有更改的文件输出差异。这已经可以有效地diff用于文本文件,但不能用于大型二进制文件 - 我还希望在最终补丁中包含的二进制文件之间有高效的“差异”(据我所知,它被称为二进制增量压缩 - 维基百科作为子集Delta 编码 - 维基百科或者换句话说,按照这个“愿望清单”评论中描述的方式去做(来自命令行语法 - xdelta - Google 项目托管):

如果 xdelta 支持多个文件修补就太好了。例如:

xdelta3 -r /path/folder1 /path/folder2 >allfilesrecursivepatch.xdelta

递归比较文件夹 1 和文件夹 2 上的所有文件,并为所有文件创建单个补丁文件。并且:

xdelta3 -r -d /path/folder1 <allfilesrecursivepatch.xdelta

对文件夹 1 中包含的所有文件应用补丁

根据以下判断,该设施不存在问题 21 - xdelta - 递归目录差异支持 - Google 项目托管),尽管有解决方法:问题页面对脚本包装器提出了几点建议,但我宁愿将其包含在一个工具中。

对我来说最重要的是修补如上所示的“实时”文件系统目录部分,因为我的预期用例 - 下面将更详细地描述,并通过bash使用脚本进行说明git


我希望能够在较便宜的 Linux 主机/网络农场上更新静态网站,它仅允许 FTP 传输(所以不行rsync),传输速度相当慢,并且仅允许执行 PHP 脚本。通常我需要从本地/客户端/主页同步到服务器/网络主机,但我当然不想每次更新页面时都上传 200 MB:)

我可以用outlandishideas/sync · GitHub改为“使用 PHP 通过 HTTP 同步目录内容”,但除了仅从服务器同步到本地之外,还只发送整个文件:“没有尝试发送差异;这不是 rsync”。同样,我可以使用GNU FTP同步;它可能可以处理文件的创建、修改和删除,但它有同样的问题——只会发送整个文件。

原则上,git也可以使用 - 下面的脚本生成目录testdir_oldtestdir_new,并显示git可以将它们之间的差异(在本例中为“删除 1024;添加 1024;修改/添加 19;内联修改 1200”,或总共 3267 字节的更改)编码为“sneakernet”git 捆绑文件大小为 4470 字节。但即使我能说服主机git在那里安装,我仍然必须.git在网络主机上维护一个仓库,以便捆绑包能够干净地应用——我绝对不想这样做,因为我无法节省额外的文件大小使用量;而且,似乎使用git管理大型二进制文件 - 代码日志需要git annexgit bup...并且放置像下面这样的脚本会出现问题,因为git每次都会重新创建新的修订哈希,从而导致捆绑包无法干净地应用。

此外,由于在 PHP 中,我显然可以“没有 exec() 的 untar-gz? - VoidCC VoidCC“,也许值得尝试确定目录中的变化,然后仅将更改的文件打包到文件中tar.gz,并将其发送到服务器上的 PHP 脚本,该脚本会将其解压到目标目录中。这仍然会发送整个文件,但至少它们会被压缩 - 但服务器上的删除将很难处理。

最后,二进制文件差异实用程序建议可以将目录打包在每个目录中.tar(.gz),然后在这些文件上运行该实用程序 - 例如(通过外部压缩 - xdelta - Google 项目托管):

gzip release-1.tar
gzip release-2.tar
xdelta3 -e -s release-1.tar.gz release-2.tar.gz delta-1-2.xd3
xdelta3 -d -s release-1.tar.gz delta-1-2.xd3 release-2.tar.gz

...可能也可以用JojoDiff /jdiff

jdiff archive0000.tar archive0001.tar archive0001.jdf
jptch archive0000.tar archive0001.jdf archive0001b.tar

...或者bsdiff。但是,这要求我还要在网络主机上维护整个网站的 tar 存档,以便补丁能够顺利应用到其中,而空间又成了问题。这还会迫使我要求网络主机允许我安装和使用至少修补部分的工具;如果这些工具不需要我在主机上保留网站的额外 tar 副本,那么这可能值得再试一次。

无论如何,下面的脚本演示了如何提取git .bundle两个目录(或者更确切地说,同一目录的两个版本)之间的递归差异;相关的终端输出包含在注释中:

#!/usr/bin/env bash

## comments with double ##; (relevant) terminal output with single #
## uses git, ImageMagick, tree

set -x

cd /tmp
rm -rf testdir export_compare
mkdir testdir
cd testdir
git init
  # Initialized empty Git repository in /tmp/testdir/.git/

git config user.name "test"
git config user.email "[email protected]"

## generate files - revision 1
## - text
cat /dev/urandom | tr -dc '[ -~]' | fold -w 80 -s | head -c 1024 > test_01.txt
mkdir subdir
cat /dev/urandom | tr -dc '[ -~]' | fold -w 80 -s | head -c 1024 > subdir/subtest_01.txt
cat /dev/urandom | tr -dc '[ -~]' | fold -w 80 -s | head -c 1024 > subdir/subtest_02.txt
## - binary
convert -depth 8 -size 200x150 xc:blue rgb:subdir/rgbimage.dat

## check:
## - files:
tree -s --dirsfirst .
  # .
  # ├── [       4096]  subdir
  # │   ├── [      90000]  rgbimage.dat
  # │   ├── [       1024]  subtest_01.txt
  # │   └── [       1024]  subtest_02.txt
  # └── [       1024]  test_01.txt
  #
  # 1 directory, 4 files
## - view image (press "q" to quit)
display -depth 8 -size 200x150 rgb:subdir/rgbimage.dat

git add *
git commit -m "initial commit"

## check usage
du -ba --max-depth=1 .
  # 1024    ./test_01.txt
  # 96144   ./subdir
  # 99947   ./.git
  # 201211  .

## change files - revision 2

## remove file:
REP="removed 1024;"
git rm subdir/subtest_02.txt

## add file
REP="$REP added 1024;"
cat /dev/urandom | tr -dc '[ -~]' | fold -w 80 -s | head -c 1024 > test_02.txt
git add test_02.txt

## change files:
## - text:
REP="$REP modified/added 19;"
echo "a new changed line" >> test_01.txt
## - binary
REP="$REP modified inline 1200"
convert -depth 8 -size 1x200 xc:red rgb:/dev/stdout | dd of=subdir/rgbimage.dat bs=1 seek=$((200*50*3)) count=$((200*3)) conv=notrunc
convert -depth 8 -size 1x200 xc:red rgb:/dev/stdout | dd of=subdir/rgbimage.dat bs=1 seek=$((200*100*3)) count=$((200*3)) conv=notrunc

## check:
## - files:
tree -s --dirsfirst .
  # .
  # ├── [       4096]  subdir
  # │   ├── [      90000]  rgbimage.dat
  # │   └── [       1024]  subtest_01.txt
  # ├── [       1043]  test_01.txt
  # └── [       1024]  test_02.txt
  #
  # 1 directory, 4 files
## - view image (press "q" to quit)
display -depth 8 -size 200x150 rgb:subdir/rgbimage.dat

git add *
git commit -m "second commit with changes"
  # [master 2b243fb] second commit with changes
  #  4 files changed, 16 insertions(+), 19 deletions(-) ...

## check usage
du -ba --max-depth=1 .
  # 1043    ./test_01.txt
  # 1024    ./test_02.txt
  # 95120   ./subdir
  # 123355  ./.git
  # 224638  .

## go back to parent dir (/tmp) and make a new directory for "clean" exports:
cd /tmp
mkdir export_compare
mkdir export_compare/testdir_new
mkdir export_compare/testdir_old
## from git, export each revision "cleanly"
cd testdir
git archive HEAD   | tar -x -C /tmp/export_compare/testdir_new
git archive HEAD^1 | tar -x -C /tmp/export_compare/testdir_old
## create git bundle, containing the changes between new and old revision
git bundle create ../commits_testdir.bundle HEAD HEAD^1
  # ... Writing objects: 100% (13/13), 4.30 KiB, done.
  # Total 13 (delta 2), reused 0 (delta 0)

## check
cd /tmp
echo $REP
  # removed 1024; added 1024; modified/added 19; modified inline 1200
du -b commits_testdir.bundle
  # 4470    commits_testdir.bundle
cd export_compare
du -bs testdir_old testdir_new
  # 101264  testdir_old
  # 101283  testdir_new
tree -s --dirsfirst .
  # .
  # ├── [       4096]  testdir_new
  # │   ├── [       4096]  subdir
  # │   │   ├── [      90000]  rgbimage.dat
  # │   │   └── [       1024]  subtest_01.txt
  # │   ├── [       1043]  test_01.txt
  # │   └── [       1024]  test_02.txt
  # └── [       4096]  testdir_old
  #     ├── [       4096]  subdir
  #     │   ├── [      90000]  rgbimage.dat
  #     │   ├── [       1024]  subtest_01.txt
  #     │   └── [       1024]  subtest_02.txt
  #     └── [       1024]  test_01.txt
  #
  # 4 directories, 8 files
  # + set +x

set +x

答案1

git ftp尝试一下。它完全是为了这个目的而制作的。

它通过将提交 ID 存储在服务器上的日志文件中来跟踪上传的文件。它使用 Git 来确定哪些本地文件已发生更改。

答案2

我会选择低调的解决方案。如果我理解正确的话,您想要更新网页,但不希望大多数网页发生变化,在这种情况下,我会上传更改的文件,全部上传。

例如,可以通过以下方式实现。在 中mc,通过 FTP 将一个面板连接到您的 Web 主机,让其他面板显示本地版本。然后选择所有内容并复制,并选择仅覆盖所有较新的文件(您可以一次为所有文件选择该选项)。或者使用其他文件管理器的同步功能,我相信 krusader 有一些。除非您有仅在本地更改的大文件(它们是什么?数据库1?可能是可执行文件,但不是压缩的?),否则二进制增量不会给您带来太多好处。

注1:以这种方式同步数据库不是一个好主意。

答案3

我会用类似的东西curlftpfs将服务器的 FTP 共享挂载到本地机器,然后在这两个文件夹上本地执行 rsync。

这样做的好处是您不需要将服务器状态保存为本地机器上的副本 - 因为如果您想先创建二进制差异,则必须将它们发送到服务器,然后在那里开始修补。

如果您仍然想采用 diff/patch 解决方案,您可以尝试使用 xargs 创建多个补丁文件(使用 xdelta3 或任何您喜欢的工具),将它们 tar 和 gzip 压缩成一个大文件,将其传输到服务器并“逆转”整个过程。

您仍然需要首先找出哪些文件需要修补,因此该过程可能如下所示:

  1. 用于cmp检查文件是否有任何不同
  2. 创建二进制差异并将其放入临时文件夹中的文件中。选择名称和子文件夹,以便可以在服务器上导出要修补的原始文件名和位置。
  3. 对需要更新的文件夹结构中的所有文件执行最后两个步骤(使用xargs或参数find
  4. 制作该临时文件夹的 patches.tar.gz,将其上传到服务器,在那里触发修补
  5. 为新文件或要删除的文件开发自定义解决方案。这些可以直接通过 ftp 处理,因为不需要修补。

但请注意,如果您实施了修补程序,那么您正在开发必须维护的产品,这不再是简单的“只需配置它”的解决方案。

相关内容