Lots of code cleanup needed here
S34LGQX3XOVZUKWNE2YQNB3LJPXBBHP5GUYPYGTBFV63HYBLTQWAC
pub enum ResourceState {
Modified,
}
fn get_hunks(
cx: &mut FunctionContext,
path: Option<PathBuf>,
stop_early: bool,
) -> Result<Vec<PrintableHunk>, anyhow::Error> {
let repo = Repository::find_root(path.clone()).expect("Could not find root");
let txn = repo.pristine.arc_txn_begin()?;
let channel_name = txn
.read()
.current_channel()
.unwrap_or(libpijul::DEFAULT_CHANNEL)
.to_string();
let channel = txn.write().open_or_create_channel(&channel_name)?;
let prefix = if let Some(path) = path {
path.strip_prefix(&repo.path)?.to_string_lossy().to_string()
} else {
String::new()
};
console_log(cx, format!("Prefix: {prefix} root: {:?}", repo.path)).unwrap();
let mut record_builder = RecordBuilder::new();
record_builder.record(
txn.clone(),
libpijul::Algorithm::default(),
stop_early,
&libpijul::DEFAULT_SEPARATOR,
channel.clone(),
&repo.working_copy,
&repo.changes,
&prefix,
std::thread::available_parallelism()?.into(),
)?;
let recorded = record_builder.finish();
let write_txn = txn.write();
let actions: Vec<_> = recorded
.actions
.into_iter()
.map(|rec| rec.globalize(&*write_txn).unwrap())
.collect();
let contents = if let Ok(cont) = std::sync::Arc::try_unwrap(recorded.contents) {
cont.into_inner()
} else {
unreachable!()
};
let change = LocalChange::make_change(
&*write_txn,
&channel,
actions,
contents,
ChangeHeader::default(),
Vec::new(),
)?;
let (dependencies, extra_known) =
libpijul::change::dependencies(&*write_txn, &*channel.read(), change.changes.iter())?;
let mut i = 0;
let mut hashes = HashMap::default();
for hash in dependencies.iter() {
hashes.insert(*hash, i);
i += 1;
}
for hash in extra_known.iter() {
if let Entry::Vacant(entry) = hashes.entry(*hash) {
entry.insert(i);
i += 1;
}
}
change.write_all_deps(|hash| {
if let Entry::Vacant(entry) = hashes.entry(hash) {
entry.insert(i);
i += 1;
}
Ok(())
})?;
Ok(change
.changes
.iter()
.map(|hunk| {
hunk.to_owned()
.to_printable(&repo.changes, &hashes, &change.contents)
.unwrap()
})
.collect())
}
#[derive(Eq, PartialEq)]
pub enum Annotation {
Added,
Modified,
}
impl Annotation {
fn display(&self) -> String {
match self {
Annotation::Added => "A",
Annotation::Modified => "M",
}
.to_string()
}
fn tooltip(&self) -> String {
match self {
Annotation::Added => "Added",
Annotation::Modified => "Modified",
}
.to_string()
}
}
fn short_diff(mut cx: FunctionContext) -> JsResult<JsArray> {
let path: Handle<JsString> = cx.argument(0)?;
let path = PathBuf::from(path.value(&mut cx));
let hunks = get_hunks(&mut cx, Some(path), true).expect("Unable to load hunks");
let mut annotations: HashMap<String, Vec<Annotation>> = HashMap::new();
console_log(&mut cx, format!("Hunk length: {}", hunks.len()))?;
for hunk in hunks.iter() {
let (path, annotation) = match hunk {
PrintableHunk::FileAddition { name, parent, .. } => {
(PathBuf::from(parent).join(name), Annotation::Added)
}
PrintableHunk::Replace { path, .. } => (PathBuf::from(path), Annotation::Modified),
PrintableHunk::Edit { path, .. } => (PathBuf::from(path), Annotation::Modified),
_ => continue,
};
let entry = annotations
.entry(path.to_string_lossy().to_string())
.or_insert(vec![]);
if !entry.contains(&annotation) {
entry.push(annotation);
}
}
let annotations_array = cx.empty_array();
for (i, (path, file_annotations)) in annotations.iter().enumerate() {
let annotations = cx.empty_object();
let path = cx.string(path);
let display_annotations = cx.string(
file_annotations
.iter()
.map(|i| i.display())
.collect::<Vec<String>>()
.join(", "),
);
let tooltip_annotations = cx.string(
file_annotations
.iter()
.map(|i| i.tooltip())
.collect::<Vec<String>>()
.join(", "),
);
annotations.set(&mut cx, "path", path)?;
annotations.set(&mut cx, "display", display_annotations)?;
annotations.set(&mut cx, "tooltip", tooltip_annotations)?;
annotations_array.set(&mut cx, i as u32, annotations)?;
}
Ok(annotations_array)
}
// TODO: ignored should include untracked files
fn ignored(mut cx: FunctionContext) -> JsResult<JsBoolean> {
let root: Handle<JsString> = cx.argument(0)?;
let root: String = root.value(&mut cx);
let root = CanonicalPath::new(&root).unwrap();
let path: Handle<JsString> = cx.argument(1)?;
let path: String = path.value(&mut cx);
let path = CanonicalPath::new(Path::new(&path)).unwrap();
Ok(
cx.boolean(!libpijul::working_copy::filesystem::filter_ignore(
root,
path,
root.join(path).unwrap().is_dir(),
)),
)
}
// TODO: fn tracked()
fn find_closest_root(mut cx: FunctionContext) -> JsResult<JsString> {
let repositories: Handle<JsArray> = cx.argument(0)?;
let repositories = repositories.to_vec(&mut cx)?;
assert!(!repositories.is_empty());
let path: Handle<JsString> = cx.argument(1)?;
let path = PathBuf::from(path.value(&mut cx));
let mut best_path = PathBuf::new();
for repository in repositories {
let repository_path: Handle<JsString> = repository.downcast(&mut cx).unwrap();
let repository_path = PathBuf::from(repository_path.value(&mut cx));
if path.strip_prefix(&repository_path).is_ok() {
if repository_path.ancestors().count() > best_path.ancestors().count() {
best_path = repository_path;
}
}
}
Ok(cx.string(&best_path.to_string_lossy()))
}
import { commands, ExtensionContext, Uri, window, workspace } from 'vscode';
import { commands, ExtensionContext, Uri, window, workspace, scm, SourceControl, RelativePattern, SourceControlResourceGroup } from 'vscode';
import path from 'path';
const { resolveRepositories, shortDiff, ignored, findClosestRoot } = require('../index.node');
}
function update_resource(repository: string, resource: { path: string, display: string, tooltip: string, untracked: boolean }) {
const repo = source_control_views.get(repository)!;
let resource_states = resource.untracked ? repo.resource_groups[1].resourceStates : repo.resource_groups[0].resourceStates;
let to_replace = resource_states.find((value, _index) => {
value.resourceUri === Uri.file(resource.path)
});
const new_resource = {
resourceUri: Uri.file(path.join(repository, resource.path)),
decorations: {
tooltip: resource.tooltip,
}
}
if (to_replace === undefined) {
// Does not exst as resource in group
source_control_views.get(repository)!.resource_groups[0].resourceStates = [new_resource];
} else {
to_replace = new_resource;
}
}
interface LoadedRepository {
source_control: SourceControl,
resource_groups: SourceControlResourceGroup[],
const repositories = resolveRepositories(paths_to_resolve());
for (let repo_path of repositories) {
const repo_scm = scm.createSourceControl(scm_id, scm_label, Uri.file(repo_path));
if (!source_control_views.has(repo_path)) {
console.log(`Activating source control for repository: ${repo_path}`);
// Each 'resource group' appears as its own heading
const groups = [
repo_scm.createResourceGroup('unrecorded', 'Unrecorded changes'),
repo_scm.createResourceGroup('untracked', 'Untracked files'),
]
// TODO: might need to fix https://github.com/microsoft/vscode/issues/162433 to make dynamic
repo_scm.inputBox.placeholder = "Message (Ctrl+Enter to record)";
const watcher = workspace.createFileSystemWatcher(new RelativePattern(repo_scm.rootUri!, '**/*'));
// When a file is created, check to see if pijul is tracking it
// watcher.onDidCreate((event) => {
// })
watcher.onDidChange((event) => {
try {
const repository = findClosestRoot(Array.from(source_control_views.keys()), event.fsPath);
if (!ignored(repository, event.fsPath)) {
console.log(`Responding to change event on file ${event.fsPath} with parent ${repository}`);
const diffs = shortDiff(event.fsPath);
console.assert(diffs.length <= 1);
// // TODO: check if file is tracked
if (diffs.length === 1) {
// File has changed, update diff in explorer tree
update_resource(repository, diffs[0]);
} else {
// File has no diff, clear any diff in explorer tree
}
} else {
console.log(`Ignored change event from ${event.fsPath}`);
}
} catch (e) {
console.error(e);
}
})
let loaded_repository: LoadedRepository = {
source_control: repo_scm,
resource_groups: groups,
}
source_control_views.set(repo_path, loaded_repository);
}
}