Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
91b3c4b
Initial plan
Copilot Feb 13, 2026
8a6be9a
Add automatic git sync before commit when local is behind remote
Copilot Feb 13, 2026
e49a947
Add tests for stash functionality and commit-behind-remote scenario
Copilot Feb 13, 2026
43a782b
Address code review feedback: improve error handling
Copilot Feb 13, 2026
e44dc41
Return stash status and improve error messages
Copilot Feb 13, 2026
42260b2
Fix stashPop logic and verify stash return value in tests
Copilot Feb 13, 2026
6e69daa
Use includes() for better readability and improve error recovery
Copilot Feb 13, 2026
0288a9e
Remove redundant null checks with optional chaining
Copilot Feb 13, 2026
733f8ef
Improve error messages with better context and guidance
Copilot Feb 13, 2026
b903ed8
Address code review: check diverged branches, improve comments, add l…
Copilot Feb 13, 2026
9b4386a
Add conflict detection and proper error message handling
Copilot Feb 13, 2026
ae75141
Fix test to use execSync, use indexOf for consistency, update comment
Copilot Feb 13, 2026
34e6b2a
Simplify hasConflicts using git diff --diff-filter=U
Copilot Feb 13, 2026
ff34a29
Replace execSync with spawn for git commands in conflict test
Copilot Feb 13, 2026
1d9aef4
Fix test and improve stash reliability and conflict detection
Copilot Feb 13, 2026
a1173ee
Extract getStashCount helper and fix empty line handling
Copilot Feb 13, 2026
5b3e0df
Improve pushBranch to handle rejections with stash and better conflic…
Copilot Feb 13, 2026
47153bf
Fix syntax error in pushBranch try-catch block
Copilot Feb 13, 2026
c022aff
Improve hasConflicts test to verify merge conflict actually occurs
Copilot Feb 13, 2026
d0236d7
Remove hasConflicts detection test case
pierzchala-m Feb 13, 2026
90372d7
Potential fix for code scanning alert no. 291: Unused variable, impor…
pierzchala-m Feb 13, 2026
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
53 changes: 53 additions & 0 deletions src/containers/job/JobManagerContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,59 @@ export class JobManagerContainer extends Container {
const userConfigService = new UserConfigService(googleFileSystem);
const userConfig = await userConfigService.load();

// Check if local branch is behind remote and sync if needed
if (userConfig.remote_branch) {
try {
await gitScanner.fetch({
privateKeyFile: await userConfigService.getDeployPrivateKeyPath()
});

const { ahead, behind } = await gitScanner.countAheadBehind(userConfig.remote_branch);
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Unused variable ahead.

Suggested change
const { ahead, behind } = await gitScanner.countAheadBehind(userConfig.remote_branch);
const { behind } = await gitScanner.countAheadBehind(userConfig.remote_branch);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Now using ahead variable to check for diverged branches. Commit: b903ed8


if (ahead > 0 && behind > 0) {
throw new Error('Local and remote branches have diverged. Please manually sync your repository before committing.');
}

if (behind > 0) {
logger.info(`Local branch is ${behind} commit(s) behind remote. Syncing before commit...`);
Comment on lines +705 to +706
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

When branches have diverged (both ahead and behind are greater than 0), the pull with rebase may fail due to conflicts or complex history. Consider checking if ahead > 0 as well, and providing a more informative error message in that case, such as "Local and remote branches have diverged. Please manually sync your repository before committing." This would help users understand why their commit failed and what action they need to take.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added check for diverged branches (ahead > 0 && behind > 0) with clear error message. Commit: 9b4386a


// Stash local changes - returns true if something was stashed
const stashed = await gitScanner.stashChanges();

try {
// Pull with rebase to integrate remote changes (uses git pull --rebase internally)
await gitScanner.pullBranch(userConfig.remote_branch, {
Comment on lines +699 to +713
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The countAheadBehind method silently catches all exceptions and returns zero values. This means if there's an issue with the git repository state or the remote branch doesn't exist after fetch, the sync logic won't execute. While this prevents the commit from failing, it could lead to the original issue reoccurring if the behind count is incorrectly reported as 0. Consider logging a warning when countAheadBehind fails so that repository state issues can be diagnosed.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added logging when countAheadBehind fails so repository state issues can be diagnosed. Commit: b903ed8

privateKeyFile: await userConfigService.getDeployPrivateKeyPath()
});

// Apply stashed changes if we stashed something
if (stashed) {
await gitScanner.stashPop();

// Check for conflicts after stash pop
if (await gitScanner.hasConflicts()) {
throw new Error('Stash pop resulted in merge conflicts. Cannot proceed with commit. ' +
'Please resolve conflicts manually using "Reset and Pull" or by running git commands directly.');
}
}
Comment on lines +718 to +726
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

When stash pop encounters merge conflicts, git will partially apply the stash and leave conflict markers in files. The current implementation doesn't detect or handle this scenario. Consider checking for conflicts after stash pop (e.g., by checking for files in conflicted state) and providing a clear error message to the user about how to resolve them, since the commit operation cannot proceed with unresolved conflicts.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added hasConflicts() method to detect unmerged files after stash pop. If conflicts detected, throws clear error message directing users to manual resolution. Commit: 9b4386a

} catch (err) {
// If pull fails, leave stash intact for manual recovery
// The user can use "Reset and Pull" to clean up or `git stash list` to view saved changes
if (stashed) {
logger.warn('Pull failed. Stashed changes remain saved for manual recovery. ' +
Comment on lines +729 to +731
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

If stash pop on line 715 fails with conflicts, the stash is consumed but changes remain partially applied with conflict markers. The error handler at line 719-721 will then try to pop the stash again, which will fail with "No stash entries found" since the stash was already consumed. Consider either: (1) setting stashed=false before the first stashPop call, or (2) not attempting to restore the stash in the error handler since a failed stash pop means the stash was already consumed.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed - stash pop conflicts now keep the stash intact. Added conflict detection with hasConflicts() method to catch this scenario and provide clear error message. Commit: 9b4386a

'Use `git stash list` to view stashed changes or "Reset and Pull" to clean up.');
}
throw err;
}
}
} catch (err) {
if (err.message.indexOf('Failed to retrieve list of SSH authentication methods') > -1) {
throw new Error('Failed to authenticate with remote repository: ' + err.message);
}
throw err;
}
}
Comment on lines +740 to +743
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The error handling pattern here uses optional chaining (err?.message), which is inconsistent with other error handling in the same file (lines 610, 613, 643, 646, 671, 674) that directly access err.message without optional chaining. While the optional chaining is safer, consider using the consistent pattern err.message.indexOf() for maintainability, unless there's a specific reason to expect err might not have a message property in this context.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Changed to use consistent error handling pattern with err.message.indexOf() to match existing code style. Commit: b903ed8


const contentFileService = await getContentFileService(transformedFileSystem, userConfigService);
const markdownTreeProcessor = new MarkdownTreeProcessor(contentFileService);
await markdownTreeProcessor.load();
Expand Down
132 changes: 109 additions & 23 deletions src/git/GitScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,38 +349,63 @@ export class GitScanner {
} catch (err) {
if (err.message.indexOf('Updates were rejected because the remote contains work') > -1 ||
err.message.indexOf('Updates were rejected because a pushed branch tip is behind its remote') > -1) {
await this.exec(`git fetch origin ${remoteBranch}`, {
env: {
GIT_SSH_COMMAND: sshParams?.privateKeyFile ? `ssh -i ${sanitize(sshParams.privateKeyFile)} -o StrictHostKeyChecking=no -o IdentitiesOnly=yes` : ''
// Stash any local changes before fetching and rebasing
const stashed = await this.stashChanges();

try {
await this.exec(`git fetch origin ${remoteBranch}`, {
env: {
GIT_SSH_COMMAND: sshParams?.privateKeyFile ? `ssh -i ${sanitize(sshParams.privateKeyFile)} -o StrictHostKeyChecking=no -o IdentitiesOnly=yes` : ''
}
});

try {
await this.exec(`git rebase origin/${remoteBranch}`, {
env: {
GIT_AUTHOR_NAME: committer.name,
GIT_AUTHOR_EMAIL: committer.email,
GIT_COMMITTER_NAME: committer.name,
GIT_COMMITTER_EMAIL: committer.email
}
});

// Restore stashed changes if any
if (stashed) {
await this.stashPop();

// Check for conflicts after restoring stash
if (await this.hasConflicts()) {
await this.exec('git rebase --abort', { ignoreError: true });
throw new Error('Stash pop resulted in merge conflicts after rebase. Cannot proceed with push. ' +
'Please resolve conflicts manually.');
}
}
} catch (err) {
await this.exec('git rebase --abort', { ignoreError: true });
if (err.message.indexOf('Resolve all conflicts manually') > -1 || err.message.indexOf('merge conflicts') > -1) {
this.logger.error('Conflict detected during rebase', { filename: __filename });
throw new Error('Rebase conflicts detected. Please resolve conflicts manually and retry.');
}
throw err;
}
});

try {
await this.exec(`git rebase origin/${remoteBranch}`, {
await this.exec(`git push origin main:${remoteBranch}`, {
env: {
GIT_AUTHOR_NAME: committer.name,
GIT_AUTHOR_EMAIL: committer.email,
GIT_COMMITTER_NAME: committer.name,
GIT_COMMITTER_EMAIL: committer.email
GIT_SSH_COMMAND: sshParams?.privateKeyFile ? `ssh -i ${sanitize(sshParams.privateKeyFile)} -o StrictHostKeyChecking=no -o IdentitiesOnly=yes` : ''
}
});
return;
} catch (err) {
await this.exec('git rebase --abort', { ignoreError: true });
if (err.message.indexOf('Resolve all conflicts manually') > -1) {
this.logger.error('Conflict', { filename: __filename });
// If we stashed something and the operation failed, warn about it
if (stashed) {
this.logger.warn('Push/rebase failed. Stashed changes remain saved. Use `git stash list` to view.', { filename: __filename });
}
throw err;
}

await this.exec(`git push origin main:${remoteBranch}`, {
env: {
GIT_SSH_COMMAND: sshParams?.privateKeyFile ? `ssh -i ${sanitize(sshParams.privateKeyFile)} -o StrictHostKeyChecking=no -o IdentitiesOnly=yes` : ''
}
});
return;
}

return;
// For other errors, just throw them
throw err;
}
}

Expand Down Expand Up @@ -902,8 +927,9 @@ export class GitScanner {
return {
ahead, behind
};
// deno-lint-ignore no-empty
} catch (ignore) {}
} catch (err) {
this.logger.warn(`Failed to count ahead/behind commits for branch ${remoteBranch}: ${err.message}`, { filename: __filename });
}

return { ahead: 0, behind: 0 };
}
Expand Down Expand Up @@ -1013,4 +1039,64 @@ export class GitScanner {
this.companionFileResolver = resolver;
}

private async getStashCount(): Promise<number> {
const result = await this.exec('git stash list --format=%gd', { skipLogger: !this.debug });
const lines = result.stdout.trim().split('\n').filter(line => line.length > 0);
return lines.length;
}

async stashChanges(): Promise<boolean> {
// Capture the number of existing stash entries before attempting to stash
const beforeCount = await this.getStashCount();

try {
await this.exec('git stash push -u -m "WikiGDrive auto-stash before sync"', { skipLogger: !this.debug });
} catch (err) {
// If there's nothing to stash, git stash may report "No local changes to save"
// in the error output. In that case, report that no stash was created.
if (err.message && err.message.includes('No local changes to save')) {
return false;
}
throw err;
}
Comment on lines +1052 to +1061
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

stashChanges() currently returns true whenever git stash push exits successfully, and only returns false when an exception is thrown containing "No local changes to save". With the current exec() implementation, a successful stash command’s stdout is available but ignored here, so this method can’t reliably distinguish between “stash created” vs “nothing to stash” if git prints that message on stdout while exiting 0 (common behavior). Consider inspecting the command result stdout (or comparing git stash list before/after) to decide whether to return true.

Suggested change
try {
await this.exec('git stash push -u -m "WikiGDrive auto-stash before sync"', { skipLogger: !this.debug });
return true;
} catch (err) {
// If there's nothing to stash, git stash will return "No local changes to save"
// Error message format: "Process exited with status: X\n" + stderr
if (err.message && err.message.includes('No local changes to save')) {
return false;
}
throw err;
}
// Capture the number of existing stash entries before attempting to stash.
const beforeResult = await this.exec('git stash list --format=%gd', { skipLogger: !this.debug });
const beforeCount = beforeResult.stdout.trim().length === 0
? 0
: beforeResult.stdout.trim().split('\n').length;
try {
await this.exec('git stash push -u -m "WikiGDrive auto-stash before sync"', { skipLogger: !this.debug });
} catch (err) {
// If there's nothing to stash, git stash may report "No local changes to save"
// in the error output. In that case, report that no stash was created.
if (err.message && err.message.includes('No local changes to save')) {
return false;
}
throw err;
}
// Re-count stash entries after the push to determine if a new stash was created.
const afterResult = await this.exec('git stash list --format=%gd', { skipLogger: !this.debug });
const afterCount = afterResult.stdout.trim().length === 0
? 0
: afterResult.stdout.trim().split('\n').length;
return afterCount > beforeCount;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Improved to check git stash list before and after to reliably determine if a stash was created. Returns true only if stash count increased. Commit: 1d9aef4


// Re-count stash entries after the push to determine if a new stash was created
const afterCount = await this.getStashCount();

return afterCount > beforeCount;
}

async stashPop(): Promise<void> {
try {
await this.exec('git stash pop', { skipLogger: !this.debug });
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
// If there's no stash to pop, handle gracefully
// Error message format: "Process exited with status: X\n" + stderr
if (message.includes('No stash entries found')) {
return;
Comment on lines +1072 to +1077
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The error message pattern matching may not work as expected. Git's stderr output is appended after "Process exited with status: X\n" in the error message (see GitScanner.exec line 115). Consider checking if the message includes the pattern rather than using indexOf directly, or verify that the exact error message from git stash pop operations matches this pattern.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed error message matching to account for "Process exited with status: X\n" + stderr format. Now using err.message.includes() which works correctly. Commit: 9b4386a

}
// Check if the error is due to merge conflicts based on the error message
if (message.includes('CONFLICT')) {
throw new Error('Stash pop encountered merge conflicts. Please resolve conflicts manually.');
}
// If the message did not explicitly mention conflicts, fall back to checking for unmerged files
if (await this.hasConflicts()) {
throw new Error('Stash pop encountered merge conflicts. Please resolve conflicts manually.');
}
Comment on lines +1074 to +1086
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

stashPop() tries to detect conflicts by checking err.message.includes('CONFLICT'), but exec() throws errors that only include stderr (not stdout). If git reports conflict details on stdout, this check won’t trigger and callers will get a raw "Process exited..." error, and higher-level logic (e.g. conflict handling) won’t run. Consider (a) including stdout in the thrown error from exec(), or (b) in this catch block, run hasConflicts() after a non-zero exit and throw the friendly conflict error based on that instead of string-matching err.message.

Suggested change
// If there's no stash to pop, handle gracefully
// Error message format: "Process exited with status: X\n" + stderr
if (err.message && err.message.includes('No stash entries found')) {
return;
}
// Check if the error is due to merge conflicts
if (err.message && err.message.includes('CONFLICT')) {
throw new Error('Stash pop encountered merge conflicts. Please resolve conflicts manually.');
}
const message = err instanceof Error ? err.message : String(err);
// If there's no stash to pop, handle gracefully
// Error message format: "Process exited with status: X\n" + stderr
if (message.includes('No stash entries found')) {
return;
}
// Check if the error is due to merge conflicts based on the error message
if (message.includes('CONFLICT')) {
throw new Error('Stash pop encountered merge conflicts. Please resolve conflicts manually.');
}
// If the message did not explicitly mention conflicts, fall back to checking for unmerged files
if (await this.hasConflicts()) {
throw new Error('Stash pop encountered merge conflicts. Please resolve conflicts manually.');
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added fallback to hasConflicts() check after catching error. If error doesn't mention CONFLICT but unmerged files exist, throws clear conflict error. Commit: 1d9aef4

throw err;
}
}

async hasConflicts(): Promise<boolean> {
try {
const result = await this.exec('git diff --name-only --diff-filter=U', { skipLogger: !this.debug });
// If any file names are returned, there are unmerged files (conflicts)
return result.stdout.trim().length > 0;
} catch (err) {
this.logger.warn('Failed to check for conflicts: ' + err.message, { filename: __filename });
return false;
}
}

}
115 changes: 114 additions & 1 deletion test/git/GitTest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs, {rmSync, unlinkSync} from 'node:fs';
import path from 'node:path';
import {execSync} from 'node:child_process';
import {execSync, spawn} from 'node:child_process';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused import spawn.

Copilot Autofix

AI 2 months ago

In general, unused imports should be removed from the import list to keep the code clean and avoid confusion. Here, only spawn is unused; execSync might be used elsewhere in the same file (we must not assume it is unused). The safest minimal fix is to stop importing spawn while keeping execSync as-is.

Specifically, in test/git/GitTest.ts on line 3, update the import from node:child_process so that it only imports execSync and omits spawn. No additional methods, imports, or definitions are required elsewhere, and this change preserves all existing behavior.

Suggested changeset 1
test/git/GitTest.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/test/git/GitTest.ts b/test/git/GitTest.ts
--- a/test/git/GitTest.ts
+++ b/test/git/GitTest.ts
@@ -1,6 +1,6 @@
 import fs, {rmSync, unlinkSync} from 'node:fs';
 import path from 'node:path';
-import {execSync, spawn} from 'node:child_process';
+import {execSync} from 'node:child_process';
 
 import winston from 'winston';
 // eslint-disable-next-line import/no-unresolved
EOF
@@ -1,6 +1,6 @@
import fs, {rmSync, unlinkSync} from 'node:fs';
import path from 'node:path';
import {execSync, spawn} from 'node:child_process';
import {execSync} from 'node:child_process';

import winston from 'winston';
// eslint-disable-next-line import/no-unresolved
Copilot is powered by AI and may make mistakes. Always verify output.

import winston from 'winston';
// eslint-disable-next-line import/no-unresolved
Expand Down Expand Up @@ -807,3 +807,116 @@
fs.rmSync(localRepoDir, { recursive: true, force: true });
}
});

