Skip to content
| Marketplace
Sign in
Visual Studio Code>Other>version482New to Visual Studio Code? Get it now.
version482

version482

pmchen

|
1,195 installs
| (0) | Free
Keeps versions of files in EECS 482 github repos
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info
//
// Keep versions of files in EECS 482 github repos
//
const vscode = require('vscode');
const os = require('os');
const fs = require('fs');
const path = require('path');
const child_process = require('child_process');
const version = 'vscode-20260506';
const minTimeVersion = 10;      // minimum time between versions
const minTimePush = 60;         // minimum time between pushes
const minTimeCheck = 600;       // minimum time between checking version482 repo

let versionTime = {};   // last time each file was written
let pushTime = {};      // last time each repo was pushed
let checkTime = 0;      // last time version482 repo was checked

let sessionStart = time();

let versionDir = {};    // .version482 directory name for each source file directory
let timer = {};

exports.activate = () => {
    vscode.workspace.textDocuments.forEach(doc => initFile(doc));
    vscode.workspace.onDidOpenTextDocument(doc => {
        initFile(doc);
    });
    vscode.workspace.onDidSaveTextDocument(doc => {
        saveFile(doc);
    });
    vscode.workspace.onDidChangeTextDocument(e => {
        textChanged(e.document);
    });
}

// return current time in seconds
function time() {
    return(Math.trunc(Date.now()/1000));
}

