chore: code-syncer-update - #357
Conversation
…nagement - Updated user directory paths to use HOME variable for portability. - Introduced blocklists for proprietary and AI extensions to prevent sync errors. - Added helper functions for command resolution and directory management. - Improved extension installation process with error handling and logging. - Implemented a watcher for automatic syncing of configuration files on changes. - Refactored sync logic for clarity and maintainability.
- Standardized indentation and spacing for better readability. - Enhanced logging for extension syncing and configuration file copying. - Minor adjustments to ensure consistent command structure and error handling. - Cleaned up comments for clarity and maintainability.
…ore syncing - Implemented a new function to identify and uninstall unnecessary extensions from the target editor. - Enhanced the extension syncing process by ensuring only required extensions are retained. - Improved logging to provide feedback on the removal of extensions.
- Changed fallback paths for "windsurf" and "cursor" commands to use the Homebrew installation directory. - Ensured compatibility with Homebrew-managed applications on macOS.
…ommand resolution - Added "anysphere.pyright" and "anysphere.cursorpyright" to the proprietary extensions list to prevent sync errors. - Enhanced the command resolution function to return the resolved path of commands, improving compatibility with applications not in the PATH.
- Added checks to ensure the source file exists and is not empty before proceeding with extension removal. - Improved logging to notify when there are no extensions to sync or when the source list is missing. - Refined the logic to determine which extensions to keep or remove, ensuring only necessary extensions are retained during the sync process.
- Enhanced the clean_extension_list function to accept input from stdin or a file, improving flexibility. - Updated remove_unnecessary_extensions and install_extensions functions to directly retrieve VS Code extensions via CLI, ensuring accurate syncing. - Added checks for VS Code CLI availability and extension presence, with appropriate logging for better user feedback. - Refined the logic for filtering and syncing extensions, ensuring only necessary extensions are retained.
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughSummary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings. WalkthroughRefactors sync.sh to use HOME-based paths, adds extension categorization (PROPRIETARY_EXTENSIONS, AI_EXTENSIONS), introduces CLI resolution and directory helpers, implements filtering/removal and staged installation of extensions, adds config file sync, and adds optional fswatch-based auto-sync. Changes
Sequence Diagram(s)sequenceDiagram
participant Main as sync.sh (Main)
participant VSCode as VS Code CLI
participant Cleaner as Cleaner (filter/remove)
participant Installer as Installer (install)
participant Editors as Target Editors
participant Config as Config Sync
participant Watcher as fswatch
Main->>VSCode: resolve_cli & verify
VSCode-->>Main: CLI present / absent
Main->>VSCode: count extensions
VSCode-->>Main: extension list
Main->>Cleaner: clean_extension_list (remove AI/proprietary)
Cleaner-->>Main: filtered list
Main->>Cleaner: remove_unnecessary_extensions
Cleaner->>Editors: uninstall blocked extensions
Editors-->>Cleaner: uninstall results
Main->>Installer: install_extensions (per-editor)
Installer->>Editors: install each extension
Editors-->>Installer: install success/fail
Main->>Config: sync_config_file -> copy settings/keybindings
Config-->>Editors: files copied (logged)
Main->>Watcher: check fswatch
alt fswatch available
Watcher->>Main: enable auto-sync
else
Watcher-->>Main: auto-sync disabled (notice)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @shunkakinoki, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly overhauls the Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Performed full review of 46f486e...cced5b9
Tip
Help
Slash Commands:
/review- Request a full code review/review latest- Review only changes since the last review/describe- Generate PR description. This will update the PR body or issue comment depending on your configuration/help- Get help with Mesa commands and configuration options
1 files reviewed | 0 comments | Edit Agent Settings • Read Docs
Mesa DescriptionTL;DRRevamped the What changed?
Description generated by Mesa. Update settings |
There was a problem hiding this comment.
Code Review
This pull request significantly refactors the code-syncer script, making it more robust and feature-rich. The changes include fetching extension lists directly via CLI, adding blocklists for certain extensions, and improving logging. My review focuses on improving efficiency, robustness, and adherence to shell scripting best practices. I've identified several areas for improvement: optimizing the extension filtering logic, making temporary file handling more secure, centralizing command validation, and fixing a minor output bug. Overall, these are great enhancements to the script's functionality.
| clean_extension_list() { | ||
| local input_source="$1" # Can be a file path or "-" for stdin | ||
| local clean_file="$2" | ||
|
|
||
| if [ -f "$source_path" ]; then | ||
| cp "$source_path" "$ANTIGRAVITY_USER_DIR/$file" | ||
| cp "$source_path" "$CURSOR_USER_DIR/$file" | ||
| cp "$source_path" "$WINDSURF_USER_DIR/$file" | ||
| echo "$file synced at $(date)" | ||
| # Read from stdin if input_source is "-", otherwise from file | ||
| if [ "$input_source" = "-" ]; then | ||
| cat >"$clean_file" | ||
| else | ||
| echo "VSCode $file not found" | ||
| cp "$input_source" "$clean_file" | ||
| fi | ||
|
|
||
| for bad_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do | ||
| # Remove the line containing the bad extension | ||
| sed -i '' "/$bad_ext/d" "$clean_file" | ||
| done | ||
|
|
||
| for ai_ext in "${AI_EXTENSIONS[@]}"; do | ||
| # Remove the line containing the AI extension | ||
| sed -i '' "/$ai_ext/d" "$clean_file" | ||
| done | ||
| } |
There was a problem hiding this comment.
The current implementation of clean_extension_list uses sed in a loop for each blocklisted extension. This is inefficient and can be buggy if extension names contain special regex characters (like '.'). A more efficient and safer approach is to use grep -v -F -f which filters using a list of fixed strings.
| clean_extension_list() { | |
| local input_source="$1" # Can be a file path or "-" for stdin | |
| local clean_file="$2" | |
| if [ -f "$source_path" ]; then | |
| cp "$source_path" "$ANTIGRAVITY_USER_DIR/$file" | |
| cp "$source_path" "$CURSOR_USER_DIR/$file" | |
| cp "$source_path" "$WINDSURF_USER_DIR/$file" | |
| echo "$file synced at $(date)" | |
| # Read from stdin if input_source is "-", otherwise from file | |
| if [ "$input_source" = "-" ]; then | |
| cat >"$clean_file" | |
| else | |
| echo "VSCode $file not found" | |
| cp "$input_source" "$clean_file" | |
| fi | |
| for bad_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do | |
| # Remove the line containing the bad extension | |
| sed -i '' "/$bad_ext/d" "$clean_file" | |
| done | |
| for ai_ext in "${AI_EXTENSIONS[@]}"; do | |
| # Remove the line containing the AI extension | |
| sed -i '' "/$ai_ext/d" "$clean_file" | |
| done | |
| } | |
| clean_extension_list() { | |
| local input_source="$1" # Can be a file path or "-" for stdin | |
| local clean_file="$2" | |
| local temp_blocklist | |
| temp_blocklist=$(mktemp) | |
| # Ensure temp file is removed on exit | |
| trap 'rm -f "$temp_blocklist"' RETURN | |
| # Combine both blocklists into a single file for grep | |
| printf '%s\n' "${PROPRIETARY_EXTENSIONS[@]}" "${AI_EXTENSIONS[@]}" >"$temp_blocklist" | |
| # Read from stdin if input_source is "-", otherwise from file | |
| if [ "$input_source" = "-" ]; then | |
| # Use grep to filter stdin and write to clean_file | |
| grep -v -F -x -f "$temp_blocklist" >"$clean_file" | |
| else | |
| # Use grep to filter the input file and write to clean_file | |
| grep -v -F -x -f "$temp_blocklist" "$input_source" >"$clean_file" | |
| fi | |
| } |
| resolve_cli() { | ||
| local cmd=$1 | ||
| local resolved_path=$(command -v "$cmd" 2>/dev/null) | ||
| if [ -n "$resolved_path" ]; then | ||
| echo "$resolved_path" | ||
| else | ||
| echo "Extensions list not found" | ||
| # Fallback paths for Mac apps if not in PATH | ||
| case $cmd in | ||
| "antigravity") echo "/Applications/Antigravity.app/Contents/Resources/app/bin/antigravity" ;; | ||
| "windsurf") echo "/opt/homebrew/bin/windsurf" ;; | ||
| "cursor") echo "/opt/homebrew/bin/cursor" ;; | ||
| *) echo "" ;; | ||
| esac | ||
| fi | ||
| } |
There was a problem hiding this comment.
The resolve_cli function returns a path without verifying if it's valid or executable. This check is then performed at the call sites (lines 111, 218) using [ ! -f ... ], which is not fully robust (e.g., for symlinks) and duplicates logic. It would be better to centralize the validation within resolve_cli and check for executability (-x). This simplifies the calling code to just if [ -z "$cli_cmd" ]; then.
resolve_cli() {
local cmd=$1
local resolved_path
resolved_path=$(command -v "$cmd" 2>/dev/null)
if [ -n "$resolved_path" ]; then
echo "$resolved_path"
return 0
fi
# Fallback paths for Mac apps if not in PATH
case $cmd in
"antigravity") resolved_path="/Applications/Antigravity.app/Contents/Resources/app/bin/antigravity" ;;
"windsurf") resolved_path="/opt/homebrew/bin/windsurf" ;;
"cursor") resolved_path="/opt/homebrew/bin/cursor" ;;
*)
echo ""
return 1
;;
esac
if [ -x "$resolved_path" ]; then
echo "$resolved_path"
else
echo ""
fi
}| local clean_list="/tmp/${target_name}_extensions_clean.list" | ||
| local installed_list="/tmp/${target_name}_extensions_installed.list" | ||
| local vscode_list="/tmp/vscode_extensions.list" |
There was a problem hiding this comment.
This function, and install_extensions, use hardcoded temporary file paths in /tmp. This can lead to race conditions and is generally insecure. It also leads to redundant work, as vscode_list is generated in both functions. Consider using mktemp to create temporary files in install_extensions and pass their paths to this function. This would make the script more robust and efficient. You should also add a trap to ensure temporary files are cleaned up on exit.
| local to_remove=() | ||
| while IFS= read -r installed_ext; do | ||
| if [ -z "$installed_ext" ]; then | ||
| continue | ||
| fi | ||
|
|
||
| # Skip PROPRIETARY_EXTENSIONS - don't attempt to remove them | ||
| local is_proprietary=false | ||
| for prop_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do | ||
| if [ "$installed_ext" = "$prop_ext" ]; then | ||
| is_proprietary=true | ||
| break | ||
| fi | ||
| done | ||
|
|
||
| if [ "$is_proprietary" = true ]; then | ||
| continue | ||
| fi | ||
|
|
||
| # Check if extension is in VS Code's original list (before filtering) | ||
| # If it's in VS Code, we should keep it (it will be synced) | ||
| local in_vscode=false | ||
| if grep -Fxq "$installed_ext" "$vscode_list" 2>/dev/null; then | ||
| in_vscode=true | ||
| fi | ||
|
|
||
| # If extension is in VS Code, keep it (don't remove) | ||
| if [ "$in_vscode" = true ]; then | ||
| continue | ||
| fi | ||
|
|
||
| # Check if extension is in AI_EXTENSIONS (should be removed) | ||
| local is_ai=false | ||
| for ai_ext in "${AI_EXTENSIONS[@]}"; do | ||
| if [ "$installed_ext" = "$ai_ext" ]; then | ||
| is_ai=true | ||
| break | ||
| fi | ||
| done | ||
|
|
||
| # Remove if it's an AI extension (not in VS Code but installed) | ||
| if [ "$is_ai" = true ]; then | ||
| to_remove+=("$installed_ext") | ||
| fi | ||
| done <"$installed_list" |
There was a problem hiding this comment.
The nested loops to check if an extension is proprietary or an AI extension are inefficient (O(N*M)). For better performance and readability, you can convert the blocklist arrays into associative arrays (requires bash 4+) for near O(1) lookups. This simplifies the logic inside the while loop considerably.
local to_remove=()
declare -A proprietary_extensions_set
for ext in "${PROPRIETARY_EXTENSIONS[@]}"; do proprietary_extensions_set["$ext"]=1; done
declare -A ai_extensions_set
for ext in "${AI_EXTENSIONS[@]}"; do ai_extensions_set["$ext"]=1; done
while IFS= read -r installed_ext; do
if [ -z "$installed_ext" ]; then
continue
fi
# Skip PROPRIETARY_EXTENSIONS - don't attempt to remove them
if [[ -v proprietary_extensions_set["$installed_ext"] ]]; then
continue
fi
# If extension is in VS Code, keep it (it will be synced)
if grep -Fxq "$installed_ext" "$vscode_list" 2>/dev/null; then
continue
fi
# Remove if it's an AI extension (not in VS Code but installed)
if [[ -v ai_extensions_set["$installed_ext"] ]]; then
to_remove+=("$installed_ext")
fi
done <"$installed_list"| echo "Initial Sync Complete." | ||
|
|
||
| # 4. Watcher (Mac only) | ||
| if command -v fswatch >/dev/null; then |
There was a problem hiding this comment.
command -v prints the path of the command to standard output if it's found. This output should be redirected to /dev/null to prevent it from being displayed during script execution. It's also good practice to redirect stderr.
| if command -v fswatch >/dev/null; then | |
| if command -v fswatch >/dev/null 2>&1; then |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (9)
home-manager/services/code-syncer/sync.sh (9)
59-59: Declare and assign separately to avoid masking return values (SC2155).The pattern
local var=$(command)masks the return value ofcommand -v. While this is low-risk here, separate the declaration and assignment for safety and consistency.Apply this diff to improve error-detection clarity:
- local resolved_path=$(command -v "$cmd" 2>/dev/null) + local resolved_path + resolved_path=$(command -v "$cmd" 2>/dev/null)Consider applying the same pattern fix to lines 106, 214, and 248 for consistency across the script.
73-77: Add error handling to ensure_dirs.The function silently ignores mkdir failures. Consider adding basic error checking for better observability.
Apply this diff to add error handling:
ensure_dirs() { - mkdir -p "$ANTIGRAVITY_USER_DIR" - mkdir -p "$CURSOR_USER_DIR" - mkdir -p "$WINDSURF_USER_DIR" + mkdir -p "$ANTIGRAVITY_USER_DIR" || echo "⚠️ Failed to create $ANTIGRAVITY_USER_DIR" + mkdir -p "$CURSOR_USER_DIR" || echo "⚠️ Failed to create $CURSOR_USER_DIR" + mkdir -p "$WINDSURF_USER_DIR" || echo "⚠️ Failed to create $WINDSURF_USER_DIR" }
81-101: Improve sed pattern specificity and add error handling.The function uses partial-line matching (e.g.,
/github.copilot/d) which could accidentally matchgithub.copilot-chatbefore the exact pattern is checked. Also, sed failures are silently ignored.Use exact line matching (
^pattern$) and add error handling:clean_extension_list() { local input_source="$1" local clean_file="$2" - if [ "$input_source" = "-" ]; then - cat >"$clean_file" + if [ "$input_source" = "-" ]; then + cat >"$clean_file" || { echo "⚠️ Failed to write to $clean_file"; return 1; } else - cp "$input_source" "$clean_file" + cp "$input_source" "$clean_file" || { echo "⚠️ Failed to copy $input_source"; return 1; } fi for bad_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do - sed -i '' "/$bad_ext/d" "$clean_file" + sed -i '' "/^$bad_ext\$/d" "$clean_file" || { echo "⚠️ Failed to filter $bad_ext"; return 1; } done for ai_ext in "${AI_EXTENSIONS[@]}"; do - sed -i '' "/$ai_ext/d" "$clean_file" + sed -i '' "/^$ai_ext\$/d" "$clean_file" || { echo "⚠️ Failed to filter $ai_ext"; return 1; } done }
106-106: Declare and assign separately to avoid masking return values (SC2155).The pattern
local cli_cmd=$(resolve_cli "$target_name")masks the return value. Separate declaration and assignment for consistency (also flagged on lines 214 and 248).Apply this diff:
- local cli_cmd=$(resolve_cli "$target_name") + local cli_cmd + cli_cmd=$(resolve_cli "$target_name")
138-144: Unused filtered list: clean_extension_list creates $clean_list but the removal logic doesn't use it.Line 138 calls
clean_extension_list "$vscode_list" "$clean_list", but the subsequent removal logic (lines 148–191) only checks if $clean_list is non-empty; it doesn't use $clean_list to filter $installed_list. The logic instead re-implements filtering inline (checking PROPRIETARY_EXTENSIONS and AI_EXTENSIONS per installed extension).This works, but it's redundant and harder to follow. Consider either:
- (A) Using the pre-filtered clean_list in the removal logic, or
- (B) Removing the clean_extension_list call here since it's not leveraged.
Clarify intent by either using $clean_list or removing the redundant call. If the goal is to remove only AI extensions that aren't in VS Code, simplify the logic:
# Create clean list of what should be synced clean_extension_list "$vscode_list" "$clean_list" - # Check if clean list has content - if [ ! -s "$clean_list" ]; then - # No extensions to sync after filtering - return - fi - # Find extensions to remove (skip PROPRIETARY_EXTENSIONS) local to_remove=() while IFS= read -r installed_ext; do if [ -z "$installed_ext" ]; then continue fi # Skip PROPRIETARY_EXTENSIONS - don't attempt to remove them local is_proprietary=false for prop_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do if [ "$installed_ext" = "$prop_ext" ]; then is_proprietary=true break fi done if [ "$is_proprietary" = true ]; then continue fi - # Check if extension is in VS Code's original list (before filtering) - # If it's in VS Code, we should keep it (it will be synced) - local in_vscode=false - if grep -Fxq "$installed_ext" "$vscode_list" 2>/dev/null; then - in_vscode=true - fi - - # If extension is in VS Code, keep it (don't remove) - if [ "$in_vscode" = true ]; then - continue - fi - # Check if extension is in AI_EXTENSIONS (should be removed) local is_ai=false for ai_ext in "${AI_EXTENSIONS[@]}"; do if [ "$installed_ext" = "$ai_ext" ]; then is_ai=true break fi done # Remove if it's an AI extension (not in VS Code but installed) if [ "$is_ai" = true ]; then to_remove+=("$installed_ext") fi done <"$installed_list"(This assumes the goal is to remove only AI extensions. If you want to preserve non-proprietary, non-AI extensions that aren't in VS Code, adjust accordingly.)
214-214: Declare and assign separately to avoid masking return values (SC2155).The pattern
local cli_cmd=$(resolve_cli "$target_name")masks the return value. Separate declaration and assignment for consistency.Apply this diff:
- local cli_cmd=$(resolve_cli "$target_name") + local cli_cmd + cli_cmd=$(resolve_cli "$target_name")
244-244: Comment inaccuracy: Clarify what "clean" means.The comment says "without proprietary MS extensions" but
clean_extension_listalso removes AI extensions (e.g., Claude Code, CodeRabbit). Update the comment for clarity.Apply this diff:
- # create a clean list without proprietary MS extensions + # create a clean list without proprietary and AI extensions clean_extension_list "$vscode_list" "$clean_list"
248-248: Declare and assign separately to avoid masking return values (SC2155).The pattern
local extension_count=$(wc -l <"$clean_list" 2>/dev/null | tr -d ' ' || echo "0")masks the return value of the pipeline. Separate declaration and assignment, or use a simpler command likegrep -c ''.Apply this diff to improve clarity:
- local extension_count=$(wc -l <"$clean_list" 2>/dev/null | tr -d ' ' || echo "0") + local extension_count + extension_count=$(grep -c '^..*$' "$clean_list" 2>/dev/null || echo "0") extension_count=${extension_count:-0}Or keep the current approach but separate the assignment for safety.
279-294: Add error handling to sync_config_file.The function silently ignores cp failures when copying config files to target editors. If a copy fails, users may not notice.
Apply this diff to add error checking:
sync_config_file() { local filename=$1 local source="$VSCODE_USER_DIR/$filename" if [ -f "$source" ]; then echo "Copying $filename to all editors..." echo " 📋 Source: $source" echo " 📤 Destinations:" echo " → $ANTIGRAVITY_USER_DIR/$filename" - cp "$source" "$ANTIGRAVITY_USER_DIR/$filename" + cp "$source" "$ANTIGRAVITY_USER_DIR/$filename" || echo " ⚠️ Failed to copy to Antigravity" echo " → $CURSOR_USER_DIR/$filename" - cp "$source" "$CURSOR_USER_DIR/$filename" + cp "$source" "$CURSOR_USER_DIR/$filename" || echo " ⚠️ Failed to copy to Cursor" echo " → $WINDSURF_USER_DIR/$filename" - cp "$source" "$WINDSURF_USER_DIR/$filename" + cp "$source" "$WINDSURF_USER_DIR/$filename" || echo " ⚠️ Failed to copy to Windsurf" fi }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
home-manager/services/code-syncer/sync.sh(1 hunks)
🧰 Additional context used
🪛 Shellcheck (0.11.0)
home-manager/services/code-syncer/sync.sh
[warning] 14-14: EXTENSIONS_FILE appears unused. Verify use (or export if used externally).
(SC2034)
[warning] 59-59: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 106-106: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 214-214: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 248-248: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 329-329: num appears unused. Verify use (or export if used externally).
(SC2034)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: Agent
- GitHub Check: nix-linux
- GitHub Check: nix-nixos
- GitHub Check: nix-darwin
- GitHub Check: e2e-run (NixOS, ubuntu-latest)
- GitHub Check: docker-build-push (linux/amd64, amd64, ubuntu-latest)
- GitHub Check: e2e-run (Ubuntu, ubuntu-latest)
- GitHub Check: e2e-run (MacOS, macos-latest)
- GitHub Check: docker-build-push (linux/arm64, arm64, ubuntu-24.04-arm)
🔇 Additional comments (2)
home-manager/services/code-syncer/sync.sh (2)
298-336: Main execution flow is well-orchestrated and user-friendly.The script follows a clear sequence: validate CLI, report extension count, sync configs, install/filter extensions, and optionally watch for changes. Error handling and logging are appropriate throughout the main flow.
Minor observation: Consider whether
/tmp/file collisions could occur if the script runs concurrently (e.g., in a cron job or multiple shells). If concurrency is possible, consider using unique temp files (e.g., withmktempor a PID suffix).Are there scenarios where this script might run concurrently? If so, would you like me to suggest temp file improvements?
14-14: Unused constant: Remove or document EXTENSIONS_FILE.The constant
EXTENSIONS_FILEis defined but never referenced in the script. Either remove it if it's dead code, or document its intended external use.If this constant is unused, apply this diff to remove it:
SETTINGS_FILE="settings.json" KEYBINDINGS_FILE="keybindings.json" -EXTENSIONS_FILE="extensions.list": If this is intentionally exported or used by external tools, clarify via a comment.
There was a problem hiding this comment.
Pull Request Overview
This PR significantly refactors the VS Code configuration syncing script to improve reliability and flexibility. The script now syncs VS Code settings, keybindings, and extensions to multiple editor forks (Antigravity, Cursor, Windsurf) while filtering out proprietary Microsoft extensions and AI-specific extensions that may not work properly in these forks.
Key Changes:
- Replaced hardcoded paths with
$HOME-based configuration for better portability - Added comprehensive blocklists for proprietary and AI extensions that should not be synced
- Implemented direct CLI-based extension retrieval and management instead of file-based syncing
- Enhanced logging with emoji indicators and detailed status messages for better user feedback
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| local installed_list="/tmp/${target_name}_extensions_installed.list" | ||
| local vscode_list="/tmp/vscode_extensions.list" | ||
|
|
||
| if [ -z "$cli_cmd" ] || [ ! -f "$cli_cmd" ]; then |
There was a problem hiding this comment.
The check [ ! -f "$cli_cmd" ] will fail if $cli_cmd is a symlink or if the command is in PATH but not a regular file. Use [ ! -x "$cli_cmd" ] instead to check if it's executable, or use command -v to verify the command exists and is executable.
| if [ -z "$cli_cmd" ] || [ ! -f "$cli_cmd" ]; then | |
| if [ -z "$cli_cmd" ] || [ ! -x "$cli_cmd" ]; then |
| clean_extension_list "$vscode_list" "$clean_list" | ||
|
|
||
| # Log extensions that will be synced | ||
| local extension_count=$(wc -l <"$clean_list" 2>/dev/null | tr -d ' ' || echo "0") |
There was a problem hiding this comment.
The expression $(wc -l <"$clean_list" 2>/dev/null | tr -d ' ' || echo "0") is overly complex. The subsequent line extension_count=${extension_count:-0} will never take effect because the || echo "0" ensures a value is always returned. Simplify to: extension_count=$(wc -l <"$clean_list" 2>/dev/null | tr -d ' ') and keep the ${extension_count:-0} assignment.
| local extension_count=$(wc -l <"$clean_list" 2>/dev/null | tr -d ' ' || echo "0") | |
| local extension_count=$(wc -l <"$clean_list" 2>/dev/null | tr -d ' ') |
| local input_source="$1" # Can be a file path or "-" for stdin | ||
| local clean_file="$2" | ||
|
|
||
| if [ -f "$source_path" ]; then | ||
| cp "$source_path" "$ANTIGRAVITY_USER_DIR/$file" | ||
| cp "$source_path" "$CURSOR_USER_DIR/$file" | ||
| cp "$source_path" "$WINDSURF_USER_DIR/$file" | ||
| echo "$file synced at $(date)" | ||
| # Read from stdin if input_source is "-", otherwise from file | ||
| if [ "$input_source" = "-" ]; then | ||
| cat >"$clean_file" | ||
| else | ||
| echo "VSCode $file not found" | ||
| cp "$input_source" "$clean_file" |
There was a problem hiding this comment.
The input_source parameter is used in a command without proper validation. If it contains special characters or command injection payloads, it could lead to security issues. Consider validating or quoting the parameter, or restrict its usage to known safe values.
| echo "" | ||
| fi | ||
| } | ||
|
|
There was a problem hiding this comment.
The function install_extensions lacks documentation explaining its parameters, what it does, and its behavior. Add a comment describing that it syncs VS Code extensions to the target editor after filtering out proprietary and AI extensions.
| # Syncs VS Code extensions to the target editor after filtering out proprietary and AI extensions. | |
| # | |
| # Parameters: | |
| # $1 - target_name: The name of the target editor (e.g., "antigravity", "cursor", "windsurf"). | |
| # | |
| # Behavior: | |
| # - Removes unnecessary extensions from the target editor. | |
| # - Retrieves the list of extensions installed in VS Code. | |
| # - Filters out proprietary and AI extensions from the list. | |
| # - Installs the filtered extensions to the target editor using its CLI. | |
| # - Logs successes and failures, skips syncing if required CLIs are not found. |
| if [ $? -ne 0 ]; then | ||
| echo " ❌ Failed: $extension (Likely missing from Open VSX)" | ||
| else | ||
| # Optional: verify it's actually installed | ||
| echo " ✅ Synced: $extension" | ||
| fi |
There was a problem hiding this comment.
Avoid using bare $? checks in conditional statements. The if statement already captures the exit status, making the subsequent check redundant. Combine into: if ! $cli_cmd --install-extension "$extension" >/dev/null 2>&1; then echo " ❌ Failed: $extension (Likely missing from Open VSX)"; else echo " ✅ Synced: $extension"; fi
| $cli_cmd --list-extensions >"$installed_list" 2>/dev/null | ||
| if [ $? -ne 0 ]; then |
There was a problem hiding this comment.
Avoid using bare $? checks in conditional statements. The if statement already captures the exit status, making the subsequent check redundant. Remove the if [ $? -ne 0 ]; then return; fi pattern and instead use: if ! $cli_cmd --list-extensions >"$installed_list" 2>/dev/null; then return; fi
| $cli_cmd --list-extensions >"$installed_list" 2>/dev/null | |
| if [ $? -ne 0 ]; then | |
| if ! $cli_cmd --list-extensions >"$installed_list" 2>/dev/null; then |
| if [ $? -eq 0 ]; then | ||
| echo " ✅ Removed: $ext" | ||
| else | ||
| echo " ⚠️ Failed to remove: $ext" | ||
| fi |
There was a problem hiding this comment.
Avoid using bare $? checks in conditional statements. The if statement already captures the exit status, making the subsequent check redundant. Combine into: if $cli_cmd --uninstall-extension "$ext" >/dev/null 2>&1; then echo " ✅ Removed: $ext"; else echo " ⚠️ Failed to remove: $ext"; fi
| for bad_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do | ||
| # Remove the line containing the bad extension | ||
| sed -i '' "/$bad_ext/d" "$clean_file" | ||
| done | ||
|
|
||
| for ai_ext in "${AI_EXTENSIONS[@]}"; do | ||
| # Remove the line containing the AI extension | ||
| sed -i '' "/$ai_ext/d" "$clean_file" | ||
| done |
There was a problem hiding this comment.
Running sed in a loop for each extension is inefficient. Instead, build a single sed expression with multiple patterns: sed -i '' -e "/$ext1/d" -e "/$ext2/d" ... "$clean_file" or use a single regex pattern that matches all extensions. This would reduce the number of file I/O operations significantly.
| for bad_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do | |
| # Remove the line containing the bad extension | |
| sed -i '' "/$bad_ext/d" "$clean_file" | |
| done | |
| for ai_ext in "${AI_EXTENSIONS[@]}"; do | |
| # Remove the line containing the AI extension | |
| sed -i '' "/$ai_ext/d" "$clean_file" | |
| done | |
| # Build sed delete expressions for all extensions | |
| local sed_expr=() | |
| for bad_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do | |
| sed_expr+=("-e" "/$bad_ext/d") | |
| done | |
| for ai_ext in "${AI_EXTENSIONS[@]}"; do | |
| sed_expr+=("-e" "/$ai_ext/d") | |
| done | |
| sed -i '' "${sed_expr[@]}" "$clean_file" |
| local clean_list="/tmp/${target_name}_extensions_clean.list" | ||
| local vscode_list="/tmp/vscode_extensions.list" |
There was a problem hiding this comment.
Using predictable filenames in /tmp (e.g., /tmp/${target_name}_extensions_clean.list) can lead to security vulnerabilities such as symlink attacks or race conditions. Use mktemp to create secure temporary files instead: local clean_list=$(mktemp)
| local clean_list="/tmp/${target_name}_extensions_clean.list" | |
| local vscode_list="/tmp/vscode_extensions.list" | |
| local clean_list=$(mktemp) | |
| local vscode_list=$(mktemp) |
| # Skip PROPRIETARY_EXTENSIONS - don't attempt to remove them | ||
| local is_proprietary=false | ||
| for prop_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do | ||
| if [ "$installed_ext" = "$prop_ext" ]; then | ||
| is_proprietary=true | ||
| break | ||
| fi | ||
| done |
There was a problem hiding this comment.
The nested loop structure checking for proprietary extensions (lines 154-160) and AI extensions (lines 178-185) is inefficient with O(n*m) complexity. Consider using associative arrays (bash 4+) or a more efficient lookup method to improve performance when dealing with large extension lists.
…n syncing - Adjusted spacing in the clean_extension_list function for consistency. - Removed unnecessary blank lines in the install_extensions function to enhance readability. - Improved logging to provide clearer feedback on the number of extensions found for syncing.
There was a problem hiding this comment.
2 issues found across 1 file
Prompt for AI agents (all 2 issues)
Understand the root cause of the following 2 issues and fix them.
<file name="home-manager/services/code-syncer/sync.sh">
<violation number="1" location="home-manager/services/code-syncer/sync.sh:94">
clean_extension_list deletes extensions whenever their ID merely contains a blocklisted substring, so legitimate extensions such as github.copilot-labs are filtered out even though they were not meant to be blocked.</violation>
<violation number="2" location="home-manager/services/code-syncer/sync.sh:169">
remove_unnecessary_extensions compares installed extensions against the unfiltered VS Code list, so extensions that should have been excluded by the blocklist are never removed when they still exist in VS Code.</violation>
</file>
Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR
| # Check if extension is in VS Code's original list (before filtering) | ||
| # If it's in VS Code, we should keep it (it will be synced) | ||
| local in_vscode=false | ||
| if grep -Fxq "$installed_ext" "$vscode_list" 2>/dev/null; then |
There was a problem hiding this comment.
remove_unnecessary_extensions compares installed extensions against the unfiltered VS Code list, so extensions that should have been excluded by the blocklist are never removed when they still exist in VS Code.
Prompt for AI agents
Address the following comment on home-manager/services/code-syncer/sync.sh at line 169:
<comment>remove_unnecessary_extensions compares installed extensions against the unfiltered VS Code list, so extensions that should have been excluded by the blocklist are never removed when they still exist in VS Code.</comment>
<file context>
@@ -1,94 +1,336 @@
+ # Check if extension is in VS Code's original list (before filtering)
+ # If it's in VS Code, we should keep it (it will be synced)
+ local in_vscode=false
+ if grep -Fxq "$installed_ext" "$vscode_list" 2>/dev/null; then
+ in_vscode=true
+ fi
</file context>
| if grep -Fxq "$installed_ext" "$vscode_list" 2>/dev/null; then | |
| if grep -Fxq "$installed_ext" "$clean_list" 2>/dev/null; then |
|
|
||
| for bad_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do | ||
| # Remove the line containing the bad extension | ||
| sed -i '' "/$bad_ext/d" "$clean_file" |
There was a problem hiding this comment.
clean_extension_list deletes extensions whenever their ID merely contains a blocklisted substring, so legitimate extensions such as github.copilot-labs are filtered out even though they were not meant to be blocked.
Prompt for AI agents
Address the following comment on home-manager/services/code-syncer/sync.sh at line 94:
<comment>clean_extension_list deletes extensions whenever their ID merely contains a blocklisted substring, so legitimate extensions such as github.copilot-labs are filtered out even though they were not meant to be blocked.</comment>
<file context>
@@ -1,94 +1,336 @@
+
+ for bad_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do
+ # Remove the line containing the bad extension
+ sed -i '' "/$bad_ext/d" "$clean_file"
+ done
+
</file context>
| sed -i '' "/$bad_ext/d" "$clean_file" | |
| sed -i '' "/^${bad_ext//./\.}$/d" "$clean_file" |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (9)
home-manager/services/code-syncer/sync.sh (9)
111-111: Use robust executable check and consolidate validation inresolve_cli().The check
[ ! -f "$cli_cmd" ]is unreliable for symlinks and executables. Previous reviews suggested consolidating validation withinresolve_cli()and checking for executability (-x). This simplifies call sites toif [ -z "$cli_cmd" ]; then.
116-117: Avoid bare$?checks; useif !pattern instead.This pattern is redundant and less readable. Let the
ifstatement capture exit status directly.- $cli_cmd --list-extensions >"$installed_list" 2>/dev/null - if [ $? -ne 0 ]; then + if ! $cli_cmd --list-extensions >"$installed_list" 2>/dev/null; then
154-160: Optimize extension lookup from O(N*M) to O(1) using associative arrays.The nested loops checking blocklists have quadratic complexity. For large extension lists, use associative arrays (bash 4+) for near-constant-time lookups, as suggested in previous reviews.
# Find extensions to remove (skip PROPRIETARY_EXTENSIONS) local to_remove=() + declare -A proprietary_set ai_set + for ext in "${PROPRIETARY_EXTENSIONS[@]}"; do proprietary_set["$ext"]=1; done + for ext in "${AI_EXTENSIONS[@]}"; do ai_set["$ext"]=1; done + while IFS= read -r installed_ext; do if [ -z "$installed_ext" ]; then continue fi # Skip PROPRIETARY_EXTENSIONS - don't attempt to remove them - local is_proprietary=false - for prop_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do - if [ "$installed_ext" = "$prop_ext" ]; then - is_proprietary=true - break - fi - done - - if [ "$is_proprietary" = true ]; then + if [[ -v proprietary_set["$installed_ext"] ]]; then continue fi # Check if extension is in VS Code's original list (before filtering) # If it's in VS Code, we should keep it (it will be synced) - local in_vscode=false if grep -Fxq "$installed_ext" "$vscode_list" 2>/dev/null; then - in_vscode=true + continue fi - - # If extension is in VS Code, keep it (don't remove) - if [ "$in_vscode" = true ]; then - continue - fi # Check if extension is in AI_EXTENSIONS (should be removed) - local is_ai=false - for ai_ext in "${AI_EXTENSIONS[@]}"; do - if [ "$installed_ext" = "$ai_ext" ]; then - is_ai=true - break - fi - done - - # Remove if it's an AI extension (not in VS Code but installed) - if [ "$is_ai" = true ]; then + if [[ -v ai_set["$installed_ext"] ]]; then to_remove+=("$installed_ext") fiAlso applies to: 180-185
198-199: Avoid bare$?checks; useif !pattern instead.Use direct exit status capture for clearer, more idiomatic bash code.
- $cli_cmd --uninstall-extension "$ext" >/dev/null 2>&1 - if [ $? -eq 0 ]; then + if $cli_cmd --uninstall-extension "$ext" >/dev/null 2>&1; then echo " ✅ Removed: $ext" else echo " ⚠️ Failed to remove: $ext"
214-214: Declare and assign separately to detect resolution failures.SC2155 pattern masks return value from
resolve_cli().- local cli_cmd=$(resolve_cli "$target_name") + local cli_cmd + cli_cmd=$(resolve_cli "$target_name")
215-216: Replace hardcoded/tmppaths withmktempfor security.Use
mktempinstead of predictable filenames, as flagged in previous reviews.- local clean_list="/tmp/${target_name}_extensions_clean.list" - local vscode_list="/tmp/vscode_extensions.list" + local clean_list + local vscode_list + clean_list=$(mktemp) + vscode_list=$(mktemp) + trap 'rm -f "$clean_list" "$vscode_list"' RETURN
218-218: Use robust executable check instead of[ ! -f ].The file test is unreliable for symlinks and executables. Previous reviews suggested centralizing validation in
resolve_cli().
264-265: Avoid bare$?checks; useif !pattern instead.Combine the command and conditional check for clarity.
- $cli_cmd --install-extension "$extension" >/dev/null 2>&1 - if [ $? -ne 0 ]; then + if ! $cli_cmd --install-extension "$extension" >/dev/null 2>&1; then
327-327: Redirect stderr incommand -vcheck.Ensure both stdout and stderr are redirected to suppress any command output, consistent with line 302 and previous review feedback.
-if command -v fswatch >/dev/null; then +if command -v fswatch >/dev/null 2>&1; then
🧹 Nitpick comments (5)
home-manager/services/code-syncer/sync.sh (5)
59-59: Declare and assign separately to detect command failures.The SC2155 pattern masks the return value of
command -v. Separate the declaration and assignment to properly detect errors.resolve_cli() { local cmd=$1 - local resolved_path=$(command -v "$cmd" 2>/dev/null) + local resolved_path + resolved_path=$(command -v "$cmd" 2>/dev/null)
92-100: Use exact-line matching instead of substring patterns in sed.The sed patterns
/$bad_ext/dperform substring matching and can remove unintended lines. For portability (Linux/macOS) and exact matching, usegrep -Fxvas suggested in previous reviews.- for bad_ext in "${PROPRIETARY_EXTENSIONS[@]}"; do - # Remove the line containing the bad extension - sed -i '' "/$bad_ext/d" "$clean_file" - done - - for ai_ext in "${AI_EXTENSIONS[@]}"; do - # Remove the line containing the AI extension - sed -i '' "/$ai_ext/d" "$clean_file" - done + local temp_blocklist + temp_blocklist=$(mktemp) + trap 'rm -f "$temp_blocklist"' RETURN + + # Combine both blocklists into a single file for grep + printf '%s\n' "${PROPRIETARY_EXTENSIONS[@]}" "${AI_EXTENSIONS[@]}" >"$temp_blocklist" + + # Use grep to filter out blocklisted extensions (exact line matching) + grep -v -F -x -f "$temp_blocklist" "$clean_file" >"${clean_file}.tmp" && mv "${clean_file}.tmp" "$clean_file"
106-106: Declare and assign separately to detect resolution failures.SC2155 pattern masks the return value of
resolve_cli(). Separate the declaration and assignment.- local cli_cmd=$(resolve_cli "$target_name") + local cli_cmd + cli_cmd=$(resolve_cli "$target_name")
107-109: Replace hardcoded/tmppaths withmktempfor security and atomicity.Predictable filenames in
/tmpcreate security vulnerabilities (symlink attacks, race conditions). Usemktempand add cleanup traps. This addresses previous review feedback.- local clean_list="/tmp/${target_name}_extensions_clean.list" - local installed_list="/tmp/${target_name}_extensions_installed.list" - local vscode_list="/tmp/vscode_extensions.list" + local clean_list + local installed_list + local vscode_list + clean_list=$(mktemp) + installed_list=$(mktemp) + vscode_list=$(mktemp) + trap 'rm -f "$clean_list" "$installed_list" "$vscode_list"' RETURN
248-249: Simplify extension count expression and remove redundant assignment.The pattern
$(... || echo "0")followed by${extension_count:-0}is redundant. The fallback in the assignment will never execute. Simplify by separating declare and assign (SC2155) and letting the fallback work correctly.- local extension_count=$(wc -l <"$clean_list" 2>/dev/null | tr -d ' ' || echo "0") + local extension_count + extension_count=$(wc -l <"$clean_list" 2>/dev/null | tr -d ' ') extension_count=${extension_count:-0}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
home-manager/services/code-syncer/sync.sh(1 hunks)
🧰 Additional context used
🪛 Shellcheck (0.11.0)
home-manager/services/code-syncer/sync.sh
[warning] 14-14: EXTENSIONS_FILE appears unused. Verify use (or export if used externally).
(SC2034)
[warning] 59-59: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 106-106: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 214-214: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 248-248: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 329-329: num appears unused. Verify use (or export if used externally).
(SC2034)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: docker-build-push (linux/amd64, amd64, ubuntu-latest)
- GitHub Check: docker-build-push (linux/arm64, arm64, ubuntu-24.04-arm)
- GitHub Check: nix-linux
- GitHub Check: nix-nixos
- GitHub Check: nix-darwin
- GitHub Check: e2e-run (MacOS, macos-latest)
- GitHub Check: e2e-run (NixOS, ubuntu-latest)
- GitHub Check: e2e-run (Ubuntu, ubuntu-latest)
| # Files | ||
| SETTINGS_FILE="settings.json" | ||
| KEYBINDINGS_FILE="keybindings.json" | ||
| EXTENSIONS_FILE="extensions.list" |
There was a problem hiding this comment.
Unused variable: remove or verify external use.
EXTENSIONS_FILE is defined but never referenced in this script. If this is intentionally preserved for external scripts, consider adding a comment. Otherwise, remove it.
🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 14-14: EXTENSIONS_FILE appears unused. Verify use (or export if used externally).
(SC2034)
🤖 Prompt for AI Agents
In home-manager/services/code-syncer/sync.sh around line 14, the variable
EXTENSIONS_FILE is declared but never used; either delete this unused variable
to clean up the script or make its intent explicit by exporting it and/or adding
a comment that it is consumed by external scripts (e.g., add a one-line comment
describing external usage or change to export EXTENSIONS_FILE so downstream
processes can read it).
| # 4. Watcher (Mac only) | ||
| if command -v fswatch >/dev/null; then | ||
| echo "Watching for changes in VS Code settings..." | ||
| fswatch -o "$VSCODE_USER_DIR/$SETTINGS_FILE" "$VSCODE_USER_DIR/$KEYBINDINGS_FILE" | while read num; do |
There was a problem hiding this comment.
Address unused variable in fswatch loop.
The num variable appears unused. Either use it explicitly or replace with _ for clarity that the value is intentionally ignored.
- fswatch -o "$VSCODE_USER_DIR/$SETTINGS_FILE" "$VSCODE_USER_DIR/$KEYBINDINGS_FILE" | while read num; do
+ fswatch -o "$VSCODE_USER_DIR/$SETTINGS_FILE" "$VSCODE_USER_DIR/$KEYBINDINGS_FILE" | while read _; do📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fswatch -o "$VSCODE_USER_DIR/$SETTINGS_FILE" "$VSCODE_USER_DIR/$KEYBINDINGS_FILE" | while read num; do | |
| fswatch -o "$VSCODE_USER_DIR/$SETTINGS_FILE" "$VSCODE_USER_DIR/$KEYBINDINGS_FILE" | while read _; do |
🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 329-329: num appears unused. Verify use (or export if used externally).
(SC2034)
🤖 Prompt for AI Agents
In home-manager/services/code-syncer/sync.sh around line 329, the fswatch loop
reads into an unused variable `num`; change the read to use a placeholder (e.g.,
`read -r _`) or otherwise name it `_` to signal the value is intentionally
ignored (or use `read -r ignored` if clearer), ensuring no functional change to
the loop but removing the unused variable warning.
Summary by cubic
Revamps the code-syncer to reliably mirror VS Code settings and extensions to Cursor, Windsurf, and Antigravity. Uses the VS Code CLI, filters blocked extensions, removes extras on targets, and adds clearer logs plus auto-watch.
New Features
Bug Fixes
Written for commit 958dd23. Summary will update automatically on new commits.