summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSadeep Madurange <sadeep@asciimx.com>2026-04-16 21:10:03 +0800
committerSadeep Madurange <sadeep@asciimx.com>2026-04-16 21:10:03 +0800
commit746683fb793b1ac30b6e418ec7143d298c12647c (patch)
tree4d0a51905fde4c48604cdd90afa583b3d9ee7530
parent7a657f4af5316fe13ed27386b9a024fbd8c6371b (diff)
downloadcvn-746683fb793b1ac30b6e418ec7143d298c12647c.tar.gz
wip: simplify run_init().
-rw-r--r--vcx216
1 files changed, 160 insertions, 56 deletions
diff --git a/vcx b/vcx
index 7a98e1f..559d449 100644
--- a/vcx
+++ b/vcx
@@ -58,26 +58,7 @@ if ($cmd eq 'init') {
sub run_init {
make_path(OBJ_DIR, REV_DIR, TMP_DIR);
touch_file(INDEX);
-
my $rev_id = to_hex_id(0);
- my $rev_file = File::Spec->catfile(REV_DIR, $rev_id);
-
- my $empty_tree = sha1_hex("");
- touch_file(File::Spec->catfile(OBJ_DIR, $empty_tree));
-
- my $ts = time();
- my $msg = "Revision zero.\n";
- my $msg_len = length($msg);
-
- open my $fh, '>:raw', $rev_file or die $!;
- print $fh "tree:$empty_tree\n";
- print $fh "parent:none\n";
- print $fh "date:$ts\n";
- print $fh "len:$msg_len\n";
- print $fh "\n"; # The "Header/body" separator
- print $fh $msg;
- close $fh;
-
write_file(HEAD, "$rev_id\n");
print "Initialized repository.\n";
}
@@ -163,6 +144,118 @@ sub run_add {
}
sub run_commit {
+ my ($msg) = @_;
+
+ # Get message and parent
+ $msg //= launch_editor();
+ die "Commit aborted: empty message\n" unless $msg && $msg =~ /\S/;
+
+ my $parent_id = read_head();
+
+ my $it = stream_index();
+ my %patches;
+
+ # New index (temporary)
+ my ($idx_fh, $idx_path) = tempfile(DIR => TMP_DIR, UNLINK => 0);
+ binmode $idx_fh, ":raw";
+
+ # Tree logic
+ my $sha_tree = Digest::SHA->new(1);
+ my @tree_lines;
+ my $tree_size = 0;
+ my $use_disk_tree = 0;
+ my ($tree_fh, $tree_path);
+
+ while (my $entry = $it->()) {
+ my $final_hash_for_tree = $entry->{base_hash};
+
+ if ($entry->{staged_hash} ne $entry->{base_hash}) {
+ my $patch = calculate_delta($entry);
+ my $compressed = compress_data($patch);
+
+ # THE PIVOT DECISION (50% Rule)
+ if (length($compressed) < ($entry->{size} * 0.5)) {
+ # Efficiency Win: Store Patch
+ $patches{"$entry->{path}.patch"} = $compressed;
+ # Tree continues to point to OLD base (maintaining delta chain)
+ $final_hash_for_tree = $entry->{base_hash};
+ } else {
+ # Pivot: Store full object as a new Keyframe
+ my $obj_path = File::Spec->catfile(OBJ_DIR, $entry->{staged_hash});
+ if (!-e $obj_path) {
+ copy($entry->{path}, $obj_path) or die "Pivot failed: $!";
+ }
+ # Tree points to NEW hash (resetting delta chain)
+ $final_hash_for_tree = $entry->{staged_hash};
+ }
+ }
+
+ # --- A. Update Tree State ---
+ my $t_line = "$final_hash_for_tree\t$entry->{path}\n";
+ $sha_tree->add($t_line);
+ $tree_size += length($t_line);
+
+ if (!$use_disk_tree && $tree_size > MEM_LIMIT) {
+ ($tree_fh, $tree_path) = tempfile(DIR => TMP_DIR, UNLINK => 0);
+ binmode $tree_fh, ":raw";
+ print $tree_fh @tree_lines;
+ @tree_lines = ();
+ $use_disk_tree = 1;
+ }
+ $use_disk_tree ? print $tree_fh $t_line : push @tree_lines, $t_line;
+
+ # --- B. Update Index State ---
+ # Current staged_hash becomes the next base_hash
+ # We also refresh metadata from the actual file
+ my @stats = stat($entry->{path});
+ print $idx_fh join("\t",
+ $entry->{staged_hash}, # New Staged
+ $entry->{staged_hash}, # New Base (Index always tracks "Current")
+ $stats[9], # Fresh mtime
+ $stats[7], # Fresh size
+ $entry->{path}
+ ) . "\n";
+ }
+
+ # 4. Finalize Tree Object
+ my $tree_hash = $sha_tree->hexdigest;
+ my $final_tree_obj = File::Spec->catfile(OBJ_DIR, $tree_hash);
+
+ if (!-e $final_tree_obj) {
+ if ($use_disk_tree) {
+ close $tree_fh;
+ rename($tree_path, $final_tree_obj) or die $!;
+ } else {
+ write_file($final_tree_obj, join("", @tree_lines));
+ }
+ } else {
+ unlink($tree_path) if $use_disk_tree;
+ }
+
+ # 5. Assemble Unified Revision File
+ my $next_id = to_hex_id(from_hex_id($parent_id) + 1);
+ my $rev_file = File::Spec->catfile(REV_DIR, $next_id);
+
+ my $tar_payload = create_tarball(\%patches);
+ my $ts = time();
+ my $msg_final = "$msg\n";
+ my $msg_len = length($msg_final);
+
+ open my $rfh, '>:raw', $rev_file or die $!;
+ print $rfh "tree:$tree_hash\n";
+ print $rfh "parent:$parent_id\n";
+ print $rfh "date:$ts\n";
+ print $rfh "len:$msg_len\n\n";
+ print $rfh $msg_final;
+ print $rfh $tar_payload;
+ close $rfh;
+
+ # 6. Final Atomicity: Commit Index and HEAD
+ close $idx_fh;
+ rename($idx_path, INDEX) or die "Index update failed: $!";
+ write_file(HEAD, "$next_id\n");
+
+ print "Revision $next_id committed.\n";
}
sub run_log {
@@ -209,30 +302,6 @@ sub run_log {
select($old_fh);
}
-sub stage_file {
- my ($src, $obj, $tmp) = @_;
- make_path(dirname($tmp));
-
- # We want the link inside tmp/ to point to "obj/HASH"
- # relative to project root.
- my $rel_target = $obj;
-
- unlink($tmp) if -e $tmp || -l $tmp;
- symlink($rel_target, $tmp) or die "Failed to symlink $tmp: $!";
- print "[Staged File] $src\n";
-}
-
-sub stage_link {
- my ($src, $tmp) = @_;
- my $target = readlink($src);
-
- make_path(dirname($tmp));
- unlink($tmp) if -e $tmp || -l $tmp;
- # For workspace symlinks, we clone the target
- symlink($target, $tmp) or die "Failed to symlink $tmp: $!";
- print "[Staged link] $src -> $target\n";
-}
-
sub make_patch {
my ($src, $obj_path, $patches) = @_;
@@ -314,19 +383,6 @@ sub apply_bin_patch {
close $fh;
}
-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
-}
-
# Convert decimal to a padded 7-char hex string
sub to_hex_id { sprintf("%07x", $_[0]) }
@@ -500,3 +556,51 @@ sub matches_paths {
return 0;
}
+sub snapshot_tree {
+ my $it = stream_index();
+
+ my @buf;
+ my $use_disk = 0;
+ my $total_size = 0;
+ my $chunk_size = 1024 * 64;
+ my $sha = Digest::SHA->new(1);
+
+ my ($tmp_fh, $tmp_path);
+
+ while (my $entry = $it->()) {
+ my $line = "$entry->{s_hash}\t$entry->{path}\n";
+ $sha->add($line);
+ $total_size += length($line);
+
+ if (!$use_disk && $total_size > MEM_LIMIT) {
+ ($tmp_fh, $tmp_path) = tempfile();
+ $tmp_fh->setvbuf(undef, POSIX::_IOFBF(), $chunk_size);
+ binmode $tmp_fh, ":raw";
+ print $tmp_fh @buf;
+ @buf = ();
+ $use_disk = 1;
+ }
+
+ if ($use_disk) {
+ print $tmp_fh $line;
+ } else {
+ push @buf, $line;
+ }
+ }
+
+ my $tree_hash = $sha->hexdigest;
+ my $obj_path = File::Spec->catfile(OBJ_DIR, $tree_hash);
+ if (!-e $obj_path) {
+ if ($use_disk) {
+ close $tmp_fh;
+ rename($tmp_path, $obj_path) or die "Rename failed: $!";
+ } else {
+ write_file($obj_path, join("", @buffer));
+ }
+ } else {
+ unlink($tmp_path) if $use_disk;
+ }
+
+ return $tree_hash;
+}
+