Add simple glob ignore patterns
This commit is contained in:
parent
bbb2adce63
commit
ceb217cda8
8 changed files with 95 additions and 8 deletions
|
|
@ -11,7 +11,7 @@ import { HistoryView } from "./views/history/history-view";
|
||||||
import { StatusBar } from "./views/status-bar/status-bar";
|
import { StatusBar } from "./views/status-bar/status-bar";
|
||||||
import { LogsView } from "./views/logs/logs-view";
|
import { LogsView } from "./views/logs/logs-view";
|
||||||
import { StatusDescription } from "./views/status-description/status-description";
|
import { StatusDescription } from "./views/status-description/status-description";
|
||||||
import { SyncClient, rateLimit } from "sync-client";
|
import { SyncClient, rateLimit, DEFAULT_SETTINGS } from "sync-client";
|
||||||
import { ObsidianFileSystemOperations } from "./obsidian-file-system";
|
import { ObsidianFileSystemOperations } from "./obsidian-file-system";
|
||||||
import { SyncSettingsTab } from "./views/settings/settings-tab";
|
import { SyncSettingsTab } from "./views/settings/settings-tab";
|
||||||
import { registerConsoleForLogging } from "./utils/register-console-for-logging";
|
import { registerConsoleForLogging } from "./utils/register-console-for-logging";
|
||||||
|
|
@ -27,6 +27,8 @@ export default class VaultLinkPlugin extends Plugin {
|
||||||
>();
|
>();
|
||||||
|
|
||||||
public async onload(): Promise<void> {
|
public async onload(): Promise<void> {
|
||||||
|
DEFAULT_SETTINGS.ignorePatterns.push(".obsidian", ".git");
|
||||||
|
|
||||||
this.client = await SyncClient.create({
|
this.client = await SyncClient.create({
|
||||||
fs: new ObsidianFileSystemOperations(
|
fs: new ObsidianFileSystemOperations(
|
||||||
this.app.vault,
|
this.app.vault,
|
||||||
|
|
|
||||||
|
|
@ -253,6 +253,29 @@ export class SyncSettingsTab extends PluginSettingTab {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Ignore patterns")
|
||||||
|
.setDesc(
|
||||||
|
"Patterns to ignore when syncing. Each line is a separate glob pattern. Patterns are matched against the relative path of the file. For example, to ignore all files in a folder named 'ignore', enter 'ignore/*'. To ignore all files with the extension '.log', enter '*.log'."
|
||||||
|
)
|
||||||
|
.addTextArea((text) =>
|
||||||
|
text
|
||||||
|
.setValue(
|
||||||
|
this.syncClient.getSettings().ignorePatterns.join("\n")
|
||||||
|
)
|
||||||
|
.setPlaceholder("Enter patterns to ignore, one per line")
|
||||||
|
.onChange(async (value) => {
|
||||||
|
const patterns = value
|
||||||
|
.split("\n")
|
||||||
|
.map((pattern) => pattern.trim())
|
||||||
|
.filter((pattern) => pattern.length > 0);
|
||||||
|
return this.syncClient.setSetting(
|
||||||
|
"ignorePatterns",
|
||||||
|
patterns
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName("Sync concurrency")
|
.setName("Sync concurrency")
|
||||||
.setDesc(
|
.setDesc(
|
||||||
|
|
|
||||||
25
frontend/package-lock.json
generated
25
frontend/package-lock.json
generated
|
|
@ -7884,6 +7884,7 @@
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"byte-base64": "^1.1.0",
|
"byte-base64": "^1.1.0",
|
||||||
|
"minimatch": "^10.0.1",
|
||||||
"openapi-fetch": "0.13.5",
|
"openapi-fetch": "0.13.5",
|
||||||
"openapi-typescript": "7.6.1",
|
"openapi-typescript": "7.6.1",
|
||||||
"p-queue": "^8.1.0",
|
"p-queue": "^8.1.0",
|
||||||
|
|
@ -7904,6 +7905,30 @@
|
||||||
"ws": "^8.18.1"
|
"ws": "^8.18.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"sync-client/node_modules/brace-expansion": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sync-client/node_modules/minimatch": {
|
||||||
|
"version": "10.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
|
||||||
|
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"test-client": {
|
"test-client": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"byte-base64": "^1.1.0",
|
"byte-base64": "^1.1.0",
|
||||||
|
"minimatch": "^10.0.1",
|
||||||
"openapi-fetch": "0.13.5",
|
"openapi-fetch": "0.13.5",
|
||||||
"openapi-typescript": "7.6.1",
|
"openapi-typescript": "7.6.1",
|
||||||
"p-queue": "^8.1.0",
|
"p-queue": "^8.1.0",
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ export {
|
||||||
type HistoryEntry
|
type HistoryEntry
|
||||||
} from "./tracing/sync-history";
|
} from "./tracing/sync-history";
|
||||||
export { Logger, LogLevel, LogLine } from "./tracing/logger";
|
export { Logger, LogLevel, LogLine } from "./tracing/logger";
|
||||||
export { type SyncSettings } from "./persistence/settings";
|
export { type SyncSettings, DEFAULT_SETTINGS } from "./persistence/settings";
|
||||||
export { rateLimit } from "./utils/rate-limit";
|
export { rateLimit } from "./utils/rate-limit";
|
||||||
export type { RelativePath, StoredDatabase } from "./persistence/database";
|
export type { RelativePath, StoredDatabase } from "./persistence/database";
|
||||||
export type {
|
export type {
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,17 @@ export interface SyncSettings {
|
||||||
syncConcurrency: number;
|
syncConcurrency: number;
|
||||||
isSyncEnabled: boolean;
|
isSyncEnabled: boolean;
|
||||||
maxFileSizeMB: number;
|
maxFileSizeMB: number;
|
||||||
|
ignorePatterns: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: SyncSettings = {
|
export const DEFAULT_SETTINGS: SyncSettings = {
|
||||||
remoteUri: "",
|
remoteUri: "",
|
||||||
token: "",
|
token: "",
|
||||||
vaultName: "default",
|
vaultName: "default",
|
||||||
syncConcurrency: 1,
|
syncConcurrency: 1,
|
||||||
isSyncEnabled: false,
|
isSyncEnabled: false,
|
||||||
maxFileSizeMB: 10
|
maxFileSizeMB: 10,
|
||||||
|
ignorePatterns: []
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Settings {
|
export class Settings {
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,11 @@ import type { FileOperations } from "../file-operations/file-operations";
|
||||||
import { createPromise } from "../utils/create-promise";
|
import { createPromise } from "../utils/create-promise";
|
||||||
import { FileNotFoundError } from "../file-operations/file-not-found-error";
|
import { FileNotFoundError } from "../file-operations/file-not-found-error";
|
||||||
import { SyncResetError } from "../services/sync-reset-error";
|
import { SyncResetError } from "../services/sync-reset-error";
|
||||||
|
import { makeRe } from "minimatch";
|
||||||
|
|
||||||
export class UnrestrictedSyncer {
|
export class UnrestrictedSyncer {
|
||||||
|
private ignorePatterns: RegExp[];
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly database: Database,
|
private readonly database: Database,
|
||||||
|
|
@ -25,7 +28,16 @@ export class UnrestrictedSyncer {
|
||||||
private readonly syncService: SyncService,
|
private readonly syncService: SyncService,
|
||||||
private readonly operations: FileOperations,
|
private readonly operations: FileOperations,
|
||||||
private readonly history: SyncHistory
|
private readonly history: SyncHistory
|
||||||
) {}
|
) {
|
||||||
|
this.ignorePatterns = this.globsToRegex(
|
||||||
|
this.settings.getSettings().ignorePatterns
|
||||||
|
);
|
||||||
|
|
||||||
|
this.settings.addOnSettingsChangeListener((newSettings) => {
|
||||||
|
this.ignorePatterns = this.globsToRegex(newSettings.ignorePatterns);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async unrestrictedSyncLocallyCreatedFile(
|
public async unrestrictedSyncLocallyCreatedFile(
|
||||||
document: DocumentRecord
|
document: DocumentRecord
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
@ -373,7 +385,14 @@ export class UnrestrictedSyncer {
|
||||||
syncType: SyncType,
|
syncType: SyncType,
|
||||||
fn: () => Promise<T>
|
fn: () => Promise<T>
|
||||||
): Promise<T | undefined> {
|
): Promise<T | undefined> {
|
||||||
this.logger.debug(`Syncing ${relativePath} (${syncType})`);
|
for (const pattern of this.ignorePatterns) {
|
||||||
|
if (pattern.test(relativePath)) {
|
||||||
|
this.logger.debug(
|
||||||
|
`File '${relativePath}' is ignored by the ignore pattern: ${pattern}`
|
||||||
|
);
|
||||||
|
return; // bail without SKIPPED status because we were told to ignore this file and we shouldn't clutter up the history
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (await this.operations.exists(relativePath)) {
|
if (await this.operations.exists(relativePath)) {
|
||||||
|
|
@ -385,7 +404,7 @@ export class UnrestrictedSyncer {
|
||||||
|
|
||||||
if (sizeInMB > this.settings.getSettings().maxFileSizeMB) {
|
if (sizeInMB > this.settings.getSettings().maxFileSizeMB) {
|
||||||
this.history.addHistoryEntry({
|
this.history.addHistoryEntry({
|
||||||
status: SyncStatus.ERROR,
|
status: SyncStatus.SKIPPED,
|
||||||
relativePath,
|
relativePath,
|
||||||
message: `File size of ${sizeInMB} MB exceeds the maximum file size limit of ${
|
message: `File size of ${sizeInMB} MB exceeds the maximum file size limit of ${
|
||||||
this.settings.getSettings().maxFileSizeMB
|
this.settings.getSettings().maxFileSizeMB
|
||||||
|
|
@ -422,4 +441,18 @@ export class UnrestrictedSyncer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private globsToRegex(globs: string[]): RegExp[] {
|
||||||
|
return globs
|
||||||
|
.map((pattern) => {
|
||||||
|
const result = makeRe(pattern);
|
||||||
|
if (result === false) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Failed to parse ${pattern}' as a glob pattern, skipping it`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
.filter((pattern) => pattern !== false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ export enum SyncType {
|
||||||
|
|
||||||
export enum SyncStatus {
|
export enum SyncStatus {
|
||||||
SUCCESS = "SUCCESS",
|
SUCCESS = "SUCCESS",
|
||||||
ERROR = "ERROR"
|
ERROR = "ERROR",
|
||||||
|
SKIPPED = "SKIPPED"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HistoryEntry = CommonHistoryEntry & { timestamp: Date };
|
export type HistoryEntry = CommonHistoryEntry & { timestamp: Date };
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue