diff options
| author | Sadeep Madurange <sadeep@asciimx.com> | 2026-04-05 08:52:05 +0800 |
|---|---|---|
| committer | Sadeep Madurange <sadeep@asciimx.com> | 2026-04-05 08:52:05 +0800 |
| commit | 5d2ea0e0b78a1a2e539289f9085df171675c7a01 (patch) | |
| tree | 3cc96866fa777178ad835ad44ecd05f6f44ca3d3 | |
| parent | 152a9e9a03d8c5ead25e9a0ecfe46aa527bf0257 (diff) | |
| download | cvn-5d2ea0e0b78a1a2e539289f9085df171675c7a01.tar.gz | |
Gzip patch sets.
| -rw-r--r-- | vcx | 120 |
1 files changed, 64 insertions, 56 deletions
@@ -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 { |
