Skip to content

feat: Replaced GitHub/Octokit with generic Isomorphic Git#99

Merged
saberzero1 merged 12 commits intomainfrom
generic-git-provider
Jan 8, 2026
Merged

feat: Replaced GitHub/Octokit with generic Isomorphic Git#99
saberzero1 merged 12 commits intomainfrom
generic-git-provider

Conversation

@saberzero1
Copy link
Owner

@saberzero1 saberzero1 commented Jan 8, 2026

This pull request introduces a major refactor to replace the previous GitHub/Octokit-specific implementation with a generic, provider-agnostic Git integration using isomorphic-git. The changes enable support for multiple Git hosting providers (GitHub, GitLab, Bitbucket, Gitea, Codeberg, and self-hosted), improve performance, and remove dependency on GitHub APIs and their rate limits. The settings system is overhauled to support generic Git configuration, with automatic migration from legacy GitHub-specific settings. UI and code references are updated to reflect the new generic Git model.

Core sync engine and provider support:

  • Replaced all Octokit/GitHub-specific logic with a new isomorphic-git-based implementation, removing direct dependency on Octokit and enabling support for GitLab, Bitbucket, Gitea, Codeberg, and self-hosted Git providers. [1] [2]
  • Updated the changelog to document these breaking and feature changes in version 1.9.0.

Settings model and migration:

  • Introduced a new generic git settings object in QuartzSyncerSettings, supporting provider hinting, multiple authentication types, and generic remote configuration. Legacy GitHub fields are deprecated but retained for migration. [1] [2]
  • Added a migration method to automatically convert legacy GitHub settings to the new generic Git format on upgrade, ensuring backward compatibility for existing users. [1] [2]
  • Updated all internal references and instantiations to use the new git settings object instead of GitHub-specific fields. [1] [2] [3]

UI/UX and settings view updates:

  • Replaced all "GitHub" references in the settings UI with "Git", and updated tab logic to use the new generic terminology and tab keys. [1] [2] [3] [4] [5]
  • Updated the default active settings tab and migration logic to use the new "git" tab instead of "github".

Repository connection and content management:

  • Updated RepositoryConnection and QuartzSyncerSiteManager to use the new generic gitSettings object, and refactored content tree and hash logic to use provider-agnostic fields (oid instead of sha, generic types). [1] [2] [3] [4]

Build and dependency updates:

  • Removed Octokit dependency and added new dependencies: isomorphic-git, @isomorphic-git/lightning-fs, and buffer-es6. Updated the esbuild configuration to inject a buffer shim and alias the buffer module for browser compatibility. [1] [2] [3]

These changes collectively modernize the sync engine, making it more flexible, performant, and provider-agnostic, while maintaining a smooth upgrade path for existing users.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request represents a major architectural refactor that replaces the GitHub/Octokit-specific implementation with a provider-agnostic Git integration using isomorphic-git. The changes enable support for multiple Git hosting providers (GitHub, GitLab, Bitbucket, Gitea, Codeberg, and self-hosted instances), remove dependency on GitHub API rate limits, and improve performance through direct Git operations.

Key changes:

  • Complete replacement of Octokit with isomorphic-git for Git operations
  • New generic settings model with automatic migration from legacy GitHub settings
  • Updated UI to reflect provider-agnostic terminology
  • Addition of new dependencies and build configuration for browser compatibility

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 19 comments.