// See if directory is in an EECS 482 git repo.
// Store .version482 directory name in versionDir, and make sure the .version482
// directory exists and is properly configured.  If the directory isn't in a
// git repo, then set the versionDir entry to the empty string.
function initVersionDir(dir) {
    if (dir in versionDir) {
        return;
    }

    versionDir[dir] = "";
    let date1 = new Date();
    let dateString = date1.getFullYear().toString() + '.'
                    + (date1.getMonth()+1).toString().padStart(2, '0') + '.'
		    + date1.getDate().toString().padStart(2, '0') + '_'
		    + date1.getHours().toString().padStart(2, '0') + '.'
		    + date1.getMinutes().toString().padStart(2, '0') + '.'
		    + date1.getSeconds().toString().padStart(2, '0');

    let dirFound = "";
    if (fs.existsSync(dir)) {
	dirFound = dir;
    } else {
	// Windows VS Code may prepend extra directory components when WSL
	// files are opened as local files).  Fix this by removing directory
	// components from the beginning of the directory name until I find a
	// directory that exists.

	// Parse the directory name into components.
	let dirRoot = dir;
	let dirRootLast;
	let basename = "";
	let components = [];
	do {
	    dirRootLast = dirRoot;
	    basename = path.basename(dirRoot);
	    if (basename != "") {
		components.unshift(basename);
		dirRoot = path.dirname(dirRoot);
	    }
	} while (basename != "" && dirRoot != dirRootLast);

	// Look for the directory, removing prefixes if needed.
	for (let i=0; i<components.length && dirFound == ""; i++) {
	    const dir1 = dirRoot + components.slice(i).join(path.sep);
	    if (fs.existsSync(dir1)) {
		dirFound = dir1;
	    }
	}
    }
    if (dirFound == "") {
	console.log('initVersionDir cannot find ' + dir);
        return;
    }

    try {
        // Get top level of working tree
	// This will throw an exception if git rev-parse fails, e.g., if
	// the directory is not in a work tree
	let top = child_process.execSync('git -C "' + dirFound + '" rev-parse --show-toplevel').toString().trimEnd();

	// Make sure top level is an eecs482 repo and get name of main repo
	const origin = child_process.execSync('git -C "' + dirFound + '" remote get-url origin 2> /dev/null').toString();
	const found = origin.match(/^(git@github.com:|https:\/\/github.com\/)(eecs482\/[a-z.]+\d+)(.*)/);
	if (! found) {
	    throw new Error('File is not in an eecs482 project repository');
	}
	const protocol = found[1];
	const repo = found[2];
	const suffix = found[3];

	// Make sure main repo is not a version482 repo
	if (origin.indexOf('version482') >= 0) {
	    // disallow editing of files in version482 repo
	    vscode.commands.executeCommand('workbench.action.closeActiveEditor');
	    throw new Error('File is in a version482 repo');
	}

	// compute correct branch for this local repo
	let branch = os.userInfo().username + os.hostname().replace(/\..*/, "") + child_process.execSync('uname -s').toString().trimEnd();
	if (fs.existsSync('/etc/os-release')) {
	    branch += child_process.execSync('grep "^ID=" /etc/os-release | sed "s/^.*=//"').toString().trimEnd();
	}
	branch += top;
	branch = branch.replace(/[^a-zA-Z0-9]/g, "");

	versionDir[dir] = top + path.sep + '.version482.' + branch;

	// Check version482 repository and move it aside if broken
	try {
	    // Make sure version482 exists and is a directory
	    if (! fs.existsSync(versionDir[dir]) || ! fs.statSync(versionDir[dir]).isDirectory) {
		throw new Error(versionDir[dir] + ' does not exist or is not a directory.');
	    }

	    // Make sure version482 is its own repo
	    let version482_top = child_process.execSync('git -C "' + versionDir[dir] + '" rev-parse --show-toplevel').toString().trimEnd();
	    if (version482_top != versionDir[dir]) {
		throw new Error(versionDir[dir] + ' is not in its own repository.');
	    }

	    // Make sure origin for version482 repo is consistent with origin for
	    // main repo
	    let url = child_process.execSync('git -C "' + versionDir[dir] + '" remote get-url origin 2> /dev/null').toString().trimEnd();
	    if (url != protocol + repo + '.version482' + suffix) {
		throw new Error('origin for ' + versionDir[dir] + ' is inconsistent with origin for main repo.');
	    }

	    // Make sure version482 repo is on the correct branch
	    let branch1 = child_process.execSync('git -C "' + versionDir[dir] + '" branch --show-current').toString().trimEnd();
	    if (branch1 != branch) {
		throw new Error(versionDir[dir] + ' is on wrong branch ' + branch1);
	    }

	    // Make sure version482 repo's upstream is set to the corresponding
	    // branch on github
	    let remote = child_process.execSync('git -C "' + versionDir[dir] + '" rev-parse --abbrev-ref "@{upstream}" 2> /dev/null').toString().trimEnd();
	    if (remote != "origin/" + branch) {
		throw new Error(versionDir[dir] + ' has the wrong upstream');
	    }

	    // Make sure I can add a file in the version482 repo
	    child_process.execSync('touch "' + versionDir[dir] + path.sep + 'tmp.' + dateString + '"; git -C "' + versionDir[dir] + '" add -f "tmp.' + dateString + '" > /dev/null 2>&1');

	    // Make sure I can commit the version482 repo
	    child_process.execSync('git -C "' + versionDir[dir] + '" commit -m "vscode-tmp" > /dev/null 2>&1');

	    // Undo temporary commit.  Use --soft to preserve the files that
	    // were already staged before the temporary commit.
	    child_process.execSync('git -C "' + versionDir[dir] + '" reset --soft HEAD~ > /dev/null');

	    // Make sure I can remove a file in the version482 repo
	    child_process.execSync('git -C "' + versionDir[dir] + '" rm -f -q "tmp.' + dateString + '" > /dev/null');
	} catch (err) {
	    // console.log(err);
	    if (fs.existsSync(versionDir[dir])) {
	        // Move broken version482 directory aside
	        let oldDir = versionDir[dir] + '.' + dateString;
		while (fs.existsSync(oldDir)) {
		    oldDir += ".";
		}
		fs.renameSync(versionDir[dir], oldDir);
	    }
	}

	if (! fs.existsSync(versionDir[dir])) {
	    // Clone and set up version482 repo
	    child_process.execSync('export SSH_ASKPASS=echo; export SSH_ASKPASS_REQUIRE=force; git -C "' + top + '" clone ' + protocol + repo + '.version482' + suffix + ' "' + versionDir[dir] + '" > /dev/null 2>&1');

	    // Try to checkout branch, in case this branch already exists
	    try {
		child_process.execSync('git -C "' + versionDir[dir] + '" checkout ' + branch + ' > /dev/null 2>&1');
	    } catch (err) {
		// Branch didn't exist (this is the common case)

		// Create new branch
		child_process.execSync('git -C "' + versionDir[dir] + '" checkout -b ' + branch + ' --no-track > /dev/null 2>&1');

		// Set upstream to github, and create branch on github
		child_process.execSync('git -C "' + versionDir[dir] + '" push --set-upstream origin ' + branch + ' > /dev/null 2>&1');
	    }
	}
    } catch (err) {
	console.log('initVersionDir(' + dir + ') caught exception');
	console.log(err);
	versionDir[dir] = '';
    }
}

