diff --git a/.docs/Get-VSTeamStaleBranch.md b/.docs/Get-VSTeamStaleBranch.md
new file mode 100644
index 000000000..857023eb2
--- /dev/null
+++ b/.docs/Get-VSTeamStaleBranch.md
@@ -0,0 +1,63 @@
+
+
+# Get-VSTeamStaleBranch
+
+## SYNOPSIS
+
+
+
+## SYNTAX
+
+## DESCRIPTION
+
+Retrieve Stale Branches
+
+You must call Set-VSTeamAccount before calling this function.
+
+## EXAMPLES
+
+### -------------------------- EXAMPLE 1 --------------------------
+
+```PowerShell
+PS C:\> Get-VSTeamStaleBranch
+```
+
+This will return all branches that have not been committed to within 90 days (default value)
+
+### -------------------------- EXAMPLE 2 --------------------------
+
+```PowerShell
+PS C:\> Get-VSTeamStaleBranch -top 5 | Format-Wide
+```
+
+This will return the top five Process Templates only showing their name
+
+## PARAMETERS
+
+
+
+### -RepositoryId
+
+Specifies the Repository Id to process
+
+```yaml
+Type: Guid
+Parameter Sets: ByRepositoryId
+```
+
+### -MaximumAgeDays
+
+The maximum number of days a branch has not been committed to rending it "stale"
+
+```yaml
+Type: Int32
+Default value: 90
+```
+
+## INPUTS
+
+## OUTPUTS
+
+## NOTES
+
+## RELATED LINKS
diff --git a/.docs/synopsis/Get-VSTeamStaleBranch.md b/.docs/synopsis/Get-VSTeamStaleBranch.md
new file mode 100644
index 000000000..eb176d6e8
--- /dev/null
+++ b/.docs/synopsis/Get-VSTeamStaleBranch.md
@@ -0,0 +1 @@
+Retrieve Stale Branches
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b2118eae4..98c803642 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,11 @@
## 6.4.5
-All unit test now pass consistently.
+Merged [Pull Request](https://github.com/DarqueWarrior/vsteam/pull/269) from [Michel Zehnder](https://github.com/MichelZ) which included the following:
+
+- Add Get-VSTeamGitStaleBranch to retrieve branches which have not been committed to recently (default: 90 days)
+
+- All unit test now pass consistently.
Merged [Pull Request](https://github.com/DarqueWarrior/vsteam/pull/265) from [Michel Zehnder](https://github.com/MichelZ) which included the following:
@@ -16,6 +20,7 @@ Merged [Pull Request](https://github.com/DarqueWarrior/vsteam/pull/273) from [Lu
- Adds a new function Update-VSTeamAgent which allows to update the agent version
+
## 6.4.4
Merged [Pull Request](https://github.com/DarqueWarrior/vsteam/pull/257) from [Michel Zehnder](https://github.com/MichelZ) which included the following:
diff --git a/Source/Classes/VSTeamGitCommitRef.ps1 b/Source/Classes/VSTeamGitCommitRef.ps1
index 01b1f5cae..bd99445cb 100644
--- a/Source/Classes/VSTeamGitCommitRef.ps1
+++ b/Source/Classes/VSTeamGitCommitRef.ps1
@@ -15,7 +15,11 @@ class VSTeamGitCommitRef : VSTeamLeaf {
$this.Committer = [VSTeamGitUserDate]::new($obj.committer, $ProjectName)
$this.CommitId = $obj.commitId
$this.Comment = $obj.comment
- $this.RemoteUrl = $obj.remoteUrl
+
+ if ($obj.PSobject.Properties.Name -contains "remoteurl") {
+ $this.RemoteUrl = $obj.remoteUrl
+ }
+
$this.Url = $obj.url
$this._internalObj = $obj
diff --git a/Source/Public/Get-VSTeamStaleBranch.ps1 b/Source/Public/Get-VSTeamStaleBranch.ps1
new file mode 100644
index 000000000..4997f431b
--- /dev/null
+++ b/Source/Public/Get-VSTeamStaleBranch.ps1
@@ -0,0 +1,89 @@
+function Get-VSTeamStaleBranch {
+ [CmdletBinding(DefaultParameterSetName="ByProjectId")]
+ param (
+ [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true, ParameterSetName = "ByRepositoryId")]
+ [Alias("Id")]
+ [guid] $RepositoryId,
+
+ [int] $MaximumAgeDays = 90
+ )
+
+ DynamicParam {
+ try { [void] $RepositoryId }
+ catch { $RepositoryId = $null }
+
+ if ($null -ne $RepositoryId) {
+ $dynamic = _buildProjectNameDynamicParam -ParameterSetName "ByRepositoryId" -Mandatory $true
+ } else {
+ $dynamic = _buildProjectNameDynamicParam -ParameterSetName "ByProjectId" -Mandatory $false
+ }
+
+ $dynamic
+ }
+
+ process {
+ # Bind the parameter to a friendly variable
+ $ProjectName = $PSBoundParameters["ProjectName"]
+ $maximumAge = (Get-Date).AddDays(-$MaximumAgeDays)
+
+ try {
+ if ($RepositoryId) # Retrieve single repository
+ {
+ Write-Verbose "Retrieving Branches for Repository ID $RepositoryId in Project $ProjectName"
+ $repository = Get-VSTeamGitRepository -ProjectName $ProjectName -RepositoryId $RepositoryId
+ $branches = $repository | Get-VSTeamGitRef -Filter "heads"
+ foreach ($branch in $branches)
+ {
+ Write-Verbose "Processing Branch $($branch.RefName)"
+ $isStale = ((Get-VSTeamGitCommit -ProjectName $ProjectName -RepositoryId $RepositoryId -FromDate $maximumAge -Top 1) | Measure-Object).Count -ne 1
+ if ($isStale)
+ {
+ Write-Verbose "Branch $($branch.RefName) is stale!"
+ $branchStats = Get-VSTeamGitStat -ProjectName $ProjectName -RepositoryId $RepositoryId -BranchName ($branch.RefName -replace 'refs/heads/', '')
+
+ $object =
+ [PSCustomObject]@{
+ ProjectName = $ProjectName
+ RepositoryName = $repository.Name
+ BranchName = ($branch.RefName -replace 'refs/heads/', '')
+ Creator = $branch.Creator
+ LastCommitId = $branchStats.commit.commitId
+ LastCommitter = $branchStats.commit.committer.name
+ LastCommitDate = $branchStats.commit.committer.date
+ Ahead = $branchStats.aheadCount
+ Behind = $branchStats.behindCount
+ }
+
+ _applyTypes $object "Team.GitStaleBranch"
+ Write-Output $object
+ }
+ }
+ } elseif ($ProjectName) { # Retrieve whole project (recursive)
+ Write-Verbose "Retrieving Repositories for Project $ProjectName"
+ $repos = Get-VSTeamGitRepository -ProjectName $ProjectName
+ foreach ($repo in $repos)
+ {
+ $staleBranchesResult = Get-VSTeamStaleBranch -ProjectName $ProjectName -RepositoryId $repo.Id -MaximumAgeDays $MaximumAgeDays
+ foreach ($result in $staleBranchesResult)
+ {
+ Write-Output $result
+ }
+ }
+ } else { # Retrieve all projects (recursive)
+ Write-Verbose "Retrieving all Projects"
+ $projects = Get-VSTeamProject
+ foreach ($project in $projects)
+ {
+ $staleBranchesResult = Get-VSTeamStaleBranch -ProjectName $project.Name -MaximumAgeDays $MaximumAgeDays
+ foreach ($result in $staleBranchesResult)
+ {
+ Write-Output $result
+ }
+ }
+ }
+ }
+ catch {
+ throw $_
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/formats/Team.GitStaleBranch.ListView.ps1xml b/Source/formats/Team.GitStaleBranch.ListView.ps1xml
new file mode 100644
index 000000000..02f7b91ce
--- /dev/null
+++ b/Source/formats/Team.GitStaleBranch.ListView.ps1xml
@@ -0,0 +1,46 @@
+
+
+
+
+ Team.GitStaleBranch.ListView
+
+ Team.GitStaleBranch
+
+
+
+
+
+
+ ProjectName
+
+
+ RepositoryName
+
+
+ BranchName
+
+
+ Creator
+
+
+ LastCommitId
+
+
+ LastCommitter
+
+
+ LastCommitDate
+
+
+ Behind
+
+
+ Ahead
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/formats/Team.GitStaleBranch.TableView.ps1xml b/Source/formats/Team.GitStaleBranch.TableView.ps1xml
new file mode 100644
index 000000000..b33edec9b
--- /dev/null
+++ b/Source/formats/Team.GitStaleBranch.TableView.ps1xml
@@ -0,0 +1,75 @@
+
+
+
+
+ Team.GitStaleBranch.TableView
+
+ Team.GitStaleBranch
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ProjectName
+
+
+ RepositoryName
+
+
+ BranchName
+
+
+ Creator
+
+
+ LastCommitId
+
+
+ LastCommitter
+
+
+ LastCommitDate
+
+
+ Ahead
+
+
+ Behind
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/formats/_formats.json b/Source/formats/_formats.json
index 9cd65634f..d53b10489 100644
--- a/Source/formats/_formats.json
+++ b/Source/formats/_formats.json
@@ -1,7 +1,7 @@
-{
- "outputFile": "vsteam.format.ps1xml",
- "fileType": "formats",
- "files": [
- "*.ps1xml"
- ]
+{
+ "outputFile": "vsteam.format.ps1xml",
+ "fileType": "formats",
+ "files": [
+ "*.ps1xml"
+ ]
}
\ No newline at end of file
diff --git a/Source/types/Team.GitStaleBranch.ps1xml b/Source/types/Team.GitStaleBranch.ps1xml
new file mode 100644
index 000000000..feb631234
--- /dev/null
+++ b/Source/types/Team.GitStaleBranch.ps1xml
@@ -0,0 +1,27 @@
+
+
+
+ Team.GitStaleBranch
+
+
+ PSStandardMembers
+
+
+ DefaultDisplayPropertySet
+
+ ProjectName
+ RepositoryName
+ BranchName
+ Creator
+ LastCommitId
+ LastCommitter
+ LastCommitDate
+ Ahead
+ Behind
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/unit/test/Get-VSTeamStaleBranch.Tests.ps1 b/unit/test/Get-VSTeamStaleBranch.Tests.ps1
new file mode 100644
index 000000000..2cad11631
--- /dev/null
+++ b/unit/test/Get-VSTeamStaleBranch.Tests.ps1
@@ -0,0 +1,168 @@
+Set-StrictMode -Version Latest
+
+InModuleScope VSTeam {
+
+ # Set the account to use for testing. A normal user would do this
+ # using the Set-VSTeamAccount function.
+ [VSTeamVersions]::Account = 'https://dev.azure.com/test'
+
+ $results = [PSCustomObject]@{
+ Branch = [PSCustomObject]@{
+ objectId = '6f365a7143e492e911c341451a734401bcacadfd'
+ name = 'refs/heads/master'
+ creator = [PSCustomObject]@{
+ displayName = 'Microsoft.VisualStudio.Services.TFS'
+ id = '1'
+ uniqueName = 'some@email.com'
+ }
+ }
+ Creator = [PSCustomObject]@{
+ displayName = 'Microsoft.VisualStudio.Services.TFS'
+ id = '1'
+ uniqueName = 'some@email.com'
+ }
+ CreationDate = "1010101"
+ ProjectName = "Test"
+ Repository = "repo"
+ LastCommit = "Block-SmbShareAccess"
+ LastCommitter = [PSCustomObject]@{
+ displayName = 'Microsoft.VisualStudio.Services.TFS'
+ id = '1'
+ uniqueName = 'some@email.com'
+ }
+ }
+
+ $singleRepository = [VSTeamGitRepository]::new([PSCustomObject]@{
+ id = '00000000-0000-0000-0000-000000000000'
+ url = ''
+ sshUrl = ''
+ remoteUrl = ''
+ defaultBranch = ''
+ size = 0
+ name = 'TestRepo'
+ project = [PSCustomObject]@{
+ name = 'Project'
+ id = 1
+ description = ''
+ url = ''
+ state = ''
+ revision = ''
+ visibility = ''
+ }
+ }, "Project")
+
+ $refs = @([VSTeamRef]::new([PSCustomObject]@{
+ objectId = '6f365a7143e492e911c341451a734401bcacadfd'
+ name = 'refs/heads/master'
+ creator = [PSCustomObject]@{
+ displayName = 'Microsoft.VisualStudio.Services.TFS'
+ id = '1'
+ uniqueName = 'some@email.com'
+ }
+ }, "Project"))
+
+ $commits = @(
+ [VSTeamGitCommitRef]::new(
+ [PSCustomObject]@{
+ author = [PSCustomObject]@{
+ date = '2019-02-19T15:12:01Z'
+ email = 'test@test.com'
+ name = 'Test User'
+ }
+ changeCounts = [PSCustomObject]@{
+ Add = 2
+ Delete = 0
+ Edit = 1
+ }
+ comment = 'Just a test commit'
+ commitId = '1234567890abcdef1234567890abcdef'
+ committer = [PSCustomObject]@{
+ date = '2019-02-19T15:12:01Z'
+ email = 'test@test.com'
+ name = 'Test User'
+ }
+ remoteUrl = 'https://dev.azure.com/test/test/_git/test/commit/1234567890abcdef1234567890abcdef'
+ url = 'https://dev.azure.com/test/21AF684D-AFFB-4F9A-9D49-866EF24D6A4A/_apid/git/repositories/06E176BE-D3D2-41C2-AB34-5F4D79AEC86B/commits/1234567890abcdef1234567890abcdef'
+ }, "Project"),
+ [VSTeamGitCommitRef]::new(
+ [PSCustomObject]@{
+ author = [PSCustomObject]@{
+ date = '2019-02-20T01:00:01Z'
+ email = 'eample@example.com'
+ name = 'Example User'
+ }
+ changeCounts = [PSCustomObject]@{
+ Add = 8
+ Delete = 1
+ Edit = 0
+ }
+ comment = 'Just another test commit'
+ commitId = 'abcdef1234567890abcdef1234567890'
+ committer = [PSCustomObject]@{
+ date = '2019-02-20T01:00:01Z'
+ email = 'eample@example.com'
+ name = 'Example User'
+ }
+ remoteUrl = 'https://dev.azure.com/test/test/_git/test/commit/abcdef1234567890abcdef1234567890'
+ url = 'https://dev.azure.com/test/21AF684D-AFFB-4F9A-9D49-866EF24D6A4A/_apid/git/repositories/06E176BE-D3D2-41C2-AB34-5F4D79AEC86B/commits/abcdef1234567890abcdef1234567890'
+ }, "Project")
+ )
+
+ $stats = [PSCustomObject]@{
+ commit = [PSCustomObject]@{
+ commitId = '67cae2b029dff7eb3dc062b49403aaedca5bad8d'
+ author = [PSCustomObject]@{
+ name = '"Chuck Reinhart'
+ email = 'fabrikamfiber3@hotmail.com'
+ date = '2014-01-29T23:52:56Z'
+ }
+ committer = [PSCustomObject]@{
+ name = '"Chuck Reinhart'
+ email = 'fabrikamfiber3@hotmail.com'
+ date = '2014-01-29T23:52:56Z'
+ }
+ comment = 'home page'
+ url = 'https://dev.azure.com/fabrikam/_apis/git/repositories/278d5cd2-584d-4b63-824a-2ba458937249/commits/67cae2b029dff7eb3dc062b49403aaedca5bad8d'
+ }
+ name = 'develop'
+ aheadCount = 1
+ behindCount = 17
+ isBaseVersion = $false
+ }
+
+ _applyTypes $stats "VSTeam.GitStat"
+
+ Describe "Git VSTS" {
+ # Mock the call to Get-Projects by the dynamic parameter for ProjectName
+ Mock Invoke-RestMethod { return @() } -ParameterFilter {
+ $Uri -like "*_apis/projects*"
+ }
+
+ . "$PSScriptRoot\mocks\mockProjectNameDynamicParam.ps1"
+
+ Context 'Get-VSTeamStaleBranch' {
+ Mock Get-VSTeamGitRepository { return $singleRepository } -ParameterFilter { $Id -eq "00000000-0000-0000-0000-000000000000" } -Verifiable
+ Mock Get-VSTeamGitRef { return $refs } -Verifiable
+ Mock Get-VSTeamGitCommit { return $commits } -Verifiable
+ Mock Get-VSTeamGitStat { return $stats } -Verifiable
+
+ $staleBranches = Get-VSTeamStaleBranch -ProjectName Test -RepositoryId 00000000-0000-0000-0000-000000000000
+
+ It 'Should return stale branch' {
+ Assert-VerifiableMock
+ }
+
+ It 'Should return 1 stale branch' {
+ $staleBranches | Should -HaveCount 1
+ }
+ }
+
+ Context 'Get-VSTeamStaleBranch by id throws' {
+ Mock Invoke-RestMethod { throw [System.Net.WebException] "Test Exception." }
+
+ It 'Should return a single repo by id' {
+ { Get-VSTeamStaleBranch -ProjectName Test -RepositoryId 00000000-0000-0000-0000-000000000000 } | Should Throw
+ }
+ }
+ }
+}
\ No newline at end of file