Show a summary per file
File Description
src/views/SettingsView/Views/GithubSettings.ts Removed (replaced by GitSettings.ts)
src/views/SettingsView/Views/GitSettings.ts New generic Git settings UI with provider detection and branch selection
src/views/SettingsView/SettingView.ts Updated to use GitSettings instead of GithubSettings
src/repositoryConnection/RepositoryConnection.ts Complete rewrite using isomorphic-git with LightningFS for local caching
src/repositoryConnection/QuartzSyncerSiteManager.ts Updated to use new Git settings structure
src/publisher/Publisher.ts Updated to use new Git settings object
src/models/settings.ts Added generic Git settings types with legacy field deprecation
src/models/SyncerTab.ts Updated imports to use GitSettings
package.json Removed @octokit/core, added isomorphic-git and related dependencies
package-lock.json Updated dependency tree for new packages
main.ts Added settings migration logic and updated defaults
esbuild.config.mjs Added buffer shim injection and alias for browser compatibility
esbuild-buffer-shim.js New shim file for Buffer polyfill
docs/Changelog.md Documented v1.9.0 changes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +697 to +709
async clearLocalCache(): Promise<void> {
const fsName = this.getFsName();

return {
path: normalizePath(file.getPath()),
mode: "100644",
type: "blob",
sha: blob.data.sha,
};
} catch (error) {
logger.error(error);
try {
if (typeof indexedDB !== "undefined") {
indexedDB.deleteDatabase(fsName);
}
});
this.fs = null;
this.initialized = false;
logger.info("Local git cache cleared");
} catch (error) {
logger.error("Failed to clear local cache", error);
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clearLocalCache method uses typeof indexedDB !== "undefined" to check for browser environment, but doesn't handle the case where the deletion might fail or be denied. Consider adding error handling and user feedback for cache clearing operations.

Copilot uses AI. Check for mistakes.
this.branchesLoaded = true;

if (!gitSettings.branch) {
gitSettings.branch = defaultBranch || "v4";
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default branch fallback is hardcoded to "v4" which may not be appropriate for all Git providers. Consider using "main" or "master" as more universal defaults, or making this configurable based on the provider hint.

Suggested change
gitSettings.branch = defaultBranch || "v4";
const fallbackBranch =
defaultBranch ||
branches.find((b) => b === "main") ||
branches.find((b) => b === "master") ||
branches[0] ||
"main";
gitSettings.branch = fallbackBranch;

Copilot uses AI. Check for mistakes.
this.settings.settings.git.auth.type =
value as GitAuthType;
await this.checkConnectionAndSaveSettings();
this.display();
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the authentication type changes, the display is refreshed by calling this.display() which will reset the entire settings view. This could be disruptive to the user if they've scrolled or are in the middle of configuring other settings. Consider a more targeted refresh that only updates the relevant input fields.

Suggested change
this.display();

Copilot uses AI. Check for mistakes.
Comment on lines +501 to +505
let previous;

do {
previous = path;
path = path.replace(/\.\.\//g, "");
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normalization function contains a potentially infinite loop if the path contains patterns that don't match the simple "../" replacement. The do-while loop should have a safety counter to prevent hanging if an unexpected path format is encountered.

Suggested change
let previous;
do {
previous = path;
path = path.replace(/\.\.\//g, "");
let previous;
const maxIterations = 1000;
let iterations = 0;
do {
previous = path;
path = path.replace(/\.\.\//g, "");
iterations++;
if (iterations >= maxIterations) {
logger.warn(
"normalizeFilePath: exceeded max normalization iterations for path",
path,
);
break;
}

Copilot uses AI. Check for mistakes.
Comment on lines +591 to +597
const normalizeFilePath = (path: string): string => {
let previous;

const repoDataPromise = this.octokit.request(
"GET /repos/{owner}/{repo}",
{
...this.getBasePayload(),
},
);
do {
previous = path;
path = path.replace(/\.\.\//g, "");
} while (path !== previous);
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same normalization pattern with a potential infinite loop is duplicated in the updateFiles method. This code duplication should be extracted into a shared utility method.

Copilot uses AI. Check for mistakes.
Comment on lines +606 to +621
const ensureDirectory = async (filePath: string): Promise<void> => {
const parts = filePath.split("/");
parts.pop();
let currentPath = this.dir;

//eslint-disable-next-line
const tree = newTreeEntries.filter((x: any) => x !== undefined) as {
path?: string | undefined;
mode?:
| "100644"
| "100755"
| "040000"
| "160000"
| "120000"
| undefined;
type?: "tree" | "blob" | "commit" | undefined;
sha?: string | null | undefined;
content?: string | undefined;
}[];

const newTree = await this.octokit.request(
"POST /repos/{owner}/{repo}/git/trees",
{
...this.getBasePayload(),
base_tree: baseTreeSha,
tree,
},
);
for (const part of parts) {
if (!part) continue;
currentPath = `${currentPath}/${part}`;

const commitMessage = "Deleted multiple files";
try {
await this.getFs().promises.mkdir(currentPath);
} catch {
logger.debug(`Directory ${currentPath} already exists`);
}
}
};
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ensureDirectory function is duplicated in both deleteFiles and updateFiles methods. This should be extracted to a class-level private method to reduce code duplication and improve maintainability.

Copilot uses AI. Check for mistakes.
Comment on lines +482 to +548
await git.checkout({
...this.getGitConfig(),
ref: remoteCommit,
force: true,
});

await git.branch({
...this.getGitConfig(),
ref: this.branch,
object: remoteCommit,
force: true,
});

await git.checkout({
...this.getGitConfig(),
ref: this.branch,
});

const normalizeFilePath = (path: string): string => {
let previous;

do {
previous = path;
path = path.replace(/\.\.\//g, "");
} while (path !== previous);

path = this.getVaultPath(path);

return path.startsWith("/")
? `${this.contentFolder}${path}`
: `${this.contentFolder}/${path}`;
};

for (const filePath of filePaths) {
const normalizedPath = normalizeFilePath(filePath);
const fullPath = `${this.dir}/${normalizedPath}`;

try {
await this.getFs().promises.unlink(fullPath);

await git.remove({
...this.getGitConfig(),
filepath: normalizedPath,
});
} catch (error) {
logger.warn(
`Could not delete file ${normalizedPath}`,
error,
);
}
}

await git.commit({
...this.getGitConfig(),
message: "Deleted multiple files",
author: {
name: "Quartz Syncer",
email: "quartz-syncer@obsidian.md",
},
});

await git.push({
...this.getGitConfig(),
url: this.remoteUrl,
remote: "origin",
ref: this.branch,
});
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The force push operation (deleteFiles and updateFiles methods) checks out to remote commits and force-pushes changes. This could cause data loss if multiple users are working on the same repository or if there are local changes. Consider adding safeguards or warnings about this destructive operation.

Copilot uses AI. Check for mistakes.
} catch (error) {
logger.error(`Could not get file ${path}`, error);
throw new Error(
`Could not get file ${path} from repository ${this.getRepositoryName()}`,
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same error handling pattern occurs here - original error information is discarded. Consider preserving the error context for better debugging.

Suggested change
`Could not get file ${path} from repository ${this.getRepositoryName()}`,
`Could not get file ${path} from repository ${this.getRepositoryName()}`,
{ cause: error },

Copilot uses AI. Check for mistakes.
Comment on lines 32 to +37
this.baseSyncerConnection = new RepositoryConnection({
githubToken: settings.githubToken,
githubUserName: "saberzero1",
quartzRepository: "quartz",
gitSettings: {
remoteUrl: "https://github.com/saberzero1/quartz.git",
branch: "main",
auth: { type: "none" },
},
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The base syncer connection is hardcoded to use "https://github.com/saberzero1/quartz.git" with no authentication. If this repository is or becomes private, the connection will fail. Consider documenting why this specific repository is used or making it configurable.

Copilot uses AI. Check for mistakes.
git: {
remoteUrl: "",
branch: "main",
corsProxyUrl: "",
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CORS proxy URL defaults to an empty string, which will be truthy but may cause issues if passed to the Git configuration. Consider using undefined as the default instead, or explicitly check for empty strings before using the value.

Suggested change
corsProxyUrl: "",
corsProxyUrl: undefined,

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 45 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +123 to +124
if (!gitSettings.branch) {
gitSettings.branch = defaultBranch || "v4";
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default branch fallback uses "v4" when no branch is provided, but the DEFAULT_SETTINGS in main.ts sets the default branch to "main". This inconsistency could cause confusion. Consider using a consistent default value across the codebase.

Copilot uses AI. Check for mistakes.
main.ts Outdated
remoteUrl: githubUserName
? `https://github.com/${githubUserName}/${githubRepo}.git`
: "",
branch: "main",
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The migration logic in main.ts sets the default branch to "main", but elsewhere in the code (GitSettings.ts line 124, 260, 276-277, 294), "v4" is used as a fallback. This inconsistency could lead to unexpected behavior where users migrating from GitHub settings get a "main" branch, but fresh installs or certain code paths default to "v4".

Copilot uses AI. Check for mistakes.

display() {
this.settingsRootElement.empty();
this.settingsRootElement.addClass("quartz-syncer-github-settings");
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class name "quartz-syncer-github-settings" is still used even though this is now the Git settings view, not GitHub-specific. This should be updated to reflect the generic nature of the settings.

Copilot uses AI. Check for mistakes.
}

if (this.connectionStatus === "connected") {
this.connectionStatusElement.innerText = "successful!";
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spelling error "succesful" should be corrected to "successful".

Copilot uses AI. Check for mistakes.

display(): void {
this.settingsRootElement.empty();
this.settingsRootElement.addClass("quartz-syncer-github-settings");
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class name "quartz-syncer-github-settings" in UISettings doesn't make semantic sense since this is the UI settings view. Consider using a more appropriate class name like "quartz-syncer-ui-settings".

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 61 out of 62 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@saberzero1 saberzero1 merged commit 6bd61b3 into main Jan 8, 2026
6 checks passed
@saberzero1 saberzero1 deleted the generic-git-provider branch January 8, 2026 21:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants