Skip to content
Merged
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
2 changes: 2 additions & 0 deletions docfx/docs/nbgv-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,6 @@ usage: nbgv <command> [<args>]
prepare-release Prepares a release by creating a release branch for
the current version and adjusting the version on the
current branch.
path-filters Manages the pathFilters property in version.json files
based on MSBuild project references and imports.
```
135 changes: 135 additions & 0 deletions docfx/docs/path-filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,138 @@ Multiple path filters may also be specified. The order is irrelevant. After a pa
| `/root-file.txt`<br>`:/dir/file.txt` | File will be included. Path is absolute (i.e., relative to the root of the repository). |
| `:!bar.txt`<br>`:^../foo/baz.txt` | File will be excluded. Path is relative to the `version.json` file. `:!` and `:^` prefixes are synonymous. |
| `:!/root-file.txt` | File will be excluded. Path is absolute (i.e., relative to the root of the repository). |

## Managing path filters with `nbgv path-filters`

For repositories with multiple projects and version.json files, manually maintaining `pathFilters` can be error-prone. The `nbgv path-filters` command automates this process by analyzing your MSBuild project structure and computing the correct path filters based on project references and shared build files.

### When to use path-filters command

Use the `nbgv path-filters` command in the following scenarios:

- **Monorepo with multiple projects** - You have multiple projects in different directories, each with their own `version.json`
- **Complex project dependencies** - Projects reference each other, and you need path filters to reflect these dependencies
- **Shared build files** - You use `Directory.Build.props` or other shared MSBuild imports that should be tracked by multiple projects
- **Maintaining accuracy** - You want to ensure path filters automatically stay in sync with your project structure

### How it works

The `nbgv path-filters` command uses the MSBuild project graph API to:

1. **Discover project files** - Finds all MSBuild project files (`.csproj`, `.vbproj`, etc.) associated with each `version.json`
2. **Compute transitive dependencies** - Uses the MSBuild project graph to determine the complete set of projects that each project depends on
3. **Include shared build files** - Identifies MSBuild imports like `Directory.Build.props` that reside within the repository
4. **Respect boundaries** - Stops searching for projects at directories containing their own `version.json` files, ensuring clean separation of concerns
5. **Generate path filters** - Converts the discovered projects and files into appropriate `pathFilters` entries

### Important behaviors

- **Only processes version.json files with projects** - A `version.json` file with no associated MSBuild projects is skipped and left unchanged
- **Respects version.json hierarchy** - When searching for projects under a `version.json`, the search stops at subdirectories that have their own `version.json` files
- **Includes project directories** - Path filters include entire project directories (e.g., `/ProjectA`) rather than individual `.csproj` files so that all source changes under those directories result in a new version of the project
- **Filters ignored files** - Automatically excludes files that match `.gitignore` patterns (including generated directories like `obj/` and `bin/`)

### Usage

#### Check current path filters

To see what path filters should be present without making changes:

```ps1
nbgv path-filters check
```

This command will:
- Compare the computed path filters against what's currently in each `version.json`
- Display mismatches (missing or extra filters)
- Exit with non-zero code if any mismatches are found (useful for CI validation)

#### Update path filters

To automatically compute and update all `version.json` files:

```ps1
nbgv path-filters update
```

This command will:
- Compute the correct path filters for each `version.json`
- Update each file that needs changes
- Display which files were updated
- Skip any `version.json` files that have no associated projects

#### Specify which version.json files to process

By default, both commands search from the current directory. You can specify specific paths:

```ps1
nbgv path-filters check ./src/ProjectA ./src/ProjectB
```

#### Include additional project file extensions

By default, the tool searches for `.csproj` and `.vbproj` files. You can include other extensions:

```ps1
nbgv path-filters update --ext .fsproj --ext .csproj
```

### Example

Consider a monorepo with this structure:

```
/
version.json (version: "1.0")
Directory.Build.props
/ProjectA
version.json (version: "2.0")
ProjectA.csproj
/ProjectB
version.json (version: "3.0")
ProjectB.csproj
(ProjectB.csproj references ProjectA.csproj)
```

Running `nbgv path-filters update` would produce:

**Root version.json** - Left unchanged (has no projects directly under it)

**ProjectA/version.json**:
```json
{
"version": "2.0",
"pathFilters": [
"/ProjectA",
"/Directory.Build.props"
]
}
```

**ProjectB/version.json**:
```json
{
"version": "3.0",
"pathFilters": [
"/ProjectA",
"/ProjectB",
"/Directory.Build.props"
]
}
```

Note that ProjectB's filters include ProjectA because ProjectB depends on it. Any change to ProjectA's source files will now correctly trigger a version bump for ProjectB as well.

### CI Integration

You can use the `path-filters check` command in your CI pipeline to validate that `pathFilters` are correctly maintained:

```ps1
nbgv path-filters check
if ($LASTEXITCODE -ne 0) {
Write-Error "Path filters are out of date. Run 'nbgv path-filters update' locally."
exit 1
}
```

This ensures that developers keep path filters in sync with project structure changes.
12 changes: 10 additions & 2 deletions src/NerdBank.GitVersioning/GitContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,13 @@ public static GitContext Create(string path, string? committish = null, Engine e
}
}

/// <summary>
/// Determines whether a file would be ignored by git based on common .gitignore patterns.
/// </summary>
/// <param name="path">The absolute file path to check.</param>
/// <returns>True if the file is ignored by git; false otherwise.</returns>
public virtual bool IsIgnored(string path) => false;

/// <inheritdoc />
public void Dispose()
{
Expand Down Expand Up @@ -294,7 +301,7 @@ internal static bool TryFindGitPaths(string? path, [NotNullWhen(true)] out strin

internal abstract Version GetIdAsVersion(VersionOptions? committedVersion, VersionOptions? workingVersion, int versionHeight);

internal string GetRepoRelativePath(string absolutePath)
internal string GetRepoRelativePath(string absolutePath, bool replaceBackslashes = false)
{
string? repoRoot = this.WorkingTreePath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);

Expand All @@ -303,8 +310,9 @@ internal string GetRepoRelativePath(string absolutePath)
throw new ArgumentException($"Path '{absolutePath}' is not within repository '{repoRoot}'", nameof(absolutePath));
}

return absolutePath.Substring(repoRoot.Length)
string result = absolutePath.Substring(repoRoot.Length)
.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
return replaceBackslashes ? result.Replace('\\', '/') : result;
}

/// <summary>
Expand Down
3 changes: 3 additions & 0 deletions src/NerdBank.GitVersioning/LibGit2/LibGit2Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ public static LibGit2Context Create(string path, string? committish = null)
};
}

/// <inheritdoc />
public override bool IsIgnored(string path) => this.Repository.Ignore.IsPathIgnored(this.GetRepoRelativePath(path, replaceBackslashes: true));

/// <inheritdoc />
public override void ApplyTag(string name) => this.Repository.Tags.Add(name, this.Commit);

Expand Down
Loading
Loading