summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSadeep Madurange <sadeep@asciimx.com>2026-03-13 22:43:10 +0800
committerSadeep Madurange <sadeep@asciimx.com>2026-03-13 22:43:10 +0800
commit28676469ba7bb4bb9e6fcf316f48bf7fb3b0a080 (patch)
tree6bd7eb006fa64136a26e4eeba67d1f384bcbe838
parent547c70a838b9fa635d7cc3d8ebced2258670b0db (diff)
downloadcvn-28676469ba7bb4bb9e6fcf316f48bf7fb3b0a080.tar.gz
Add command.
-rw-r--r--vcx159
1 files changed, 129 insertions, 30 deletions
diff --git a/vcx b/vcx
index d5baf2c..24f0551 100644
--- a/vcx
+++ b/vcx
@@ -2,51 +2,150 @@
use strict;
use warnings;
-use File::Path qw(make_path);
+use File::Path qw(make_path remove_tree);
+use File::Copy qw(copy);
+use File::Find;
+use File::Basename;
+use File::Glob qw(:bsd_glob);
+use File::Spec;
use constant VCX_DIR => '.vcx';
-use constant BASE_DIR => VCX_DIR . '/bse';
+use constant BSE_DIR => VCX_DIR . '/bse';
use constant OBJ_DIR => VCX_DIR . '/obj';
use constant TMP_DIR => VCX_DIR . '/tmp';
my $cmd = $ARGV[0] // '';
+my $path = $ARGV[1] // '';
if ($cmd eq 'init') {
- init_repo();
+ init_repo();
} elsif ($cmd eq 'status') {
- run_status();
+ run_status();
+} elsif ($cmd eq 'add') {
+ die "Usage: $0 add [path]\n" unless $path;
+ run_add($path);
} else {
- print "Usage: $0 [init|status]\n";
- exit 1;
+ print "Usage: $0 [init|status]\n";
+ exit 1;
}
sub init_repo {
- my @dirs = (OBJ_DIR, BASE_DIR, TMP_DIR);
- foreach my $dir (@dirs) {
- if (!-d $dir) {
- make_path($dir);
- print "Created: $dir\n";
- }
- }
- print "Repository ready\n";
+ make_path(OBJ_DIR, BSE_DIR, TMP_DIR);
+ print "Repository ready\n";
}
sub run_status {
- my $diff_cmd = "diff -x " . VCX_DIR . " -rq " . BASE_DIR . " .";
- $diff_cmd .= " -X .vcxignore" if -e ".vcxignore";
-
- my @output = `$diff_cmd`;
-
- foreach my $line (@output) {
- chomp $line;
- # Format output
- if ($line =~ /^Only in \Q@{[BASE_DIR]}\E: (.+)$/) {
- print "[D] $1\n";
- } elsif ($line =~ /^Only in \.: (.+)$/) {
- print "[N] $1\n";
- } elsif ($line =~ /^Files \Q@{[BASE_DIR]}\E\/(.+) and \.\/(.+) differ$/) {
- print "[M] $1\n";
- }
- }
+ my $diff_cmd = "diff -x " . VCX_DIR . " -rq " . BSE_DIR . " .";
+ $diff_cmd .= " -X .vcxignore" if -e ".vcxignore";
+
+ my @output = `$diff_cmd`;
+
+ foreach my $line (@output) {
+ chomp $line;
+ # Format output
+ if ($line =~ /^Only in \Q@{[BSE_DIR]}\E: (.+)$/) {
+ print "[D] $1\n";
+ } elsif ($line =~ /^Only in \.: (.+)$/) {
+ print "[N] $1\n";
+ } elsif ($line =~ /^Files \Q@{[BSE_DIR]}\E\/(.+) and \.\/(.+) differ$/) {
+ print "[M] $1\n";
+ }
+ }
+}
+
+sub run_add {
+ my ($target) = @_;
+
+ # Copy BSE_DIR to TMP_DIR
+ remove_tree(TMP_DIR);
+ make_path(TMP_DIR);
+ if (glob(BSE_DIR . "/*")) {
+ system("cp -R '" . BSE_DIR . "/.' '" . TMP_DIR . "/'");
+ }
+
+ my @targets = ($target eq '.') ? ('.') : bsd_glob($target);
+
+ foreach my $t (@targets) {
+ find({
+ wanted => sub {
+ # Skip VCX directory
+ return if $File::Find::name =~ /^\.\/\Q${\VCX_DIR}\E/;
+
+ my $rel_path = File::Spec->abs2rel($File::Find::name, '.');
+ my $local_path = $File::Find::name;
+ my $base_path = BSE_DIR . "/$rel_path";
+ my $obj_path = OBJ_DIR . "/$rel_path";
+ my $tmp_link = TMP_DIR . "/$rel_path";
+
+ # Regular file
+ if (-f $local_path) {
+ if (!-e $base_path) {
+ _sync_new_file($local_path, $obj_path, $tmp_link);
+ } elsif (!-l $base_path && system("diff -q '$local_path' '$base_path' > /dev/null") != 0) {
+ _sync_modified_file($local_path, $obj_path, $tmp_link);
+ }
+ }
+ # Symlink
+ elsif (-l $local_path) {
+ if (-l $base_path) {
+ # Both are symlinks: compare the targets (where they point)
+ if (readlink($local_path) ne readlink($base_path)) {
+ _sync_modified_file($local_path, $obj_path, $tmp_link);
+ }
+ } else {
+ # Local is a symlink, but base isn't: treat as modification
+ _sync_modified_file($local_path, $obj_path, $tmp_link);
+ }
+ }
+ },
+ follow => 0,
+ no_chdir => 1,
+ }, $t) if -e $t;
+
+ _handle_deletions($t);
+ }
+}
+
+sub _sync_new_file {
+ my ($src, $obj, $tmp) = @_;
+ make_path(dirname($obj));
+ make_path(dirname($tmp));
+ copy($src, $obj);
+ # Symlink from tmp/path/to/file to obj/path/to/file
+ # Note: symlink target is relative to the symlink's location
+ symlink("../../obj/" . $src, $tmp);
+ print "[Add New] $src\n";
+}
+
+sub _sync_modified_file {
+ my ($src, $obj, $tmp) = @_;
+ my $tmp_obj = "$obj.tmp";
+ make_path(dirname($tmp_obj));
+ copy($src, $tmp_obj);
+ unlink($tmp) if -l $tmp;
+ symlink("../../obj/" . $src . ".tmp", $tmp);
+ print "[Add Mod] $src\n";
+}
+
+sub _handle_deletions {
+ my ($target) = @_;
+ my $search_base = ($target eq '.') ? BSE_DIR : BSE_DIR . "/$target";
+
+ return unless -d $search_base || -e $search_base;
+
+ find({
+ wanted => sub {
+ return if -d $_;
+ my $rel_path = File::Spec->abs2rel($File::Find::name, BSE_DIR);
+
+ # If file exists in BSE_DIR but NOT in current directory (.)
+ if (!-e $rel_path) {
+ unlink(OBJ_DIR . "/$rel_path");
+ unlink(BSE_DIR . "/$rel_path");
+ print "[Deleted] $rel_path\n";
+ }
+ },
+ no_chdir => 1,
+ }, $search_base);
}