Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{
"name": "logseq-plugin-git",
"version": "1.7.0",
"name": "logseq-git-with-sync",
"version": "1.8.0",
"main": "dist/index.html",
"logseq": {
"id": "logseq-git",
"id": "logseq-git-with-sync",
"title": "Git with Sync",
"icon": "logo.png"
},
"scripts": {
Expand Down
158 changes: 135 additions & 23 deletions src/helper/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,43 +109,90 @@ export const BUTTONS = [
{ key: "commit", title: "Commit", event: "commit" },
{ key: "push", title: "Push", event: "push" },
{ key: "commitAndPush", title: "Commit & Push", event: "commitAndPush" },
{ key: "initRepo", title: "Initialize Repository", event: "initRepo" },
];

export const SETTINGS_SCHEMA: SettingSchemaDesc[] = [
{
key: "buttons",
title: "Buttons",
type: "enum",
default: ["Check Status", "Show Log", "Pull Rebase", "Commit & Push"],
description: "Select buttons to show",
enumPicker: "checkbox",
enumChoices: BUTTONS.map(({ title }) => title),
key: "repoInitHeader",
title: "Repository Initialization",
type: "heading",
default: "",
description: "",
},
{
key: "checkWhenDBChanged",
title: "Check Status when DB Changed",
type: "boolean",
default: true,
description: "Check status when DB changed, restart logseq to take effect",
key: "gitHostDomain",
title: "Git Host Domain",
type: "string",
default: "github.com",
description: "Git hosting domain (e.g., github.com, gitlab.com, codeberg.org)",
},
{
key: "autoCheckSynced",
title: "Auto Check If Synced",
type: "boolean",
default: false,
description:
"Automatically check if the local version is the same as the remote",
key: "gitUsername",
title: "Git Username/Email",
type: "string",
default: "",
description: "Your git username or email for authentication",
},
{
key: "autoPush",
title: "Auto Push",
key: "gitPassword",
title: "Password/Token",
type: "string",
default: "",
description: "Your git password or personal access token (stored locally)",
},
{
key: "gitRepoOwner",
title: "Repository Owner",
type: "string",
default: "",
description: "Repository owner username (appears in repository URL path)",
},
{
key: "gitRepoName",
title: "Repository Name",
type: "string",
default: "",
description: "Name of the repository (e.g., logseq-notes, my-vault)",
},
{
key: "gitBranch",
title: "Default Branch",
type: "string",
default: "main",
description: "Default branch name (typically main or master)",
},
{
key: "gitUserEmail",
title: "Git Commit Email",
type: "string",
default: "",
description: "Email address to use for git commits",
},
{
key: "gitUserName",
title: "Git Commit Name",
type: "string",
default: "",
description: "Display name to use for git commits",
},
{
key: "testCredentialsButton",
title: "Test Credentials",
type: "boolean",
default: false,
description: "Auto push when logseq hide",
description: "Click to test if credentials can connect to the remote repository",
},
{
key: "commitSettingsHeader",
title: "Commit Settings",
type: "heading",
default: "",
description: "",
},
{
key: "typeCommitMessage",
title: "Type Commit Message",
title: "Commit Message Type",
type: "enum",
default: "Default Message With Date",
description: "Type of commit message to use",
Expand All @@ -157,6 +204,71 @@ export const SETTINGS_SCHEMA: SettingSchemaDesc[] = [
title: "Custom Commit Message",
type: "string",
default: "",
description: "Custom commit message for plugin (valid only if commit message is set to Custom Message)",
description: "Custom commit message (only used if Commit Message Type is set to Custom)",
},
{
key: "automationHeader",
title: "Automation & Behavior",
type: "heading",
default: "",
description: "",
},
{
key: "checkWhenDBChanged",
title: "Check Status on DB Changes",
type: "boolean",
default: true,
description: "Check git status when database changes (restart logseq to take effect)",
},
{
key: "autoCheckSynced",
title: "Auto Check If Synced",
type: "boolean",
default: false,
description: "Automatically check if local version matches remote",
},
{
key: "autoPush",
title: "Auto Push on Hide",
type: "boolean",
default: false,
description: "Automatically push changes when Logseq is hidden/minimized",
},
{
key: "autoPullBeforePush",
title: "Auto Pull Before Push",
type: "boolean",
default: true,
description: "Automatically pull-rebase before committing and pushing to prevent conflicts",
},
{
key: "checkRemotePeriodically",
title: "Check Remote for Changes",
type: "boolean",
default: false,
description: "Check remote repository every 5 minutes for new changes",
},
{
key: "autoSyncOnRemoteChanges",
title: "Auto Pull on Remote Changes",
type: "boolean",
default: false,
description: "Automatically pull when remote changes are detected (requires Check Remote for Changes)",
},
{
key: "uiHeader",
title: "UI Settings",
type: "heading",
default: "",
description: "",
},
{
key: "buttons",
title: "Toolbar Buttons",
type: "enum",
default: ["Check Status", "Show Log", "Pull Rebase", "Commit & Push"],
description: "Select which buttons to show in the toolbar dropdown",
enumPicker: "checkbox",
enumChoices: BUTTONS.map(({ title }) => title),
}
];
158 changes: 157 additions & 1 deletion src/helper/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export const push = async (showRes = true): Promise<IGitResult> => {
* @returns The commit message.
*/
export const commitMessage = () : string => {

let defaultMessage = "[logseq-plugin-git:commit]";

switch (logseq.settings?.typeCommitMessage as string) {
Expand All @@ -173,3 +173,159 @@ export const commitMessage = () : string => {
return defaultMessage;
}
}

/**
* Test git credentials by attempting to ls-remote
* @returns Promise<IGitResult>
*/
export const testCredentials = async (showRes = true): Promise<IGitResult> => {
const domain = logseq.settings?.gitHostDomain as string;
const username = logseq.settings?.gitUsername as string;
const password = logseq.settings?.gitPassword as string;
const repoOwner = logseq.settings?.gitRepoOwner as string;
const repoName = logseq.settings?.gitRepoName as string;

// Validate required fields
if (!domain || !username || !password || !repoOwner || !repoName) {
const errorMsg = "Missing required fields. Please fill in Git Host Domain, Username, Password/Token, Repository Owner, and Repository Name.";
if (showRes) logseq.UI.showMsg(errorMsg, 'error');
return { exitCode: 1, stdout: '', stderr: errorMsg };
}

try {
// URL encode username and password to handle special characters like @, :, etc.
const encodedUsername = encodeURIComponent(username);
const encodedPassword = encodeURIComponent(password);
const remoteUrl = `https://${encodedUsername}:${encodedPassword}@${domain}/${repoOwner}/${repoName}.git`;

// Test credentials with ls-remote
const res = await execGitCommand(['ls-remote', remoteUrl, 'HEAD']);

if (res.exitCode === 0) {
if (showRes) logseq.UI.showMsg('✓ Credentials valid! Successfully connected to remote repository.', 'success');
console.log('[logseq-plugin-git] === Credentials test successful');
return { exitCode: 0, stdout: 'Credentials valid', stderr: '' };
} else {
if (showRes) logseq.UI.showMsg(`✗ Credential test failed: ${res.stderr || 'Repository not found or invalid credentials'}`, 'error');
return res;
}
} catch (error) {
const errorMsg = `Credential test failed: ${error}`;
if (showRes) logseq.UI.showMsg(errorMsg, 'error');
return { exitCode: 1, stdout: '', stderr: errorMsg };
}
}

/**
* Check if remote repository has new changes (is ahead of local)
* @returns Promise<boolean> - true if remote has changes
*/
export const checkRemoteChanges = async (): Promise<boolean> => {
try {
// Fetch from remote without merging
await execGitCommand(['fetch']);

// Check if remote is ahead
const res = await execGitCommand(['rev-list', 'HEAD..@{u}', '--count']);

if (res.exitCode === 0) {
const count = parseInt(res.stdout.trim());
return count > 0;
}
return false;
} catch (error) {
console.log('[logseq-plugin-git] === Error checking remote changes', error);
return false;
}
}

/**
* Initialize a git repository with HTTPS remote
* @returns Promise<IGitResult>
*/
export const initRepo = async (showRes = true): Promise<IGitResult> => {
const domain = logseq.settings?.gitHostDomain as string;
const username = logseq.settings?.gitUsername as string;
const password = logseq.settings?.gitPassword as string;
const repoOwner = logseq.settings?.gitRepoOwner as string;
const repoName = logseq.settings?.gitRepoName as string;
const branch = (logseq.settings?.gitBranch as string) || "main";
const userEmail = logseq.settings?.gitUserEmail as string;
const userName = logseq.settings?.gitUserName as string;

// Validate required fields
if (!domain || !username || !password || !repoOwner || !repoName) {
const errorMsg = "Missing required fields. Please fill in Git Host Domain, Username, Password/Token, Repository Owner, and Repository Name in settings.";
if (showRes) logseq.UI.showMsg(errorMsg, 'error');
return { exitCode: 1, stdout: '', stderr: errorMsg };
}

if (!userEmail || !userName) {
const errorMsg = "Missing git user configuration. Please fill in Git User Email and Git User Name in settings.";
if (showRes) logseq.UI.showMsg(errorMsg, 'error');
return { exitCode: 1, stdout: '', stderr: errorMsg };
}

try {
// Check if already initialized
const statusCheck = await execGitCommand(['status']);
if (statusCheck.exitCode === 0) {
const msg = "Repository already initialized.";
if (showRes) logseq.UI.showMsg(msg, 'warning');
return { exitCode: 1, stdout: '', stderr: msg };
}

// Initialize git repository
let res = await execGitCommand(['init']);
if (res.exitCode !== 0) {
if (showRes) logseq.UI.showMsg(`Git init failed: ${res.stderr}`, 'error');
return res;
}

// Configure user
await execGitCommand(['config', 'user.email', userEmail]);
await execGitCommand(['config', 'user.name', userName]);

// Create HTTPS remote URL with encoded credentials
const encodedUsername = encodeURIComponent(username);
const encodedPassword = encodeURIComponent(password);
const remoteUrl = `https://${encodedUsername}:${encodedPassword}@${domain}/${repoOwner}/${repoName}.git`;

// Add remote origin
res = await execGitCommand(['remote', 'add', 'origin', remoteUrl]);
if (res.exitCode !== 0) {
if (showRes) logseq.UI.showMsg(`Failed to add remote: ${res.stderr}`, 'error');
return res;
}

// Set default branch
res = await execGitCommand(['branch', '-M', branch]);
if (res.exitCode !== 0) {
if (showRes) logseq.UI.showMsg(`Failed to set branch: ${res.stderr}`, 'error');
return res;
}

// Create initial commit
await execGitCommand(['add', '.']);
res = await execGitCommand(['commit', '-m', 'Initial commit from Logseq']);
if (res.exitCode !== 0) {
if (showRes) logseq.UI.showMsg(`Failed to create initial commit: ${res.stderr}`, 'error');
return res;
}

// Set upstream and push
res = await execGitCommand(['push', '-u', 'origin', branch]);
if (res.exitCode !== 0) {
if (showRes) logseq.UI.showMsg(`Repository initialized locally, but push failed: ${res.stderr}. You may need to create the repository on ${domain} first.`, 'warning');
return res;
}

if (showRes) logseq.UI.showMsg('Repository initialized successfully!', 'success');
console.log('[logseq-plugin-git] === Repository initialized');
return { exitCode: 0, stdout: 'Repository initialized successfully', stderr: '' };
} catch (error) {
const errorMsg = `Initialization failed: ${error}`;
if (showRes) logseq.UI.showMsg(errorMsg, 'error');
return { exitCode: 1, stdout: '', stderr: errorMsg };
}
}
Loading