myvcs 0.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example ADDED
@@ -0,0 +1,4 @@
1
+ AWS_ACCESS_KEY_ID=...
2
+ AWS_SECRET_ACCESS_KEY=...
3
+ S3_BUCKET=myvcs-bucket
4
+ MY_NAMESPACE=123e4567...
@@ -1,7 +1,7 @@
1
1
  import fs from "fs/promises"
2
2
  import path from "path"
3
3
  async function readIgnoreFile() {
4
- const ignorePath = path.join(process.cwd(), ".myvcsignore");
4
+ const ignorePath = path.resolve(process.cwd(), ".myvcsignore");
5
5
 
6
6
  try {
7
7
  // Check if file exists
@@ -9,7 +9,6 @@ async function readIgnoreFile() {
9
9
 
10
10
  // Read file content
11
11
  const content = await fs.readFile(ignorePath, "utf-8");
12
-
13
12
  // Split into lines and clean up
14
13
  const patterns = content
15
14
  .split("\n") // Split by newline
@@ -24,7 +23,7 @@ async function readIgnoreFile() {
24
23
  return null;
25
24
  }
26
25
  }
27
- const ignoreList = await readIgnoreFile();
26
+ let ignoreList=[];
28
27
  async function checkExists(path) {
29
28
  try {
30
29
  await fs.access(path);
@@ -37,6 +36,7 @@ async function addRepo(filePath){
37
36
  const repoPath=path.resolve(process.cwd(),".myvcs");
38
37
  const stagingPath=path.resolve(repoPath,"staging");
39
38
  const repoExists=await checkExists(repoPath);
39
+ ignoreList = await readIgnoreFile();
40
40
  if (!repoExists) {
41
41
  console.log("Not a repository. Run 'myvcs init' first.");
42
42
  return;
@@ -44,7 +44,7 @@ async function addRepo(filePath){
44
44
  try{
45
45
  await fs.mkdir(stagingPath,{recursive:true});
46
46
  const fileName=path.basename(filePath);
47
- if(fileName==".") addAllFiles(process.cwd(),stagingPath);
47
+ if(fileName==".") await addAllFiles(process.cwd(),stagingPath);
48
48
  else if(ignoreList && ignoreList.includes(fileName)){
49
49
  console.log("File not added to staging area Ignored!!")
50
50
  }
@@ -56,37 +56,53 @@ async function addRepo(filePath){
56
56
  console.error("Failed to add file to staging area!! ",err)
57
57
  }
58
58
  }
59
- async function shouldIgnore(filename) {
60
-
61
- // Ignore specific files/folders
59
+ async function shouldIgnore(filename, fullPath) {
60
+ // Check against ignore patterns
62
61
  if (ignoreList && ignoreList.includes(filename)) return true;
63
62
 
64
- // Ignore hidden files (starting with .)
63
+ // Check if path contains ignored directories
64
+ if (ignoreList) {
65
+ for (const pattern of ignoreList) {
66
+ if (fullPath.includes(pattern)) return true;
67
+ }
68
+ }
69
+
70
+ // Ignore hidden files
65
71
  if (filename.startsWith(".")) return true;
66
72
 
67
73
  return false;
68
74
  }
69
- async function addAllFiles(directory,stagingPath,baseDir=process.cwd()){
70
- const items=await fs.readdir(directory);
71
- let addedCount=0;
75
+ async function addAllFiles(directory, stagingPath, baseDir=process.cwd()){
76
+ // Check if this directory itself should be ignored
77
+ const dirName = path.basename(directory);
78
+ const shouldSkip = await shouldIgnore(dirName, directory);
79
+
80
+ if(shouldSkip) {
81
+ console.log(` X SKIPPING ${directory}`);
82
+ return 0;
83
+ }
84
+
85
+ const items = await fs.readdir(directory);
86
+ let addedCount = 0;
72
87
  for(const item of items){
73
- if(await shouldIgnore(item)) continue;
74
- const fullPath=path.join(directory,item);
75
- const stat=await fs.stat(fullPath);
88
+ const fullPath = path.join(directory, item);
89
+ if(await shouldIgnore(item, fullPath)) continue;
90
+
91
+ const stat = await fs.stat(fullPath);
76
92
  if(stat.isDirectory()){
77
- const count=await addAllFiles(fullPath,stagingPath);
78
- addedCount+=count;
93
+ const count = await addAllFiles(fullPath, stagingPath, baseDir); // recursive call will check it
94
+ addedCount += count;
79
95
  }
80
- else{
81
- const relativePath=path.relative(baseDir,fullPath);
82
- const destPath=path.join(stagingPath,relativePath);
83
- await fs.mkdir(path.dirname(destPath),{recursive:true});
84
- await fs.copyFile(fullPath,destPath);
96
+ else {
97
+ const relativePath = path.relative(baseDir, fullPath);
98
+ const destPath = path.join(stagingPath, relativePath);
99
+ await fs.mkdir(path.dirname(destPath), {recursive: true});
100
+ await fs.copyFile(fullPath, destPath);
85
101
  addedCount++;
86
102
  console.log(`added file ${relativePath}`);
87
103
  }
88
104
  }
89
- if(directory==baseDir){
105
+ if(directory == baseDir){
90
106
  console.log(`Added ${addedCount} files to staging area`);
91
107
  }
92
108
  return addedCount;
@@ -2,30 +2,82 @@ import fs from "fs/promises"
2
2
  import path from "path"
3
3
  import { v5 as uuidv5 } from 'uuid';
4
4
  import dotenv from "dotenv"
5
+ import { diffLines } from "./diffLines.js";
5
6
  dotenv.config();
6
- function generateCommitID(message) {
7
- return uuidv5(message, process.env.MY_NAMESPACE);
7
+
8
+ function generateCommitID(message, parent) {
9
+ const unique = message + (parent || "") + Date.now();
10
+ return uuidv5(unique, process.env.MY_NAMESPACE);
8
11
  }
9
- async function commitRepo(message){
10
- const repoPath=path.resolve(process.cwd(),".myvcs");
11
- const commitPath=path.resolve(repoPath,"commits");
12
- const stagedPath=path.resolve(repoPath,"staging");
13
- const commitId=generateCommitID(message);
14
- const thisCommitPath=path.resolve(path.join(commitPath,commitId));
15
- const commitsJsonPath = path.join(commitPath, "commits.json");
16
- try{
17
- await fs.mkdir(commitPath,{recursive:true});
18
- await fs.mkdir(thisCommitPath,{recursive:true});
19
- const files=await fs.readdir(stagedPath);
20
- for(const file of files){
21
- await fs.copyFile(path.join(stagedPath,file),path.join(thisCommitPath,file));
12
+
13
+ async function processFiles(stagedPath, thisCommitPath, commitPath, parent, diffs, baseDir = stagedPath) {
14
+ const items = await fs.readdir(stagedPath);
15
+
16
+ for (const item of items) {
17
+ const sourcePath = path.join(stagedPath, item);
18
+ const stat = await fs.stat(sourcePath);
19
+
20
+ if (stat.isDirectory()) {
21
+ // Recurse into subdirectory
22
+ const destDir = path.join(thisCommitPath, path.relative(baseDir, sourcePath));
23
+ await fs.mkdir(destDir, { recursive: true });
24
+ await processFiles(sourcePath, thisCommitPath, commitPath, parent, diffs, baseDir);
25
+ } else {
26
+ // Process file
27
+ const relativePath = path.relative(baseDir, sourcePath);
28
+ const newContent = await fs.readFile(sourcePath, "utf-8");
29
+
30
+ let oldContent = "";
31
+ if (parent) {
32
+ const parentSnapshotPath = path.join(commitPath, parent, relativePath);
33
+ try {
34
+ oldContent = await fs.readFile(parentSnapshotPath, "utf-8");
35
+ } catch {
36
+ oldContent = "";
37
+ }
38
+ }
39
+
40
+ const diff = diffLines(oldContent, newContent);
41
+ diffs[relativePath] = diff;
42
+
43
+ // Copy file to commit snapshot
44
+ const destPath = path.join(thisCommitPath, relativePath);
45
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
46
+ await fs.copyFile(sourcePath, destPath);
22
47
  }
23
- let commits = [];
48
+ }
49
+ }
50
+
51
+ async function commitRepo(message) {
52
+ const repoPath = path.resolve(process.cwd(), ".myvcs");
53
+ const headPath = path.join(repoPath, "HEAD");
54
+ const commitPath = path.resolve(repoPath, "commits");
55
+ const stagedPath = path.resolve(repoPath, "staging");
56
+ const commitsJsonPath = path.join(commitPath, "commits.json");
57
+
58
+ let parent = null;
59
+ try {
60
+ parent = (await fs.readFile(headPath, "utf-8")).trim() || null;
61
+ } catch {
62
+ parent = null;
63
+ }
64
+
65
+ const diffs = {};
66
+ const commitId = generateCommitID(message, parent);
67
+ const thisCommitPath = path.resolve(path.join(commitPath, commitId));
68
+
69
+ try {
70
+ await fs.mkdir(commitPath, { recursive: true });
71
+ await fs.mkdir(thisCommitPath, { recursive: true });
72
+
73
+ // Process all files recursively
74
+ await processFiles(stagedPath, thisCommitPath, commitPath, parent, diffs);
75
+
76
+ let commits = [];
24
77
  try {
25
78
  const data = await fs.readFile(commitsJsonPath, 'utf-8');
26
79
  commits = JSON.parse(data);
27
80
  } catch (err) {
28
- // File doesn't exist yet, start with empty array
29
81
  if (err.code !== 'ENOENT') throw err;
30
82
  }
31
83
 
@@ -33,17 +85,19 @@ async function commitRepo(message){
33
85
  commits.push({
34
86
  commitId,
35
87
  message,
36
- date: new Date().toISOString()
88
+ date: new Date().toISOString(),
89
+ parent
37
90
  });
38
91
 
39
92
  // Write back to file
93
+ const diffsPath = path.join(thisCommitPath, "diffs.json");
94
+ await fs.writeFile(diffsPath, JSON.stringify(diffs, null, 2));
40
95
  await fs.writeFile(commitsJsonPath, JSON.stringify(commits, null, 2));
96
+ await fs.writeFile(headPath, commitId);
41
97
  console.log(`commit done ${commitId} with message ${message}`);
42
- }catch(err){
43
- console.error("Commit failed!! ",err);
98
+ } catch (err) {
99
+ console.error("Commit failed!! ", err);
44
100
  }
45
-
46
-
47
101
  }
48
102
 
49
- export {commitRepo}
103
+ export { commitRepo }
@@ -1,5 +1,4 @@
1
1
  import {readConfig,setConfigValue} from "../config/vcsConfig.js"
2
-
3
2
  export function showConfig(){
4
3
  try{
5
4
  const config=readConfig();
@@ -16,14 +15,14 @@ export function showConfig(){
16
15
  }
17
16
  }
18
17
 
19
- export function setConfig(key,value){
18
+ export async function setConfig(key,value){
20
19
  try{
21
20
  const validKeys=["username","email"];
22
21
  if(!validKeys.includes(key)){
23
22
  console.log(`Invalid Config Key ${key}`);
24
23
  process.exit(1);
25
24
  }
26
- if(key=="username"){
25
+ if(key==="username"){
27
26
  if(!/^[a-zA-Z0-9_-]+$/.test(value)){
28
27
  console.log("username must only contain letters,underscores and hyphen");
29
28
  process.exit(1);
@@ -0,0 +1,41 @@
1
+ function lcs(a,b){
2
+ const m=a.length,n=b.length;
3
+ const dp=Array.from({length:m+1},()=>Array(n+1).fill(0));
4
+
5
+ for(let i=1;i<m;i++){
6
+ for(let j=1;j<n;j++){
7
+ if(a[i-1]===b[j-1]){
8
+ dp[i][j]=dp[i-1][j-1]+1;
9
+ }
10
+ else dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j]);
11
+ }
12
+ }
13
+
14
+ return dp;
15
+ }
16
+ function backtrack(dp,a,b,i,j){
17
+ if(i===0 && j==0) return [];
18
+ if(i>0 && j>0 && a[i-1]===b[j-1]){
19
+ const result=backtrack(dp,a,b,i-1,j-1);
20
+ result.push({type:"same",line:a[i-1]});
21
+ return result;
22
+ }
23
+ else if(j>0 && (i==0||dp[i][j-1]>=dp[i-1][j])){
24
+ const result=backtrack(dp,a,b,i,j-1);
25
+ result.push({type:"added",line:b[j-1]});
26
+ return result;
27
+ }
28
+ else{
29
+ const result=backtrack(dp,a,b,i-1,j);
30
+ result.push({type:"removed",line:a[i-1]});
31
+ return result;
32
+ }
33
+ }
34
+ function diffLines(oldContent,newContent){
35
+ const a = oldContent.split("\n").filter(line => line.length > 0);
36
+ const b = newContent.split("\n").filter(line => line.length > 0);
37
+ const dp=lcs(a,b);
38
+ return backtrack(dp,a,b,a.length,b.length);
39
+ }
40
+
41
+ export {diffLines}
@@ -10,6 +10,7 @@ async function initRepo(){
10
10
  try{
11
11
  await fs.mkdir(repoPath,{recursive:true});
12
12
  await fs.mkdir(commitPath,{recursive:true});
13
+ await fs.writeFile(path.join(repoPath, "HEAD"), "");
13
14
  await fs.writeFile(
14
15
  path.join(repoPath,"config.json"),
15
16
  JSON.stringify({bucket:process.env.S3_BUCKET})
@@ -1,64 +1,89 @@
1
1
  import fs from "fs/promises"
2
2
  import path from "path"
3
- import {S3,S3_BUCKET} from "../config/aws-config.js"
4
- import {getUsername,getConfigValue} from "../config/vcsConfig.js"
5
- async function pullRepo(){
6
- const repoPath=path.resolve(process.cwd(),".myvcs");
7
- const commitsPath=path.resolve(repoPath,"commits");
8
- const username=getUsername();
9
- const repoName=getConfigValue("repoName");
10
- try{
11
- const data = await S3.listObjectsV2({
12
- Bucket:S3_BUCKET,
13
- Prefix:`commits/${username}/${repoName}`
14
- }).promise();
15
- const objects=data.Contents;
16
- console.log(`Found ${objects.length} files to pull`);
17
- for(const object of objects){
18
- const key=object.Key;
19
- console.log(key);
20
- // Remove 'commits/' prefix to get relative path
21
- const relativePath = key.replace('commits/', ''); // e.g., 'a0f0a67b.../hello.txt'
22
- const localFilePath = path.join(commitsPath, relativePath);
23
- // Create parent directory if needed
24
- const localDir = path.dirname(localFilePath);
25
- await fs.mkdir(localDir, {recursive: true});
26
- const params={
27
- Bucket: S3_BUCKET,
28
- Key:key
29
- }
30
- const fileContent=await S3.getObject(params).promise();
31
- await fs.writeFile(localFilePath,fileContent.Body);
32
- console.log(`Downloaded: ${relativePath}`);
3
+ import { S3, S3_BUCKET } from "../config/aws-config.js"
4
+ import { getUsername, getConfigValue } from "../config/vcsConfig.js"
5
+
6
+ async function pullRepo() {
7
+ const repoPath = path.resolve(process.cwd(), ".myvcs");
8
+ const commitsPath = path.resolve(repoPath, "commits");
9
+ const commitsJsonPath = path.join(commitsPath, "commits.json");
10
+ const headPath = path.join(repoPath, "HEAD");
11
+ const username = getUsername();
12
+ const repoName = getConfigValue("repoName");
13
+
14
+ try {
15
+ // Download repo payload from S3
16
+ const params = {
17
+ Bucket: S3_BUCKET,
18
+ Key: `repos/${username}/${repoName}/repo.json`
19
+ };
20
+
21
+ const data = await S3.getObject(params).promise();
22
+ const payload = JSON.parse(data.Body.toString());
23
+
24
+ // Read local commits
25
+ let localCommits = [];
26
+ try {
27
+ const localData = await fs.readFile(commitsJsonPath, "utf-8");
28
+ localCommits = JSON.parse(localData);
29
+ } catch {
30
+ localCommits = [];
33
31
  }
34
- console.log("Pulled all commits");
35
- const commitsJsonPath=path.resolve(commitsPath,"commits.json");
36
- const commitsData=await fs.readFile(commitsJsonPath,'utf-8');
37
- const commits=JSON.parse(commitsData);
38
- if(commits.length==0){
39
- console.log("No commits to Restore");
32
+
33
+ const localHashes = new Set(localCommits.map(c => c.commitId));
34
+
35
+ // Find new commits
36
+ const newCommits = payload.commits.filter(c => !localHashes.has(c.commitId));
37
+
38
+ if (newCommits.length === 0) {
39
+ console.log("✓ Already up to date");
40
40
  return;
41
41
  }
42
- const latestCommit=commits[commits.length-1];
43
- const latestCommitId=latestCommit.commitId;
44
- const latestPathCommit=path.join(commitsPath,latestCommitId);
45
-
46
- console.log(`Restoring files from commit: ${latestCommitId}`);
47
- console.log(`Message: ${latestCommit.message}`);
48
-
49
- const files=await fs.readdir(latestPathCommit);
50
-
51
- for(const file of files){
52
- const sourcePath=path.join(latestPathCommit,file);
53
- const destPath=path.join(process.cwd(),file);
54
-
55
- await fs.copyFile(sourcePath,destPath);
56
- console.log(`Restored ${file} \n`);
42
+
43
+ console.log(`Pulling ${newCommits.length} commit(s)...`);
44
+
45
+ // Apply new commits
46
+ for (const commit of newCommits) {
47
+ const commitDir = path.join(commitsPath, commit.commitId);
48
+ await fs.mkdir(commitDir, { recursive: true });
49
+
50
+ // Write snapshot files
51
+ const snapshot = payload.snapshots[commit.commitId] || {};
52
+ for (const [filePath, content] of Object.entries(snapshot)) {
53
+ const destPath = path.join(commitDir, filePath);
54
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
55
+ await fs.writeFile(destPath, content, "utf-8");
56
+ }
57
+
58
+ // Write diffs
59
+ const diff = payload.diffs[commit.commitId] || {};
60
+ const diffsPath = path.join(commitDir, "diffs.json");
61
+ await fs.writeFile(diffsPath, JSON.stringify(diff, null, 2));
62
+
63
+ console.log(`Applied [${commit.commitId.slice(0, 7)}] ${commit.message}`);
57
64
  }
58
-
59
- }catch(err){
60
- console.log("Error pulling from S3!! ",err);
65
+
66
+ // Update commits.json
67
+ const allCommits = [...localCommits, ...newCommits];
68
+ await fs.writeFile(commitsJsonPath, JSON.stringify(allCommits, null, 2));
69
+
70
+ // Update HEAD
71
+ await fs.writeFile(headPath, payload.head);
72
+
73
+ // Restore working files from latest commit
74
+ const latestSnapshot = payload.snapshots[payload.head] || {};
75
+ for (const [filePath, content] of Object.entries(latestSnapshot)) {
76
+ const destPath = path.join(process.cwd(), filePath);
77
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
78
+ await fs.writeFile(destPath, content, "utf-8");
79
+ }
80
+
81
+ console.log(`Pulled ${newCommits.length} commit(s)`);
82
+ console.log(`HEAD -> ${payload.head.slice(0, 7)}`);
83
+
84
+ } catch (err) {
85
+ console.error("Pull failed:", err);
61
86
  }
62
87
  }
63
88
 
64
- export {pullRepo}
89
+ export { pullRepo }
@@ -1,49 +1,136 @@
1
1
  import fs from "fs/promises"
2
2
  import path from "path"
3
- import {S3,S3_BUCKET} from "../config/aws-config.js"
4
- import {getUsername,getConfigValue} from "../config/vcsConfig.js"
5
- async function pushRepo(){
6
- const repoPath=path.resolve(process.cwd(),".myvcs");
7
- const commitsPath=path.resolve(repoPath,"commits");
8
- const username=getUsername();
9
- const repoName=getConfigValue("repoName");
10
- try{
11
- const commitDirs=await fs.readdir(commitsPath);
12
- console.log(commitDirs);
13
- for(const dir of commitDirs){
14
- const dirPath=path.join(commitsPath,dir);
15
- // CHECK IF IT'S A DIRECTORY FIRST
16
- const stats = await fs.stat(dirPath);
17
- if(!stats.isDirectory()){
18
- // If it's commits.json, upload it separately
19
- if(dir === 'commits.json'){
20
- const fileContent = await fs.readFile(dirPath);
21
- const params = {
22
- Bucket: S3_BUCKET,
23
- Key: `commits/${username}/${repoName}/commits.json`,
24
- Body: fileContent
25
- };
26
- await S3.upload(params).promise();
27
- console.log('commits.json uploaded to S3');
28
- }
29
- continue; // Skip to next item
3
+ import { S3, S3_BUCKET } from "../config/aws-config.js"
4
+ import { getUsername, getConfigValue } from "../config/vcsConfig.js"
5
+ import repoModel from "../models/repoModel.js"
6
+ import userModel from "../models/userModel.js"
7
+
8
+ async function pushRepo() {
9
+ const repoPath = path.resolve(process.cwd(), ".myvcs");
10
+ const commitsPath = path.resolve(repoPath, "commits");
11
+ const commitsJsonPath = path.join(commitsPath, "commits.json");
12
+ const headPath = path.join(repoPath, "HEAD");
13
+ const username = getUsername();
14
+ const repoName = getConfigValue("repoName");
15
+
16
+ try {
17
+ // Read commits metadata
18
+ const commitsData = await fs.readFile(commitsJsonPath, "utf-8");
19
+ const commits = JSON.parse(commitsData);
20
+
21
+ if (commits.length === 0) {
22
+ console.log("No commits to push");
23
+ return;
24
+ }
25
+
26
+ // Read HEAD
27
+ const head = (await fs.readFile(headPath, "utf-8")).trim();
28
+
29
+ // Create S3 key
30
+ const s3Key = `repos/${username}/${repoName}/repo.json`;
31
+
32
+ // Check if repo exists in MongoDB, create if not
33
+ const existingRepo = await repoModel.findOne({ name: repoName });
34
+
35
+ if (!existingRepo) {
36
+ const existingUser = await userModel.findOne({ email: username });
37
+
38
+ if (!existingUser) {
39
+ throw new Error("User not found. Please register first.");
30
40
  }
31
- const files=await fs.readdir(dirPath);
32
- for(const file of files){
33
- const filePath=path.join(dirPath,file);
34
- const fileContent=await fs.readFile(filePath);
35
- const params={
36
- Bucket:S3_BUCKET,
37
- Key:`commits/${username}/${repoName}/${dir}/${file}`,
38
- Body:fileContent
39
- }
40
- await S3.upload(params).promise();
41
+
42
+ const newRepo = new repoModel({
43
+ name: repoName,
44
+ visibility: false,
45
+ owner: existingUser._id,
46
+ s3Key: s3Key,
47
+ lastCommitHash: head,
48
+ totalCommits: commits.length
49
+ });
50
+
51
+ await newRepo.save();
52
+ console.log(`Created new repo: ${repoName}`);
53
+ } else {
54
+ // Update existing repo metadata
55
+ const latestCommit = commits[commits.length - 1];
56
+ existingRepo.s3Key = s3Key;
57
+ existingRepo.lastCommitHash = head;
58
+ existingRepo.lastCommitMessage = latestCommit.message;
59
+ existingRepo.lastCommitDate = new Date(latestCommit.date);
60
+ existingRepo.totalCommits = commits.length;
61
+ await existingRepo.save();
62
+ }
63
+
64
+ // Bundle all commit snapshots and diffs
65
+ const snapshots = {};
66
+ const diffs = {};
67
+
68
+ for (const commit of commits) {
69
+ const commitDir = path.join(commitsPath, commit.commitId);
70
+
71
+ // Read snapshot files recursively
72
+ snapshots[commit.commitId] = await readCommitFiles(commitDir);
73
+
74
+ // Read diffs
75
+ const diffsPath = path.join(commitDir, "diffs.json");
76
+ try {
77
+ const diffData = await fs.readFile(diffsPath, "utf-8");
78
+ diffs[commit.commitId] = JSON.parse(diffData);
79
+ } catch {
80
+ diffs[commit.commitId] = {};
41
81
  }
42
82
  }
43
- console.log("All commits pushed to myVCS")
44
- }catch(err){
45
- console.error("Error in pushing code to GitHub!!",err);
83
+
84
+ // Create payload
85
+ const payload = {
86
+ repoName,
87
+ username,
88
+ pushedAt: new Date().toISOString(),
89
+ head,
90
+ commits,
91
+ snapshots,
92
+ diffs
93
+ };
94
+
95
+ // Upload as single JSON file
96
+ const params = {
97
+ Bucket: S3_BUCKET,
98
+ Key: s3Key,
99
+ Body: JSON.stringify(payload, null, 2),
100
+ ContentType: "application/json"
101
+ };
102
+
103
+ await S3.upload(params).promise();
104
+ console.log(`✓ Pushed ${commits.length} commit(s) to S3`);
105
+ console.log(` HEAD → ${head.slice(0, 7)}`);
106
+
107
+ } catch (err) {
108
+ console.error("Push failed:", err);
109
+ throw err; // re-throw so withDB can handle cleanup
110
+ }
111
+ }
112
+
113
+ async function readCommitFiles(commitDir, baseDir = commitDir) {
114
+ const files = {};
115
+ const items = await fs.readdir(commitDir);
116
+
117
+ for (const item of items) {
118
+ if (item === "diffs.json") continue;
119
+
120
+ const fullPath = path.join(commitDir, item);
121
+ const stat = await fs.stat(fullPath);
122
+
123
+ if (stat.isDirectory()) {
124
+ const nested = await readCommitFiles(fullPath, baseDir);
125
+ Object.assign(files, nested);
126
+ } else {
127
+ const relativePath = path.relative(baseDir, fullPath);
128
+ const content = await fs.readFile(fullPath, "utf-8");
129
+ files[relativePath] = content;
130
+ }
46
131
  }
132
+
133
+ return files;
47
134
  }
48
135
 
49
- export {pushRepo}
136
+ export { pushRepo }
@@ -1,23 +1,56 @@
1
1
  import fs from "fs/promises"
2
2
  import path from "path"
3
-
4
- async function revertRepo(commitID){
5
- const repoPath=path.resolve(process.cwd(),".myvcs");
6
- const commitPath=path.resolve(repoPath,"commits");
7
- const thisCommit=path.join(commitPath,commitID);
8
- const parentDir=path.resolve(repoPath,"..");
9
- try{
10
- const files=await fs.readdir(thisCommit);
11
-
12
- for(const file of files){
13
- await fs.copyFile(path.join(thisCommit,file),path.join(parentDir,file));
3
+ import {addRepo} from "./addRepo.js"
4
+ import { commitRepo } from "./commitRepo.js"
5
+ async function applyInverseDiff(diff, filePath) {
6
+ const invertedLines = [];
7
+
8
+ for(const d of diff) {
9
+ if(d.type === "same") {
10
+ invertedLines.push(d.line); // keep it
14
11
  }
15
-
16
- console.log(`Reverted to commitId:${commitID} successfully!!`);
17
- }
18
- catch(err){
19
- console.log("Unable to Revert!! ",err);
12
+ else if(d.type === "removed") {
13
+ invertedLines.push(d.line); // it was removed, bring it back
14
+ }
15
+ // skip "added"
20
16
  }
17
+
18
+ const content = invertedLines.join("\n");
19
+ await fs.writeFile(filePath, content);
21
20
  }
22
21
 
22
+ async function revertRepo(commitID) {
23
+ const repoPath = path.resolve(process.cwd(), ".myvcs");
24
+ const commitPath = path.resolve(repoPath, "commits");
25
+ const diffsPath = path.join(commitPath, commitID, "diffs.json");
26
+ const commitJSON = path.join(commitPath, "commits.json");
27
+
28
+ try {
29
+ // Read diffs
30
+ const diffs = JSON.parse(await fs.readFile(diffsPath, "utf-8"));
31
+
32
+ // Read original commit message
33
+ const commits = JSON.parse(await fs.readFile(commitJSON, "utf-8"));
34
+ const originalCommit = commits.find(c => c.commitId === commitID);
35
+
36
+ if (!originalCommit) {
37
+ console.error(`Commit ${commitID} not found`);
38
+ return;
39
+ }
40
+
41
+ // Apply inverse diffs and stage files
42
+ for (const [file, diff] of Object.entries(diffs)) {
43
+ const filePath = path.join(process.cwd(), file);
44
+ await applyInverseDiff(diff, filePath);
45
+ await addRepo(filePath);
46
+ }
47
+
48
+ // Create revert commit
49
+ await commitRepo(`Revert: ${originalCommit.message}`);
50
+
51
+ console.log(`Reverted commit ${commitID} successfully!`);
52
+ } catch (err) {
53
+ console.error("Unable to revert:", err);
54
+ }
55
+ }
23
56
  export {revertRepo}
@@ -0,0 +1,16 @@
1
+ import path from "path"
2
+ import fs from "fs/promises"
3
+ async function showSpecificCommit(commitId) {
4
+ const repoPath=path.resolve(process.cwd(),".myvcs");
5
+ const commitsPath=path.join(repoPath,"commits");
6
+ const diffsPath = path.join(commitsPath, commitId, "diffs.json");
7
+ const diffs = JSON.parse(await fs.readFile(diffsPath, "utf-8"));
8
+ for(const [file, diff] of Object.entries(diffs)) {
9
+ console.log(`\n${file}:`);
10
+ for(const line of diff) {
11
+ if(line.type === "added") console.log(`+ ${line.line}`);
12
+ if(line.type === "removed") console.log(`- ${line.line}`);
13
+ }
14
+ }
15
+ }
16
+ export {showSpecificCommit}
package/README.md CHANGED
@@ -8,6 +8,16 @@ npm install -g myvcs
8
8
  ```
9
9
 
10
10
  ## Setup
11
+ Create a .env file in your project:
12
+
13
+ AWS_ACCESS_KEY_ID=...
14
+ AWS_SECRET_ACCESS_KEY=...
15
+ S3_BUCKET=...
16
+ MY_NAMESPACE=...
17
+
18
+ Then run:
19
+
20
+ myvcs init
11
21
  ```
12
22
  Initialize a repository:
13
23
  ```bash
@@ -7,7 +7,7 @@ aws.config.update({
7
7
  });
8
8
 
9
9
  const S3=new aws.S3();
10
- const S3_BUCKET="amzn-777";
10
+ const S3_BUCKET=process.env.S3_BUCKET;
11
11
 
12
12
  export {S3,S3_BUCKET}
13
13
 
@@ -1,6 +1,5 @@
1
1
  import fs from "fs"
2
2
  import path from "path"
3
-
4
3
  function getConfigPath(){
5
4
  return path.join(process.cwd(),".myvcs","config.json");
6
5
  }
@@ -8,7 +7,6 @@ function configDirExists(){
8
7
  const configDir=path.join(process.cwd(),".myvcs");
9
8
  return fs.existsSync(configDir);
10
9
  }
11
-
12
10
  function initConfig(){
13
11
  const defaultConfig={
14
12
  "username":null,
@@ -50,7 +48,7 @@ function writeConfig(config){
50
48
  throw new Error("Not a myvcs repository. Run 'myvcs init' first.")
51
49
  }
52
50
  }
53
- function setConfigValue(key,value){
51
+ async function setConfigValue(key,value){
54
52
  const config=readConfig();
55
53
  config[key]=value;
56
54
  writeConfig(config);
@@ -61,11 +59,11 @@ function getConfigValue(key) {
61
59
  return config[key];
62
60
  }
63
61
  function getUsername(){
64
- const username=getConfigValue("username");
62
+ const username=getConfigValue("email");
65
63
  if(!username){
66
64
  throw new Error(
67
- "Username not configured. Please set your username with:\n" +
68
- " myvcs config set username <your-username>"
65
+ "Username not configured. Please set your email with:\n" +
66
+ " myvcs config set email <your-registered-mail>"
69
67
  );
70
68
  }
71
69
  return username;
package/index.js CHANGED
@@ -9,8 +9,24 @@ import { pullRepo } from "./Controllers/pullRepo.js";
9
9
  import {revertRepo} from "./Controllers/revertRepo.js"
10
10
  import {showCommits} from "./Controllers/showCommits.js"
11
11
  import {showConfig,setConfig} from "./Controllers/config.js"
12
+ import { showSpecificCommit } from "./Controllers/specificCommit.js";
13
+ import { connectDB, disconnectDB } from "./utils/db.js";
14
+
12
15
  yargs(hideBin(process.argv))
13
- .command("init","initialise a new repository",{},initRepo)
16
+ .command("init","initialise a new repository",{},
17
+ async ()=>{
18
+ try{
19
+ await connectDB();
20
+ await initRepo()
21
+ }
22
+ catch(e){
23
+ console.log('ERROR:',e);
24
+ }
25
+ finally{
26
+ await disconnectDB();
27
+ }
28
+ }
29
+ )
14
30
  .command("add <file>","Add a file to repo/staging area use 'add .' to add all files",
15
31
  (yargs)=>{
16
32
  yargs.positional("file",{
@@ -28,11 +44,26 @@ yargs(hideBin(process.argv))
28
44
  type:"string"
29
45
  })
30
46
  },
31
- (argv)=>{
32
- commitRepo(argv.message);
47
+ async (argv)=>{
48
+ await commitRepo(argv.message);
49
+ }
50
+ )
51
+ .command("push","push commits to cloud storage",{},
52
+ async ()=>{
53
+ try{
54
+ await connectDB();
55
+ pushRepo();
56
+ }
57
+ catch(e){
58
+ console.log('ERROR:',e);
59
+ }
60
+ finally{
61
+ await disconnectDB();
62
+ }
33
63
  }
64
+
65
+
34
66
  )
35
- .command("push","push commits to cloud storage",{},pushRepo)
36
67
  .command("pull","pull commits from cloud to local system",{},pullRepo)
37
68
  .command("revert <commitID>","revert back to specified commitID",
38
69
  (yargs)=>{
@@ -41,11 +72,22 @@ yargs(hideBin(process.argv))
41
72
  type:"string"
42
73
  })
43
74
  },
44
- (argv)=>{
45
- revertRepo(argv.commitID);
75
+ async (argv)=>{
76
+ await revertRepo(argv.commitID);
46
77
  }
47
78
  )
48
79
  .command("list commits","show all commmits",{},showCommits)
80
+ .command("show <commitID>","show specific CommitID",
81
+ (yargs)=>{
82
+ yargs.positional("commitID",{
83
+ describe:"commitID to revert",
84
+ type:"string"
85
+ })
86
+ }
87
+ ,async (argv)=>{
88
+ await showSpecificCommit(argv.commitID);
89
+ }
90
+ )
49
91
  .command("config","configure myVCS on your system",
50
92
  (yargs)=>{
51
93
  return yargs
@@ -61,8 +103,18 @@ yargs(hideBin(process.argv))
61
103
  type:"string"
62
104
  })
63
105
  },
64
- (argv)=>{
65
- setConfig(argv.key,argv.value);
106
+ async (argv)=>{
107
+ try{
108
+ await connectDB();
109
+ await setConfig(argv.key,argv.value);
110
+ }
111
+ catch(e){
112
+ console.log('Error while configuring:',e);
113
+ }
114
+ finally{
115
+ await disconnectDB();
116
+ process.exit(0);
117
+ }
66
118
  }
67
119
  )
68
120
  .command("list","show config details",{},showConfig)
package/package.json CHANGED
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "name": "myvcs",
3
- "version": "0.1.0",
3
+ "version": "1.1.2",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
7
7
  },
8
- "keywords": ["vcs", "version-control", "git", "s3", "cloud-storage"],
8
+ "keywords": [
9
+ "vcs",
10
+ "version-control",
11
+ "git",
12
+ "s3",
13
+ "cloud-storage"
14
+ ],
9
15
  "type": "module",
10
16
  "author": "Amarnath Vasanth amarnathvasanth39@gmail.com",
11
17
  "license": "ISC",
@@ -15,11 +21,12 @@
15
21
  },
16
22
  "repository": {
17
23
  "type": "git",
18
- "url": "https://github.com/yourusername/myvcs.git"
24
+ "url": "https://github.com/amar1009/VCStool.git"
19
25
  },
20
26
  "dependencies": {
21
27
  "aws-sdk": "^2.1693.0",
22
28
  "dotenv": "^17.2.3",
29
+ "mongoose": "^9.2.2",
23
30
  "uuid": "^13.0.0",
24
31
  "yargs": "^18.0.0"
25
32
  }
package/utils/db.js ADDED
@@ -0,0 +1,11 @@
1
+ import mongoose from 'mongoose';
2
+
3
+ export async function connectDB() {
4
+ if (mongoose.connection.readyState === 0) {
5
+ await mongoose.connect(process.env.MONGO_URI);
6
+ }
7
+ }
8
+
9
+ export async function disconnectDB() {
10
+ await mongoose.disconnect();
11
+ }