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
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI

on:
push:
branches: [master]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: Build and test
runs-on: windows-latest
steps:
- uses: actions/checkout@v7

- uses: microsoft/setup-msbuild@v3

- name: Build
run: msbuild SourceControlSwitcher.slnx -restore -p:Configuration=Release -p:DeployExtension=false -v:minimal

- name: Test
run: dotnet test SourceControlSwitcher.Tests/SourceControlSwitcher.Tests.csproj -c Release --no-build --logger "console;verbosity=normal"

- name: Upload VSIX
uses: actions/upload-artifact@v7
with:
name: SourceControlSwitcher-vsix
path: target/Release/SourceControlSwitcher.vsix
if-no-files-found: error
116 changes: 116 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: Release

# Publishes a new version when a tag vX.Y.Z is pushed:
# 1. checks that the tag matches the version in the manifest, AssemblyInfo and package registration;
# 2. builds and tests;
# 3. publishes the VSIX to the Visual Studio Marketplace;
# 4. creates the GitHub release with the VSIX and the CHANGELOG section as notes.
# Run it manually (workflow_dispatch) for a dry run: steps 1-2 only, VSIX uploaded as an artifact.
#
# Secrets:
# VS_MARKETPLACE_PAT Azure DevOps personal access token, scope "Marketplace (Manage)", owned by an account
# that is a member of the "Ryadel" Marketplace publisher. Required to publish.
# SNK_BASE64 Optional: the strong-name key (Key.snk) encoded in base64. Without it the build
# generates a new key on every run.

on:
push:
tags: ['v*.*.*']
workflow_dispatch:

permissions:
contents: write
Comment on lines +21 to +22

concurrency:
group: release
cancel-in-progress: false

jobs:
release:
name: Build and publish
runs-on: windows-latest
# Protect the environment in Settings > Environments to require an approval before publishing.
environment: marketplace
Comment on lines +32 to +33
env:
SNK_BASE64: ${{ secrets.SNK_BASE64 }}
PUBLISH: ${{ github.event_name == 'push' && github.ref_type == 'tag' }}
steps:
- uses: actions/checkout@v7

- uses: microsoft/setup-msbuild@v3

- name: Check version
id: version
shell: pwsh
run: |
[xml]$manifest = Get-Content 'SourceControlSwitcher/Manifest/source.extension.vsixmanifest'
$version = $manifest.PackageManifest.Metadata.Identity.Version
$assembly = (Select-String -Path 'SourceControlSwitcher/Properties/AssemblyInfo.cs' -Pattern 'AssemblyVersion\("([^"]+)"\)').Matches[0].Groups[1].Value
$registration = (Select-String -Path 'SourceControlSwitcher/SourceControlSwitcherPackage.cs' -Pattern 'InstalledProductRegistration\("#110", "#112", "([^"]+)"\)').Matches[0].Groups[1].Value
Write-Host "Manifest: $version AssemblyVersion: $assembly InstalledProductRegistration: $registration"
if ($assembly -ne "$version.0") { throw "AssemblyVersion $assembly does not match manifest version $version" }
if ($registration -ne $version) { throw "InstalledProductRegistration $registration does not match manifest version $version" }
if ($env:PUBLISH -eq 'true' -and $env:GITHUB_REF_NAME -ne "v$version") { throw "Tag $env:GITHUB_REF_NAME does not match manifest version v$version" }
if (-not (Select-String -Path 'CHANGELOG.txt' -SimpleMatch "SourceControlSwitcher - v$version" -Quiet)) { throw "CHANGELOG.txt has no section for v$version" }
"version=$version" >> $env:GITHUB_OUTPUT

- name: Restore strong-name key
if: env.SNK_BASE64 != ''
shell: pwsh
run: '[IO.File]::WriteAllBytes("$PWD/Key.snk", [Convert]::FromBase64String($env:SNK_BASE64))'

- name: Build
run: msbuild SourceControlSwitcher.slnx -restore -p:Configuration=Release -p:DeployExtension=false -v:minimal

- name: Test
run: dotnet test SourceControlSwitcher.Tests/SourceControlSwitcher.Tests.csproj -c Release --no-build

- name: Release notes from CHANGELOG
shell: pwsh
run: |
$lines = Get-Content 'CHANGELOG.txt'
$start = [Array]::FindIndex($lines, [Predicate[string]]{ param($l) $l -eq "SourceControlSwitcher - v${{ steps.version.outputs.version }}" })
$end = [Array]::FindIndex($lines, $start + 1, [Predicate[string]]{ param($l) $l -like 'SourceControlSwitcher - v*' })
if ($end -lt 0) { $end = $lines.Length }
$lines[($start + 2)..($end - 1)] | Set-Content -Encoding utf8 'release-notes.md'
Get-Content 'release-notes.md'

