mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-18 22:54:24 +01:00
mcp(developer): add fallback on .gitignore if no .gooseignore is present (#2661)
This commit is contained in:
committed by
GitHub
parent
3a22d6b452
commit
d1dc6c3ff0
@@ -408,10 +408,20 @@ impl DeveloperRouter {
|
|||||||
if local_ignore_path.is_file() {
|
if local_ignore_path.is_file() {
|
||||||
let _ = builder.add(local_ignore_path);
|
let _ = builder.add(local_ignore_path);
|
||||||
has_ignore_file = true;
|
has_ignore_file = true;
|
||||||
|
} else {
|
||||||
|
// If no .gooseignore exists, check for .gitignore as fallback
|
||||||
|
let gitignore_path = cwd.join(".gitignore");
|
||||||
|
if gitignore_path.is_file() {
|
||||||
|
tracing::debug!(
|
||||||
|
"No .gooseignore found, using .gitignore as fallback for ignore patterns"
|
||||||
|
);
|
||||||
|
let _ = builder.add(gitignore_path);
|
||||||
|
has_ignore_file = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only use default patterns if no .gooseignore files were found
|
// Only use default patterns if no .gooseignore files were found
|
||||||
// If the file is empty, we will not ignore any file
|
// AND no .gitignore was used as fallback
|
||||||
if !has_ignore_file {
|
if !has_ignore_file {
|
||||||
// Add some sensible defaults
|
// Add some sensible defaults
|
||||||
let _ = builder.add_line(None, "**/.env");
|
let _ = builder.add_line(None, "**/.env");
|
||||||
@@ -1758,4 +1768,199 @@ mod tests {
|
|||||||
|
|
||||||
temp_dir.close().unwrap();
|
temp_dir.close().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_gitignore_fallback_when_no_gooseignore() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
std::env::set_current_dir(&temp_dir).unwrap();
|
||||||
|
|
||||||
|
// Create a .gitignore file but no .gooseignore
|
||||||
|
std::fs::write(temp_dir.path().join(".gitignore"), "*.log\n*.tmp\n.env").unwrap();
|
||||||
|
|
||||||
|
let router = DeveloperRouter::new();
|
||||||
|
|
||||||
|
// Test that gitignore patterns are respected
|
||||||
|
assert!(
|
||||||
|
router.is_ignored(Path::new("test.log")),
|
||||||
|
"*.log pattern from .gitignore should be ignored"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
router.is_ignored(Path::new("build.tmp")),
|
||||||
|
"*.tmp pattern from .gitignore should be ignored"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
router.is_ignored(Path::new(".env")),
|
||||||
|
".env pattern from .gitignore should be ignored"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!router.is_ignored(Path::new("test.txt")),
|
||||||
|
"test.txt should not be ignored"
|
||||||
|
);
|
||||||
|
|
||||||
|
temp_dir.close().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_gooseignore_takes_precedence_over_gitignore() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
std::env::set_current_dir(&temp_dir).unwrap();
|
||||||
|
|
||||||
|
// Create both .gooseignore and .gitignore files with different patterns
|
||||||
|
std::fs::write(temp_dir.path().join(".gooseignore"), "*.secret").unwrap();
|
||||||
|
std::fs::write(temp_dir.path().join(".gitignore"), "*.log\ntarget/").unwrap();
|
||||||
|
|
||||||
|
let router = DeveloperRouter::new();
|
||||||
|
|
||||||
|
// .gooseignore patterns should be used
|
||||||
|
assert!(
|
||||||
|
router.is_ignored(Path::new("test.secret")),
|
||||||
|
"*.secret pattern from .gooseignore should be ignored"
|
||||||
|
);
|
||||||
|
|
||||||
|
// .gitignore patterns should NOT be used when .gooseignore exists
|
||||||
|
assert!(
|
||||||
|
!router.is_ignored(Path::new("test.log")),
|
||||||
|
"*.log pattern from .gitignore should NOT be ignored when .gooseignore exists"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!router.is_ignored(Path::new("build.tmp")),
|
||||||
|
"*.tmp pattern from .gitignore should NOT be ignored when .gooseignore exists"
|
||||||
|
);
|
||||||
|
|
||||||
|
temp_dir.close().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_default_patterns_when_no_ignore_files() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
std::env::set_current_dir(&temp_dir).unwrap();
|
||||||
|
|
||||||
|
// Don't create any ignore files
|
||||||
|
let router = DeveloperRouter::new();
|
||||||
|
|
||||||
|
// Default patterns should be used
|
||||||
|
assert!(
|
||||||
|
router.is_ignored(Path::new(".env")),
|
||||||
|
".env should be ignored by default patterns"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
router.is_ignored(Path::new(".env.local")),
|
||||||
|
".env.local should be ignored by default patterns"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
router.is_ignored(Path::new("secrets.txt")),
|
||||||
|
"secrets.txt should be ignored by default patterns"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!router.is_ignored(Path::new("normal.txt")),
|
||||||
|
"normal.txt should not be ignored"
|
||||||
|
);
|
||||||
|
|
||||||
|
temp_dir.close().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_text_editor_respects_gitignore_fallback() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
std::env::set_current_dir(&temp_dir).unwrap();
|
||||||
|
|
||||||
|
// Create a .gitignore file but no .gooseignore
|
||||||
|
std::fs::write(temp_dir.path().join(".gitignore"), "*.log").unwrap();
|
||||||
|
|
||||||
|
let router = DeveloperRouter::new();
|
||||||
|
|
||||||
|
// Try to write to a file ignored by .gitignore
|
||||||
|
let result = router
|
||||||
|
.call_tool(
|
||||||
|
"text_editor",
|
||||||
|
json!({
|
||||||
|
"command": "write",
|
||||||
|
"path": temp_dir.path().join("test.log").to_str().unwrap(),
|
||||||
|
"file_text": "test content"
|
||||||
|
}),
|
||||||
|
dummy_sender(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"Should not be able to write to file ignored by .gitignore fallback"
|
||||||
|
);
|
||||||
|
assert!(matches!(result.unwrap_err(), ToolError::ExecutionError(_)));
|
||||||
|
|
||||||
|
// Try to write to a non-ignored file
|
||||||
|
let result = router
|
||||||
|
.call_tool(
|
||||||
|
"text_editor",
|
||||||
|
json!({
|
||||||
|
"command": "write",
|
||||||
|
"path": temp_dir.path().join("allowed.txt").to_str().unwrap(),
|
||||||
|
"file_text": "test content"
|
||||||
|
}),
|
||||||
|
dummy_sender(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"Should be able to write to non-ignored file"
|
||||||
|
);
|
||||||
|
|
||||||
|
temp_dir.close().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_bash_respects_gitignore_fallback() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
std::env::set_current_dir(&temp_dir).unwrap();
|
||||||
|
|
||||||
|
// Create a .gitignore file but no .gooseignore
|
||||||
|
std::fs::write(temp_dir.path().join(".gitignore"), "*.log").unwrap();
|
||||||
|
|
||||||
|
let router = DeveloperRouter::new();
|
||||||
|
|
||||||
|
// Create a file that would be ignored by .gitignore
|
||||||
|
let log_file_path = temp_dir.path().join("test.log");
|
||||||
|
std::fs::write(&log_file_path, "log content").unwrap();
|
||||||
|
|
||||||
|
// Try to cat the ignored file
|
||||||
|
let result = router
|
||||||
|
.call_tool(
|
||||||
|
"shell",
|
||||||
|
json!({
|
||||||
|
"command": format!("cat {}", log_file_path.to_str().unwrap())
|
||||||
|
}),
|
||||||
|
dummy_sender(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"Should not be able to cat file ignored by .gitignore fallback"
|
||||||
|
);
|
||||||
|
assert!(matches!(result.unwrap_err(), ToolError::ExecutionError(_)));
|
||||||
|
|
||||||
|
// Try to cat a non-ignored file
|
||||||
|
let allowed_file_path = temp_dir.path().join("allowed.txt");
|
||||||
|
std::fs::write(&allowed_file_path, "allowed content").unwrap();
|
||||||
|
|
||||||
|
let result = router
|
||||||
|
.call_tool(
|
||||||
|
"shell",
|
||||||
|
json!({
|
||||||
|
"command": format!("cat {}", allowed_file_path.to_str().unwrap())
|
||||||
|
}),
|
||||||
|
dummy_sender(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "Should be able to cat non-ignored file");
|
||||||
|
|
||||||
|
temp_dir.close().unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,24 @@ Goose supports two types of `.gooseignore` files:
|
|||||||
You can use both global and local `.gooseignore` files simultaneously. When both exist, Goose will combine the restrictions from both files to determine which paths are restricted.
|
You can use both global and local `.gooseignore` files simultaneously. When both exist, Goose will combine the restrictions from both files to determine which paths are restricted.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## Automatic `.gitignore` fallback
|
||||||
|
|
||||||
|
If no `.gooseignore` file is found in your current directory, Goose will automatically use your `.gitignore` file as a fallback. This means:
|
||||||
|
|
||||||
|
1. **Priority Order**: Goose checks for ignore patterns in this order:
|
||||||
|
- Global `.gooseignore` (if exists)
|
||||||
|
- Local `.gooseignore` (if exists)
|
||||||
|
- Local `.gitignore` (if no local `.gooseignore` and `.gitignore` exists)
|
||||||
|
- Default patterns (if none of the above exist)
|
||||||
|
|
||||||
|
2. **Seamless Integration**: Projects with existing `.gitignore` files get automatic protection without needing a separate `.gooseignore` file.
|
||||||
|
|
||||||
|
3. **Override Capability**: Creating a local `.gooseignore` file will completely override `.gitignore` patterns for that directory.
|
||||||
|
|
||||||
|
:::info Debug logging
|
||||||
|
When Goose uses `.gitignore` as a fallback, it will log a message to help you understand which ignore file is being used.
|
||||||
|
:::
|
||||||
|
|
||||||
## Example `.gooseignore` file
|
## Example `.gooseignore` file
|
||||||
|
|
||||||
In your `.gooseignore` file, you can write patterns to match files you want Goose to ignore. Here are some common patterns:
|
In your `.gooseignore` file, you can write patterns to match files you want Goose to ignore. Here are some common patterns:
|
||||||
@@ -49,7 +67,7 @@ downloads/ # Ignore everything in the "downloads" directory
|
|||||||
|
|
||||||
## Default patterns
|
## Default patterns
|
||||||
|
|
||||||
By default, if you haven't created any `.gooseignore` files, Goose will not modify files matching these patterns:
|
By default, if you haven't created any `.gooseignore` files **and no `.gitignore` file exists**, Goose will not modify files matching these patterns:
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
**/.env
|
**/.env
|
||||||
@@ -57,6 +75,8 @@ By default, if you haven't created any `.gooseignore` files, Goose will not modi
|
|||||||
**/secrets.*
|
**/secrets.*
|
||||||
```
|
```
|
||||||
|
|
||||||
|
These default patterns only apply when neither `.gooseignore` nor `.gitignore` files are found in your project.
|
||||||
|
|
||||||
## Common use cases
|
## Common use cases
|
||||||
|
|
||||||
Here are some typical scenarios where `.gooseignore` is helpful:
|
Here are some typical scenarios where `.gooseignore` is helpful:
|
||||||
@@ -65,4 +85,6 @@ Here are some typical scenarios where `.gooseignore` is helpful:
|
|||||||
- **Third-Party Code**: Keep Goose from changing external libraries or dependencies
|
- **Third-Party Code**: Keep Goose from changing external libraries or dependencies
|
||||||
- **Important Configurations**: Protect critical configuration files from accidental modifications
|
- **Important Configurations**: Protect critical configuration files from accidental modifications
|
||||||
- **Version Control**: Prevent changes to version control files like `.git` directory
|
- **Version Control**: Prevent changes to version control files like `.git` directory
|
||||||
|
- **Existing Projects**: Most projects already have `.gitignore` files that work automatically as ignore patterns for Goose
|
||||||
|
- **Custom Restrictions**: Create `.gooseignore` when you need different patterns than your `.gitignore` (e.g., allowing Goose to read files that Git ignores)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user