diff options
| author | Sadeep Madurange <sadeep@asciimx.com> | 2026-03-13 22:43:10 +0800 |
|---|---|---|
| committer | Sadeep Madurange <sadeep@asciimx.com> | 2026-03-13 22:43:10 +0800 |
| commit | 28676469ba7bb4bb9e6fcf316f48bf7fb3b0a080 (patch) | |
| tree | 6bd7eb006fa64136a26e4eeba67d1f384bcbe838 | |
| parent | 547c70a838b9fa635d7cc3d8ebced2258670b0db (diff) | |
| download | cvn-28676469ba7bb4bb9e6fcf316f48bf7fb3b0a080.tar.gz | |
Add command.
| -rw-r--r-- | vcx | 159 |
1 files changed, 129 insertions, 30 deletions
@@ -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); } |