- name: Upload VSIX
uses: actions/upload-artifact@v7
with:
name: SourceControlSwitcher-${{ steps.version.outputs.version }}-vsix
path: target/Release/SourceControlSwitcher.vsix
if-no-files-found: error

- name: Publish to the Visual Studio Marketplace
if: env.PUBLISH == 'true'
shell: pwsh
env:
VS_MARKETPLACE_PAT: ${{ secrets.VS_MARKETPLACE_PAT }}
run: |
if (-not $env:VS_MARKETPLACE_PAT) { throw 'Secret VS_MARKETPLACE_PAT is not set' }
# VsixPublisher ships with the Microsoft.VSSDK.BuildTools package restored by the build.
$publisher = Get-ChildItem "$env:USERPROFILE\.nuget\packages\microsoft.vssdk.buildtools" -Recurse -Filter VsixPublisher.exe |
Where-Object { $_.Directory.Name -eq 'bin' } | Sort-Object FullName -Descending | Select-Object -First 1
if (-not $publisher) { throw 'VsixPublisher.exe not found' }
& $publisher.FullName publish `
-payload 'target/Release/SourceControlSwitcher.vsix' `
-publishManifest 'SourceControlSwitcher/Manifest/publishManifest.json' `
-personalAccessToken $env:VS_MARKETPLACE_PAT
if ($LASTEXITCODE -ne 0) { throw "VsixPublisher failed with exit code $LASTEXITCODE" }

- name: Create GitHub release
if: env.PUBLISH == 'true'
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
$tag = $env:GITHUB_REF_NAME
gh release view $tag *> $null
if ($LASTEXITCODE -eq 0) {
gh release upload $tag 'target/Release/SourceControlSwitcher.vsix' --clobber
} else {
gh release create $tag 'target/Release/SourceControlSwitcher.vsix' --verify-tag `
--title "Source Control Switcher ${{ steps.version.outputs.version }}" --notes-file 'release-notes.md'
}
if ($LASTEXITCODE -ne 0) { throw "gh release failed with exit code $LASTEXITCODE" }
2 changes: 2 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ SourceControlSwitcher - v1.2.0
* Removed clients that are no longer maintained: Easy Git Integration Tools / Git Source Control Provider,
VisualHG, VSHG, HgSccPackage (Mercurial support).
* Solution migrated to .slnx; unit tests added.
* Icon redrawn from the vector logo; Getting Started link in the extension details. Builds, tests and releases now run
on GitHub Actions (CI on every push and pull request; tag vX.Y.Z publishes to the Marketplace).

SourceControlSwitcher - v1.1.2
------------------------------------------
Expand Down
152 changes: 111 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,129 @@
# Source Control Switcher
<p align="center">
<img src="https://raw.githubusercontent.com/Ryadel/SourceControlSwitcher/master/SourceControlSwitcher/Resources/Icon.png" alt="Source Control Switcher" width="128" height="128" />
</p>

