summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--vcx133
1 files changed, 91 insertions, 42 deletions
diff --git a/vcx b/vcx
index 04b445c..db94101 100644
--- a/vcx
+++ b/vcx
@@ -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);
+}