本质上,我正在寻找一个 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_old
和testdir_new
,并显示git
可以将它们之间的差异(在本例中为“删除 1024;添加 1024;修改/添加 19;内联修改 1200”,或总共 3267 字节的更改)编码为“sneakernet”git 捆绑文件大小为 4470 字节。但即使我能说服主机git
在那里安装,我仍然必须.git
在网络主机上维护一个仓库,以便捆绑包能够干净地应用——我绝对不想这样做,因为我无法节省额外的文件大小使用量;而且,似乎使用git管理大型二进制文件 - 代码日志需要git annex
或git 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 压缩成一个大文件,将其传输到服务器并“逆转”整个过程。
您仍然需要首先找出哪些文件需要修补,因此该过程可能如下所示:
- 用于
cmp
检查文件是否有任何不同 - 创建二进制差异并将其放入临时文件夹中的文件中。选择名称和子文件夹,以便可以在服务器上导出要修补的原始文件名和位置。
- 对需要更新的文件夹结构中的所有文件执行最后两个步骤(使用
xargs
或参数find
) - 制作该临时文件夹的 patches.tar.gz,将其上传到服务器,在那里触发修补
- 为新文件或要删除的文件开发自定义解决方案。这些可以直接通过 ftp 处理,因为不需要修补。
但请注意,如果您实施了修补程序,那么您正在开发必须维护的产品,这不再是简单的“只需配置它”的解决方案。