diff options
Diffstat (limited to 'vcx')
| -rw-r--r-- | vcx | 133 |
1 files changed, 91 insertions, 42 deletions
@@ -12,7 +12,7 @@ use Digest::SHA qw(sha1_hex); use POSIX qw(strftime); use constant VCX_DIR => '.vcx'; -use constant OBJ_DIR => VCX_DIR . '/store'; # Latest version of a file +use constant OBJ_DIR => VCX_DIR . '/objs'; # Latest version of a file use constant REV_DIR => VCX_DIR . '/revs'; # Commits use constant TREE_DIR => VCX_DIR . '/trees'; # Trees use constant HEAD_FILE => VCX_DIR . '/head'; # Current commit ID @@ -21,7 +21,9 @@ use constant HEAD_FILE => VCX_DIR . '/head'; # Current commit ID 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_FILE => VCX_DIR . '/meta'; +use constant TMP_TREE_FILE => VCX_DIR . '/tree'; my $cmd = shift @ARGV // ''; my @args = @ARGV; @@ -52,23 +54,28 @@ sub run_init { my $rev0_dir = File::Spec->catfile(REV_DIR, $initial_hex); make_path($rev0_dir); - open my $fh, '>', HEAD_FILE or die $!; - print $fh "$initial_hex\n"; - close $fh; + write_file(HEAD_FILE, "$initial_hex\n"); + + # Baseline tree (empty) + my $empty_tree_hash = sha1_hex(""); + write_file(File::Spec->catfile($rev0_dir, "tree"), "$empty_tree_hash\n"); + make_path(File::Spec->catdir(TREE_DIR, $empty_tree_hash)); + + open my $mfh, '>', File::Spec->catfile($rev0_dir, "message"); close $mfh; - # Baseline tree (empty) and message - open my $tfh, '>', File::Spec->catfile($rev0_dir, "tree"); close $tfh; - open my $mfh, '>', File::Spec->catfile($rev0_dir, "message"); - close $mfh; + print "Initialized repository.\n"; } sub run_status { open my $fh, '<', HEAD_FILE or die "VCX not initialized.\n"; my $head = <$fh>; chomp $head; close $fh; - - my $latest_tree_dir = File::Spec->catfile(REV_DIR, $head, "tree"); print "On revision [$head]\n"; + my $tree_hash_path = File::Spec->catfile(REV_DIR, $head, "tree"); + open my $tp_fh, '<', $tree_hash_path or die $!; + my $tree_hash = <$tp_fh>; chomp $tree_hash; close $tp_fh; + my $latest_tree_dir = File::Spec->catdir(TREE_DIR, $tree_hash); + # Pass 1: Workspace -> History (Detects New and Modified) find({ wanted => sub { @@ -144,26 +151,43 @@ sub run_add { open my $fh_h, '<', HEAD_FILE or die $!; my $head = <$fh_h>; chomp $head; close $fh_h; - my $latest_tree_dir = File::Spec->catfile(REV_DIR, $head, "tree"); + + my $latest_tree_hash_path = File::Spec->catfile(REV_DIR, $head, "tree"); + open my $tp_fh, '<', $latest_tree_hash_path or die $!; + my $latest_tree_hash = <$tp_fh>; chomp $latest_tree_hash; close $tp_fh; + my $latest_tree_dir = File::Spec->catdir(TREE_DIR, $latest_tree_hash); + my $next_id_hex = to_hex_id(from_hex_id($head) + 1); + my @entries; + open my $afh, '>>', TMP_META_FILE or die $!; + + init_stage($latest_tree_dir, \@entries); + foreach my $input (@targets) { my @expanded = bsd_glob($input); foreach my $t (@expanded) { find({ wanted => sub { - return if $File::Find::name =~ /^\.\/\Q${\VCX_DIR}\E/; - return if -d $File::Find::name; + if ($File::Find::name =~ /\/\Q${\VCX_DIR}\E$/ || $_ eq VCX_DIR) { + $File::Find::prune = 1; + return; + } + + return if -d $_; my $rel = $File::Find::name =~ s|^\./||r; + push @entries, $rel; + my $staged_path = File::Spec->catfile(TMP_TREE, $rel); + my $prev_link = File::Spec->catfile($latest_tree_dir, $rel); # CASE 1: Regular File if (-f $File::Find::name && !-l $File::Find::name) { return if -e $staged_path; # Already staged - if (-l File::Spec->catfile($latest_tree_dir, $rel)) { # No changes - my $obj_in_head = readlink(File::Spec->catfile($latest_tree_dir, $rel)); + if (-l $prev_link) { + my $obj_in_head = readlink($prev_link); return if compare_files($File::Find::name, $obj_in_head); } @@ -182,23 +206,17 @@ sub run_add { } stage_file($File::Find::name, $obj_path, $staged_path); - - # Record staged file in meta for commit command to find it - open my $afh, '>>', TMP_META_FILE or die $!; - print $afh "$rel\n"; - close $afh; + print $afh "$rel\n"; # Record in meta file for the commit command } # CASE 2: Symlink elsif (-l $File::Find::name) { my $target = readlink($File::Find::name); - # HEAD link points to the same target - if (-l File::Spec->catfile($latest_tree_dir, $rel)) { - return if readlink(File::Spec->catfile($latest_tree_dir, $rel)) eq $target; + if (-l $prev_link) { + return if readlink($prev_link) eq $target; } - # Staged link points to the same target if (-l $staged_path) { return if readlink($staged_path) eq $target; } @@ -210,13 +228,25 @@ sub run_add { }, $t); } } + close $afh; + + my $tree_data = join("\n", sort @entries); + my $tree_hash = sha1_hex($tree_data); + write_file(TMP_TREE_FILE, $tree_hash); } sub run_commit { my ($message) = @_; - if (! -e TMP_META_FILE) { + my $content_changed = -s TMP_META_FILE; + open my $th_fh, '<', TMP_TREE_FILE or die $!; + my $staged_hash = <$th_fh>; chomp $staged_hash; close $th_fh; + my $tree_path = File::Spec->catdir(TREE_DIR, $staged_hash); + my $tree_exists = -d $tree_path; + + if ($tree_exists && !$content_changed) { print "Nothing to commit."; + File::Path::remove_tree(TMP_DIR) if -d TMP_DIR; return; } @@ -239,18 +269,19 @@ sub run_commit { chomp $rel; my $obj_name = sha1_hex($rel); my $obj_path = File::Spec->catfile(OBJ_DIR, $obj_name); - - # Copy workspace version to the permanent store copy($rel, $obj_path) or die "Failed to update store for $rel: $!"; } close $mfh; - # Delete meta so it doesn't leak into the next commit - unlink TMP_META_FILE; } - # Save the tree (snapshots the structure) - rename(TMP_TREE, File::Spec->catfile($rev_dir, "tree")) - or die "Failed to move staged tree: $!"; + # Save tree (snapshots the structure) + if (!$tree_exists) { + make_path($tree_path); + rename(TMP_TREE, $tree_path) or die "Failed to save directories: $!"; + } + + rename(TMP_TREE_FILE, File::Spec->catfile($rev_dir, "tree")) + or die "Failed to save tree pointer to revision: $!"; # Move deltas if (-d TMP_DIFF) { @@ -259,17 +290,9 @@ sub run_commit { or die "Failed to move deltas to $dest_diff_dir: $!"; } - # Commit message - open my $msg_fh, '>', File::Spec->catfile($rev_dir, "message") or die $!; - print $msg_fh "$message\n"; - close $msg_fh; - - # Update head - open my $head_fh, '>', HEAD_FILE or die $!; - print $head_fh "$next_id_hex\n"; - close $head_fh; + write_file(File::Spec->catfile($rev_dir, "message"), "$message\n"); + write_file(HEAD_FILE, "$next_id_hex\n"); # Update head - # Clean up staging area File::Path::remove_tree(TMP_DIR) if -d TMP_DIR; my ($subject) = split(/\n/, $message); @@ -483,3 +506,29 @@ sub launch_editor { return $final_msg; } +sub write_file { + my ($path, $content) = @_; + open my $fh, '>', $path or die "Could not open '$path' for writing: $!"; + print $fh $content; + close $fh or die "Could not close '$path' after writing: $!"; +} + +sub init_stage { + my ($latest_tree_dir, $entries_ref) = @_; + + find({ + wanted => sub { + return if -d _; + + my $rel = File::Spec->abs2rel($_, $latest_tree_dir); + my $staged_path = File::Spec->catfile(TMP_TREE, $rel); + make_path(dirname($staged_path)); + + my $target = readlink($_); + symlink($target, $staged_path) or die "Failed to link $rel: $!"; + + push @$entries_ref, $rel; + }, + no_chdir => 1 + }, $latest_tree_dir); +} |
