summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSadeep Madurange <sadeep@asciimx.com>2026-04-05 08:52:05 +0800
committerSadeep Madurange <sadeep@asciimx.com>2026-04-05 08:52:05 +0800
commit5d2ea0e0b78a1a2e539289f9085df171675c7a01 (patch)
tree3cc96866fa777178ad835ad44ecd05f6f44ca3d3
parent152a9e9a03d8c5ead25e9a0ecfe46aa527bf0257 (diff)
downloadcvn-5d2ea0e0b78a1a2e539289f9085df171675c7a01.tar.gz
Gzip patch sets.
-rw-r--r--vcx120
1 files changed, 64 insertions, 56 deletions
diff --git a/vcx b/vcx
index cbe5620..8dd61e5 100644
--- a/vcx
+++ b/vcx
@@ -10,8 +10,10 @@ use File::Basename;
use File::Glob qw(:bsd_glob);
use File::Spec;
use Getopt::Long;
-use Digest::SHA qw(sha1_hex);
+use Archive::Tar;
+use Compress::Zlib;
use POSIX qw(strftime);
+use Digest::SHA qw(sha1_hex);
use constant VCX_DIR => '.vcx';
use constant HEAD => VCX_DIR . '/head'; # Current commit ID
@@ -21,8 +23,8 @@ use constant REV_DIR => VCX_DIR . '/rev'; # Commits
# Staging area
use constant TMP_DIR => VCX_DIR . '/index';
use constant TMP_TREE => TMP_DIR . '/tree';
-use constant TMP_DIFF => TMP_DIR . '/deltas';
use constant TMP_META => TMP_DIR . '/meta';
+use constant TMP_DIFF => TMP_DIR . '/delta.tar.gz';
Getopt::Long::Configure("bundling");
@@ -72,6 +74,15 @@ sub run_status {
my $head = <$fh>; chomp $head; close $fh;
print "On revision [$head]\n";
+ my %staged_diffs;
+ if (-e TMP_DIFF) {
+ my @list = qx(tar -tf TMP_DIFF);
+ foreach (@list) {
+ chomp;
+ if (/(.+)\.patch$/) { $staged_diffs{$1} = 1; }
+ }
+ }
+
my ($tree_ptr) = bsd_glob(File::Spec->catfile(REV_DIR, $head, "tree-*"));
my ($tree_hash) = $tree_ptr =~ /tree-([a-f0-9]{40})$/;
my $latest_tree_dir = File::Spec->catdir(OBJ_DIR, $tree_hash);
@@ -86,15 +97,17 @@ sub run_status {
$rel =~ s|^\./||;
my $base_in_tree = File::Spec->catfile($latest_tree_dir, $rel);
+ my $path_hash = sha1_hex($rel);
if (-e $base_in_tree || -l $base_in_tree) {
my $obj_in_store = readlink($base_in_tree);
if (compare($_, $obj_in_store) != 0) {
- my $staged = check_staged_status($rel, 'M') ? " (staged)" : "";
+ my $staged = $staged_diffs{$path_hash} ? " (staged)" : "";
print "[M] $rel$staged\n";
}
} else {
- my $staged = check_staged_status($rel, 'N') ? " (staged)" : "";
+ my $tmp_link = File::Spec->catfile(TMP_TREE, $rel);
+ my $staged = (-e $tmp_link || -l $tmp_link) ? " (staged)" : "";
print "[N] $rel$staged\n";
}
},
@@ -110,7 +123,8 @@ sub run_status {
# If it's in the commit tree but GONE from the workspace
if (!-e $rel && !-l $rel) {
- my $staged = check_staged_status($rel, 'D') ? " (staged)" : "";
+ my $tmp_link = File::Spec->catfile(TMP_TREE, $rel);
+ my $staged = (!-e $tmp_link && !-l $tmp_link) ? " (staged)" : "";
print "[D] $rel$staged\n";
}
},
@@ -119,34 +133,9 @@ sub run_status {
}
}
-sub check_staged_status {
- my ($path, $type) = @_;
- my $path_hash = sha1_hex($path);
- my $tmp_link = File::Spec->catfile(TMP_TREE, $path);
-
- # If the file is in history but the symlink not in TMP_DIR, it's staged for deletion.
- if ($type eq 'D') {
- return !-e $tmp_link && !-l $tmp_link;
- }
-
- # If it's a new file, it's staged if the symlink exists in TMP_DIR
- if ($type eq 'N') {
- return (-e $tmp_link || -l $tmp_link);
- }
-
- if ($type eq 'M') {
- # Check if a patch exists in the temporary diff directory.
- # We look for any file starting with the path_hash in TMP_DIFF.
- my $patch_pattern = File::Spec->catfile(TMP_DIFF, "$path_hash.*.patch");
- my @patches = bsd_glob($patch_pattern);
- return scalar @patches > 0;
- }
-
- return 0;
-}
-
sub run_add {
my @targets = @_;
+ my %patches;
make_path(TMP_TREE);
@@ -194,7 +183,8 @@ sub run_add {
my $obj_name = sha1_hex($rel);
my $obj_path = File::Spec->catfile(OBJ_DIR, $obj_name);
- make_patch($File::Find::name, $obj_name, $obj_path);
+ # Prepare patches in memory and save to disk in one write.
+ make_patch($File::Find::name, $obj_name, $obj_path, \%patches);
stage_file($File::Find::name, $obj_path, $staged_path);
print $afh "$rel\n"; # Record in meta file for the commit command
}
@@ -216,6 +206,8 @@ sub run_add {
}
}
+ save_patches(\%patches);
+
# Pass 2: History -> Workspace (Detects Deletions)
foreach my $path (keys %entries) {
if (!-e $path && !-l $path) {
@@ -248,13 +240,14 @@ sub run_add {
sub run_commit {
my ($msg) = @_;
- my $content_changed = -s TMP_META;
my ($staged_tree_ptr) = bsd_glob(File::Spec->catfile(TMP_DIR, "tree-*"));
my ($tree_hash) = $staged_tree_ptr =~ /tree-([a-f0-9]{40})$/;
my $tree_path = File::Spec->catdir(OBJ_DIR, $tree_hash);
my $tree_exists = -d $tree_path;
+ my $content_changed = (-e TMP_META || -e TMP_DIFF);
+
if ($tree_exists && !$content_changed) {
print "Nothing to commit.";
File::Path::remove_tree(TMP_DIR) if -d TMP_DIR;
@@ -295,10 +288,10 @@ sub run_commit {
or die "Failed to save tree pointer to revision: $!";
# Move deltas
- if (-d TMP_DIFF) {
- my $dest_diff_dir = File::Spec->catfile($rev_dir, "deltas");
- rename(TMP_DIFF, $dest_diff_dir)
- or die "Failed to move deltas to $dest_diff_dir: $!";
+ if (-e TMP_DIFF) {
+ my $dest_diff = File::Spec->catfile($rev_dir, "delta.tar.gz");
+ rename(TMP_DIFF, $dest_diff)
+ or die "Failed to move delta to $dest_diff: $!";
}
write_file(File::Spec->catfile($rev_dir, "message"), "$msg\n");
@@ -411,28 +404,32 @@ sub stage_deletions {
}
sub make_patch {
- my ($src, $obj_name, $obj_path) = @_;
-
- if (-e $obj_path) {
- my $p_path = File::Spec->catfile(TMP_DIFF, "$obj_name.patch");
- if (-T $src) {
- if (compare($src, $obj_path) != 0) {
- unless (-d TMP_DIFF) { make_path(TMP_DIFF); }
- system("diff -u '$obj_path' '$src' > '$p_path'");
- }
- } else {
- make_bin_patch($src, $obj_path, $p_path);
+ my ($src, $obj_name, $obj_path, $patches) = @_;
+
+ return unless -e $obj_path;
+
+ my $patch;
+
+ # TODO: implement a disk-based diff for large files, esp bin files
+ if (-T $src) {
+ if (compare($src, $obj_path) != 0) {
+ $patch = qx(diff -u '$obj_path' '$src');
}
+ } else {
+ $patch = make_bin_patch($src, $obj_path);
}
+
+ if ($patch) { $patches->{$obj_name} = $patch; }
}
sub make_bin_patch {
- my ($new_file, $old_file, $patch_out) = @_;
+ my ($new_file, $old_file) = @_;
open my $f_new, '<:raw', $new_file or die "Cannot open file: $!";
open my $f_old, '<:raw', $old_file or die "Cannot open object file: $!";
my $f_out;
+ my $patch = "";
my $offset = 0;
my $blk_size = 4096;
@@ -443,25 +440,36 @@ sub make_bin_patch {
# Stop when we've processed the entire new file
last if $read_new == 0 && $read_old == 0;
- # Handle file size differences (padding with nulls if one is shorter)
+ # Handle file size differences (pad with nulls if one is shorter)
$buf_new .= "\0" x ($blk_size - length($buf_new)) if length($buf_new) < $blk_size;
$buf_old .= "\0" x ($blk_size - length($buf_old)) if length($buf_old) < $blk_size;
# If they differ, we save the OLD buffer (reverse delta)
if ($buf_new ne $buf_old) {
- if (!$f_out) {
- make_path(dirname($patch_out));
- open $f_out, '>:raw', $patch_out or die "Cannot create patch: $!";
- }
# Header: [64-bit offset][32-bit length][data]
- my $header = pack("QL", $offset, length($buf_old));
- syswrite($f_out, $header . $buf_old);
+ # 'Q' is 64-bit unsigned (quad), 'L' is 32-bit unsigned (long)
+ $patch .= pack("QL", $offset, length($buf_old)) . $buf_old;
}
$offset += $blk_size;
}
close $f_new; close $f_old;
close $f_out if $f_out;
+
+ return length($patch) > 0 ? $patch : undef;
+}
+
+sub save_patches {
+ my ($patches) = @_;
+
+ return unless keys %$patches;
+
+ my $tar = Archive::Tar->new;
+ while (my ($obj_name, $patch) = each %$patches) {
+ $tar->add_data("$obj_name.patch", $patch);
+ }
+
+ $tar->write(TMP_DIFF, 1); # gzip
}
sub apply_bin_patch {