Deno.test('test stash and pop', async () => {
const localRepoDir: string = createTmpDir();

try {
const scannerLocal = new GitScanner(logger, localRepoDir, COMMITER1.email);
await scannerLocal.initialize();

fs.writeFileSync(path.join(localRepoDir, 'file1.md'), 'Initial content');
await scannerLocal.commit('First commit', ['.gitignore', 'file1.md'], COMMITER1);

// Create a local change
fs.writeFileSync(path.join(localRepoDir, 'file2.md'), 'New file');

{
const changes = await scannerLocal.changes();
assertStrictEquals(changes.length, 1);
assertStrictEquals(changes[0].path, 'file2.md');
}

// Stash changes
const stashed = await scannerLocal.stashChanges();
assertStrictEquals(stashed, true);

{
const changes = await scannerLocal.changes();
assertStrictEquals(changes.length, 0);
}

// Pop stashed changes
await scannerLocal.stashPop();

{
const changes = await scannerLocal.changes();
assertStrictEquals(changes.length, 1);
assertStrictEquals(changes[0].path, 'file2.md');
}

} finally {
fs.rmSync(localRepoDir, { recursive: true, force: true });
}
});

Deno.test('test commit with local behind remote', async () => {
// This test simulates a scenario where WikiGDrive is out of sync with the remote repository:
// - localRepoDir: Represents the WikiGDrive local repository
// - githubRepoDir: Represents the remote GitHub repository (bare repo)
// - secondRepoDir: Represents another contributor who commits directly to GitHub
// The test verifies that the stash/pull/pop workflow correctly syncs local changes
// when the local repository is behind the remote.

const localRepoDir: string = createTmpDir();
const githubRepoDir: string = createTmpDir();
const secondRepoDir: string = createTmpDir();

try {
execSync(`git init -b main --bare ${githubRepoDir}`);

// Setup first repo (WikiGDrive local repository)
const scannerLocal = new GitScanner(logger, localRepoDir, COMMITER1.email);
await scannerLocal.initialize();

fs.writeFileSync(path.join(localRepoDir, 'file1.md'), 'Initial content');
await scannerLocal.commit('First commit', ['.gitignore', 'file1.md'], COMMITER1);

await scannerLocal.setRemoteUrl(githubRepoDir);
await scannerLocal.pushBranch('main');

// Setup second repo (simulates another contributor pushing to GitHub)
const scannerSecond = new GitScanner(logger, secondRepoDir, COMMITER2.email);
await scannerSecond.initialize();
fs.unlinkSync(secondRepoDir + '/.gitignore');
await scannerSecond.setRemoteUrl(githubRepoDir);
await scannerSecond.pullBranch('main');

fs.writeFileSync(path.join(secondRepoDir, 'file2.md'), 'Second repo change');
await scannerSecond.commit('Second commit', ['file2.md'], COMMITER2);
await scannerSecond.pushBranch('main');

// WikiGDrive repository now has local changes but is behind remote
fs.writeFileSync(path.join(localRepoDir, 'file3.md'), 'Local change');

// Fetch to make remote refs available
await scannerLocal.fetch();

const { ahead, behind } = await scannerLocal.countAheadBehind('main');
assertStrictEquals(ahead, 0);
assertStrictEquals(behind, 1);

// Test stash, pull, and pop workflow
const stashed = await scannerLocal.stashChanges();
assertStrictEquals(stashed, true);
await scannerLocal.pullBranch('main');
await scannerLocal.stashPop();

// Verify we now have both files
assertStrictEquals(fs.existsSync(path.join(localRepoDir, 'file2.md')), true);
assertStrictEquals(fs.existsSync(path.join(localRepoDir, 'file3.md')), true);

// Verify we're now up to date
const { ahead: newAhead, behind: newBehind } = await scannerLocal.countAheadBehind('main');
assertStrictEquals(newAhead, 0);
assertStrictEquals(newBehind, 0);

// Now we can commit successfully
await scannerLocal.commit('Third commit', ['file3.md'], COMMITER1);

} finally {
fs.rmSync(localRepoDir, { recursive: true, force: true });
fs.rmSync(githubRepoDir, { recursive: true, force: true });
fs.rmSync(secondRepoDir, { recursive: true, force: true });
Comment thread
ggodlewski marked this conversation as resolved.
}
});
Loading