![Source Control Switcher Logo](https://www.ryadel.com/wp-content/uploads/2019/10/Source-Control-Switcher-logo-368x200.png)
<h1 align="center">Source Control Switcher</h1>

Visual Studio can only have one source control provider active at a time, and it does not pick one per
repository. **Source Control Switcher** does: when you open a solution or a folder, it detects which kind of
repository it belongs to and activates the matching source control provider.
<p align="center">
Automatically activates the right source control provider in Visual Studio for every solution you open.
</p>

| Repository | Detected by | Providers |
<p align="center">
<a href="https://marketplace.visualstudio.com/items?itemName=Ryadel.SourceControlSwitcher"><img src="https://vsmarketplacebadges.dev/version-short/Ryadel.SourceControlSwitcher.svg" alt="Visual Studio Marketplace version" /></a>
<a href="https://marketplace.visualstudio.com/items?itemName=Ryadel.SourceControlSwitcher"><img src="https://vsmarketplacebadges.dev/installs-short/Ryadel.SourceControlSwitcher.svg" alt="Installs" /></a>
<a href="https://github.com/Ryadel/SourceControlSwitcher/actions/workflows/ci.yml"><img src="https://github.com/Ryadel/SourceControlSwitcher/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
<a href="https://github.com/Ryadel/SourceControlSwitcher/blob/master/LICENSE.txt"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /></a>
</p>

---

## Why

Visual Studio can have only **one** source control provider active at a time, and it does not choose one per
repository. If you work with Git in one solution and Subversion, TFVC or Perforce in another, you end up changing
*Tools > Options > Source Control > Plug-in Selection* by hand every time.

**Source Control Switcher does it for you**: when a solution or folder opens, it detects which kind of repository
it belongs to and activates the matching provider.

## Supported repositories

| Repository | Detected by | Provider activated |
|---|---|---|
| Git | a `.git` folder (or file, for worktrees and submodules) in the solution folder or a parent | Git (Microsoft Git provider, built into Visual Studio) |
| Subversion | a `.svn` folder in the solution folder or a parent | VisualSVN, AnkhSVN |
| Team Foundation Version Control | TFVC bindings in the `.sln` file, or a TFVC local workspace (`$tf`) | Visual Studio Team Foundation Server (built in) |
| Perforce | `p4 where` succeeds in the solution folder (only when `p4.exe` is on PATH) | P4VS |
| Anything else | a file or folder name you choose | any source control provider, by GUID |
| **Git** | a `.git` folder, or a `.git` file (worktrees, submodules), in the solution folder or a parent | Git (Microsoft Git provider, built into Visual Studio) |
| **Subversion** | a `.svn` folder in the solution folder or a parent | VisualSVN or AnkhSVN |
| **Team Foundation (TFVC)** | TFVC bindings in the `.sln`/`.slnx`, or a TFVC local workspace (`$tf`) | Visual Studio Team Foundation Server (built in) |
| **Perforce** | `p4 where` succeeds in the solution folder (only when `p4.exe` is on PATH) | P4VS |
| **Anything else** | a file or folder name you choose | any source control provider, by GUID |

Works with solutions (`.sln`, `.slnx`) and with **Open Folder**.

## Requirements

- **Visual Studio 2022 or Visual Studio 2026**: Community, Professional or Enterprise, on x64 or Arm64.
- The source control client(s) you use: Git and TFVC are built into Visual Studio; for Subversion install
VisualSVN or AnkhSVN; for Perforce install P4VS.

Still on Visual Studio 2015–2019? Use version 1.1.2.

## Installation

In Visual Studio: **Extensions > Manage Extensions**, search for *Source Control Switcher*, install and restart.
Alternatively, download it from the
[Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=Ryadel.SourceControlSwitcher) or
from the [GitHub releases](https://github.com/Ryadel/SourceControlSwitcher/releases).

The extension supports **Visual Studio 2022 and Visual Studio 2026** (Community, Professional, Enterprise; x64 and
Arm64). Users of Visual Studio 2015–2019 can keep using version 1.1.2.
That's it: there is nothing to configure for the common cases.

## How it works

When a solution or folder opens, the extension looks for repository markers starting from the solution folder and
moving up to the drive root. The nearest marker wins; at each folder level the order is:

1. your custom markers (see [Custom providers](#custom-providers));
2. TFVC bindings in the solution file (solution folder only);
3. `.git`, then `.svn`, then `$tf`.

If nothing is found and Perforce detection is enabled, it asks the Perforce client in the background. When a
repository is found, the provider configured for it is activated; when nothing is found, the current provider is
left unchanged.

## Options

*Tools* > *Options* > *Source Control Switcher* > *General*:
**Tools > Options > Source Control Switcher > General**

- **General**
- *Switch automatically* — switch when a solution or folder opens. When off, switch on demand with
*Tools* > *Detect Source Control Provider*.
- *Show status bar notifications* — a status bar message whenever the provider changes.
- *Logging* — detail written to the *Source Control Switcher* pane of the Output window (Off, Normal, Verbose).
- **Providers** — for Git, Subversion, TFVC and Perforce: *Automatic* (first supported provider installed), a
specific provider, or *Disabled* (leave the provider alone for that repository type).
- **Detection**
- *Search parent folders* — look for markers in the solution folder and all its parents.
- *Detect TFVC bindings in .sln files*.
- *Detect Perforce* and *Perforce timeout (seconds)* — the Perforce check runs in the background.
- **Custom providers** — `marker=providerGuid` rules separated by `;`. When the marker (a file or folder name)
is found, the provider with that GUID is activated. Custom rules are checked before the built-in ones.
| Category | Option | Default | Description |
|---|---|---|---|
| General | Switch automatically | On | Switch when a solution or folder opens. When off, use the *Detect Source Control Provider* command. |
| General | Show status bar notifications | On | Show a message in the status bar when the provider changes. |
| General | Logging | Normal | Detail written to the *Source Control Switcher* pane of the Output window: Off, Normal, Verbose. |
| Providers | Git / Subversion / Team Foundation / Perforce | Automatic | *Automatic* picks the first supported provider installed; you can force a specific one, or choose *Disabled* to leave the provider alone for that repository type. |
| Detection | Search parent folders | On | Look for markers in the solution folder and all its parents. |
| Detection | Detect TFVC bindings in .sln files | On | Turn off for repositories migrated from TFVC that still carry old bindings. |
| Detection | Detect Perforce | On | Ask `p4 where` when no other marker is found (background, only if `p4.exe` is on PATH). |
| Detection | Perforce timeout (seconds) | 5 | Maximum wait for the Perforce client (1–60). |
| Custom providers | Custom providers | *(empty)* | `marker=providerGuid` rules, separated by `;`. |

## Commands
## Command

- *Tools* > *Detect Source Control Provider* — re-runs detection for the open solution or folder and activates the
matching provider, for example after changing the provider by hand.
**Tools > Detect Source Control Provider** runs the detection again for the open solution or folder and activates
the matching provider. Use it after changing the provider by hand, or to switch on demand with automatic switching
turned off.

## Building
## Custom providers

See [BUILD.txt](BUILD.txt).
For a source control client the extension does not know, add a rule mapping a marker to the client's
**provider GUID**, for example:

## Credits
```text
.vault=01234567-89ab-cdef-0123-456789abcdef
```

When `.vault` (a file or a folder) is found, that provider is activated. To find a provider's GUID, look for
`SourceControlProviders\{GUID}` in the `.pkgdef` files of the client's extension folder.

This extension is based on [SccAutoSwitcher](https://github.com/ceztko/SccAutoSwitcher) by *Francesco Pretto*.
Thanks to everyone who contributed fixes and ideas, including *QianMoXi* (start-up with a solution),
*joe-kr* (Visual Studio 2022 support, AnkhSVN 2019) and *davideciarmiello* (TFVC support).
## Troubleshooting

Set **Logging** to *Verbose* and open **View > Output**, then pick *Source Control Switcher* in the
*Show output from* list: every detection step and its outcome is logged there. Messages are also written to the
Visual Studio activity log when Visual Studio is started with `/log`.

Found a bug or have an idea? [Open an issue](https://github.com/Ryadel/SourceControlSwitcher/issues).

## Building from source

Open `SourceControlSwitcher.slnx` in Visual Studio 2022 (17.14+) or 2026 and build; press F5 to debug in the
experimental instance. Details in
[BUILD.txt](https://github.com/Ryadel/SourceControlSwitcher/blob/master/BUILD.txt). Release notes are in the
[CHANGELOG](https://github.com/Ryadel/SourceControlSwitcher/blob/master/CHANGELOG.txt).

## Credits

License is MIT.
Based on [SccAutoSwitcher](https://github.com/ceztko/SccAutoSwitcher) by **Francesco Pretto**.
Thanks to everyone who contributed fixes and ideas, including **@QianMoXi** (start-up with a solution),
**@joe-kr** (Visual Studio 2022 support, AnkhSVN 2019) and **@davideciarmiello** (TFVC support).

## Useful References
## License

* [**Source Control Switcher** on GitHub](https://github.com/Ryadel/SourceControlSwitcher/)
* [**Source Control Switcher** on Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=Ryadel.SourceControlSwitcher)
* [**Source Control Switcher** official page](https://www.ryadel.com/en/portfolio/source-control-switcher/)
[MIT](https://github.com/Ryadel/SourceControlSwitcher/blob/master/LICENSE.txt) — see also the
[official page](https://www.ryadel.com/en/portfolio/source-control-switcher/) on ryadel.com.
13 changes: 13 additions & 0 deletions SourceControlSwitcher/Manifest/publishManifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://json.schemastore.org/vsix-publish",
"categories": [ "source control", "team development" ],
"identity": {
"internalName": "SourceControlSwitcher"
},
"overview": "../../README.md",
"priceCategory": "free",
"publisher": "Ryadel",
"private": false,
"qna": true,
"repo": "https://github.com/Ryadel/SourceControlSwitcher"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
<Description xml:space="preserve">Automatically sets the Source Control Provider according to the one used by the current Visual Studio solution: Git, Subversion, TFVC or Perforce.</Description>
<MoreInfo>https://github.com/Ryadel/SourceControlSwitcher</MoreInfo>
<License>Resources\LICENSE.txt</License>
<GettingStartedGuide>https://github.com/Ryadel/SourceControlSwitcher#readme</GettingStartedGuide>
<ReleaseNotes>Resources\CHANGELOG.txt</ReleaseNotes>
<Icon>Resources\Icon.png</Icon>
<PreviewImage>Resources\Preview.png</PreviewImage>
<Tags>Source Control, Source Code Control, Source Control Provider, Source Control Client, Git, SVN, Subversion, TFVC, Team Foundation, Perforce, Helix</Tags>
</Metadata>
<Installation InstalledByMsi="false">
Expand Down
Binary file modified SourceControlSwitcher/Resources/Icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed SourceControlSwitcher/Resources/Preview.png
Binary file not shown.
4 changes: 0 additions & 4 deletions SourceControlSwitcher/SourceControlSwitcher.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,6 @@
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<Resource Include="Resources\LICENSE_SCCAS.txt" />
<Content Include="Resources\Preview.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
<None Include="Manifest\source.extension.vsixmanifest">
<SubType>Designer</SubType>
</None>
Expand Down
Loading