//
// 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 crypto = require('crypto');
const child_process = require('child_process');
const version = 'vscode-20250826';
const minTimeInterval = 1; // minimum time between versions
const username = os.userInfo().username.replace(/[^a-zA-Z0-9]/g, "");
let hash = {}; // last hash value for each version file
let size = {}; // last size for each version file
let prior = {}; // prior contents for each file
let priorTime = {}; // last time each file was written
let sessionStart = time();
let hashInitial = crypto.createHash('sha256').update(username + ' ' + sessionStart.toString());
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 => {
writeFile(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
function initVersionDir(dir) {
if (! (dir in versionDir)) {
try {
const stdout = child_process.execSync('cd "' + dir + '"; git remote -v 2> /dev/null');
if (stdout.indexOf('eecs482') < 0) {
versionDir[dir] = '';
} else {
versionDir[dir] = child_process.execSync('cd "' + dir + '"; git rev-parse --show-toplevel').toString().trimEnd() + path.sep + '.version482';
try {
if (! fs.statSync(versionDir[dir]).isDirectory) {
versionDir[dir] = '';
}
} catch (err) {
fs.mkdirSync(versionDir[dir]);
}
}
} catch (err) {
versionDir[dir] = '';
}
}
}
function initFile(doc) {
initVersionDir(path.dirname(doc.fileName));
}
function writeFile(doc) {
initVersionDir(path.dirname(doc.fileName));
// make sure file is in an EECS 482 git repo
if (versionDir[path.dirname(doc.fileName)] == '') {
return;
}
child_process.execSync('cd "' + path.dirname(doc.fileName) + '"; git add "' + versionDir[path.dirname(doc.fileName)] + '"');
}
function textChanged(doc) {
let now = time();
// Limit the rate of versioning events. Also log events where time has
// gone backward by more than minTimeInterval.
if (doc.fileName in priorTime
&& Math.abs(now - priorTime[doc.fileName]) < minTimeInterval) {
// 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, minTimeInterval*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;
}
// make sure file isn't too big
if (doc.getText().length > 10 * 1024 * 1024) {
return;
}
const versionDirname = versionDir[path.dirname(doc.fileName)];
// check if version file has grown too old or too big
if (now - sessionStart > 60*60 || (versionDirname in size && size[versionDirname] > 50 * 1024 * 1024)) {
// start new version file by mimicking restarting vscode
hash = {};
size = {};
prior = {};
priorTime = {};
sessionStart = now;
hashInitial = crypto.createHash('sha256').update(username + ' ' + sessionStart.toString());
}
if (! (versionDirname in hash)) {
hash[versionDirname] = hashInitial.copy();
}
if (! (versionDirname in size)) {
size[versionDirname] = 0;
}
const priorname = versionDirname + path.sep + sessionStart.toString() + '.' + username + '.prior';
const currentname = versionDirname + path.sep + sessionStart.toString() + '.' + username + '.current';
if (! (doc.fileName in prior)) {
prior[doc.fileName] = '';
}
fs.writeFileSync(priorname, prior[doc.fileName]);
const current = doc.getText();
fs.writeFileSync(currentname, current);
const versionfile = versionDirname + path.sep + sessionStart.toString() + '.' + username;
const diff = child_process.execSync('diff "' + priorname + '" "' + currentname + '"; rm "' + priorname + '" "' + currentname + '"').toString();
if (diff != '') {
let dict = {};
dict['file'] = doc.fileName;
dict['diff'] = diff;
const line = version + ' ' + now + ' '
+ size[versionDirname] + ' '
+ hash[versionDirname].copy().digest('hex') + ' '
+ JSON.stringify(dict);
try {
fs.appendFileSync(versionfile, line + "\n", () => {});
} catch (err) {
return;
}
prior[doc.fileName] = current;
priorTime[doc.fileName] = now;
hash[versionDirname] = crypto.createHash('sha256').update(line);
size[versionDirname] += line.length + 1;
// console.log(size[versionDirname]);
}
}
| |