function initFile(doc) {
    initVersionDir(path.dirname(doc.fileName));
}

function textChanged(doc) {
    const now = time();

    // Limit the rate of versioning events.  Also save events where time has
    // gone backward by more than minTimeVersion.
    if (doc.fileName in versionTime
            && Math.abs(now - versionTime[doc.fileName]) < minTimeVersion) {
        // replace any pending timer event for this file, so events don't pile up
        if (doc.fileName in timer) {
            clearTimeout(timer[doc.fileName]);
        }
        // make sure this version is eventually saved
        timer[doc.fileName] = setTimeout(textChanged,
	    (minTimeVersion-(now-versionTime[doc.fileName]))*1000, doc);
        return;
    }

    initVersionDir(path.dirname(doc.fileName));

    // make sure file is in an EECS 482 git repo
    if (versionDir[path.dirname(doc.fileName)] == '') {
        return;
    }

    // make sure file is a program source file, i.e., has extension
    // {cpp,cc,h,hpp,py}
    const ext = path.extname(doc.fileName);
    if (ext != '.cpp' && ext != '.cc' && ext != '.h' && ext != '.hpp' && ext != '.py') {
        return;
    }

    const versionDirname = versionDir[path.dirname(doc.fileName)];

    // create/update file
    const basename = path.basename(doc.fileName);
    fs.writeFileSync(versionDirname + path.sep + basename, doc.getText());

    // add file
    child_process.execSync('git -C "' + versionDirname + '" add -f "' + basename + '" > /dev/null 2>&1');

    // commit changes
    child_process.execSync('git -C "' + versionDirname + '" commit --allow-empty -m "' + version + '" > /dev/null 2>&1');

    versionTime[doc.fileName] = now;
}

// push commits to github
function saveFile(doc) {
    console.log('saveFile');
    const now = time();

    // Clear versionDir every so often, so the version482 repo gets re-checked.
    if (now - checkTime >= minTimeCheck) {
	versionDir = {};
        checkTime = now;
    }

    initVersionDir(path.dirname(doc.fileName));

    // make sure file is in an EECS 482 git repo
    if (versionDir[path.dirname(doc.fileName)] == '') {
        return;
    }
    const versionDirname = versionDir[path.dirname(doc.fileName)];

    // limit the rate of pushing
    if (versionDirname in pushTime
            && now - pushTime[versionDirname] < minTimePush) {
        // replace any pending push for this directory, so events don't pile up
        if (versionDirname in timer) {
            clearTimeout(timer[versionDirname]);
        }
        // make sure this event is eventually pushed
        timer[versionDirname] = setTimeout(saveFile,
            (minTimePush-(now-pushTime[versionDirname]))*1000, doc);
        return;
    }

    // add version482 entry, so it's as new as the saved file
    textChanged(doc);

    // push to github
    child_process.execSync('export SSH_ASKPASS=echo; export SSH_ASKPASS_REQUIRE=force; git -C "' + versionDirname + '" push --quiet > /dev/null &');
    pushTime[versionDirname] = now;
}
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft