Skip to content
This repository has been archived by the owner on Apr 15, 2020. It is now read-only.

Commit

Permalink
Add extension files
Browse files Browse the repository at this point in the history
  • Loading branch information
fakhrulhilal committed Apr 25, 2018
1 parent 6027007 commit 916ddba
Show file tree
Hide file tree
Showing 9 changed files with 365 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.vs/
*.vsix
198 changes: 198 additions & 0 deletions GitDownloader/GitDownloader.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
[CmdletBinding()]
param(
# repository name
[string]
[parameter(mandatory=$true)]
$RepositoryUrl,

# the root directory to store all git repositories
[string]
[parameter(mandatory=$false)]
$RepositoryPath,

# branch/tag name to checkout
[parameter(mandatory=$false)]
[string]
$BranchTag = 'master',

# determine whether to clean the folder before downloading the repository or not (default: false)
[parameter(mandatory=$false)]
[string]
[ValidateSet('true', 'false', 'yes', 'no')]
$Clean = 'false'
)

Function Get-CurrentBranch {
$branch = (git symbolic-ref -q --short HEAD)
If (-not ([string]::IsNullOrEmpty($branch)) -and ($branch -ne 'HEAD')) {
Return $branch
}
$tag = (git describe --tags --exact-match)
Return $tag
}

Function Invoke-VerboseCommand {
param(
[ScriptBlock]$Command,
[string] $StderrPrefix = "",
[int[]]$AllowedExitCodes = @(0)
)
$Script = $Command.ToString()
$Captures = Select-String '\$(\w+)' -Input $Script -AllMatches
ForEach ($Capture in $Captures.Matches) {
$Variable = $Capture.Groups[1].Value
$Value = Get-Variable -Name $Variable -ValueOnly
$Script = $Script.Replace("`$$($Variable)", $Value)
}
Write-Host $Script
If ($script:ErrorActionPreference -ne $null) {
$backupErrorActionPreference = $script:ErrorActionPreference
} ElseIf ($ErrorActionPreference -ne $null) {
$backupErrorActionPreference = $ErrorActionPreference
}
$script:ErrorActionPreference = "Continue"
try
{
& $Command 2>&1 | ForEach-Object -Process `
{
if ($_ -is [System.Management.Automation.ErrorRecord])
{
"$StderrPrefix$_"
}
else
{
"$_"
}
}
if ($AllowedExitCodes -notcontains $LASTEXITCODE)
{
throw "Execution failed with exit code $LASTEXITCODE"
}
}
finally
{
$script:ErrorActionPreference = $backupErrorActionPreference
}
}

Function Update-GitRepository {
param(
[string]$Path,
[string]$BranchTag
)

Write-Host "Updating repository inside $Path"
Set-Location $Path | Out-Null
$CurrentBranch = Get-CurrentBranch
Invoke-VerboseCommand -Command { git stash }
If ($CurrentBranch -ine $BranchTag) {
Write-Host "Undoing any pending changes in $Path"
Invoke-VerboseCommand -Command {
git checkout "$BranchTag"
git checkout -- .
git clean -fdx
}
}
Write-Host "Pulling update from branch/tag $BranchTag"
Invoke-VerboseCommand -Command { git config credential.interactive never }
# try to use token provided by TFS server
If (Test-SameTfsServer -Uri $Uri) {
$AuthHeader = "Authorization: bearer $($Env:SYSTEM_ACCESSTOKEN)"
Invoke-VerboseCommand -Command { git -c http.extraheader="$AuthHeader" pull origin "$BranchTag" }
}
Else {
Invoke-VerboseCommand -Command { git pull origin "$BranchTag" }
}
}

Function Start-CloneGitRepository {
param(
[string]$Uri,
[string]$BranchTag,
[string]$Path
)

Write-Host "Cloning $Uri for branch/tag '$BranchTag' into $Path"
New-Item -Path $Path -ItemType Directory -Force | Out-Null
Set-Location -Path $Path | Out-Null
Invoke-VerboseCommand -Command {
git init "$Path"
git config credential.interactive never
git remote add origin "$Uri"
}
# try to embed authentication from system token for same TFS server
If ((Test-SameTfsServer -Uri $Uri)) {
$AuthHeader = "Authorization: bearer $($Env:SYSTEM_ACCESSTOKEN)"
Invoke-VerboseCommand -Command { git -c http.extraheader="$AuthHeader" fetch --progress --prune origin "$BranchTag" }
}
Else {
Invoke-VerboseCommand -Command { git fetch --progress --prune origin "$BranchTag" }
}
If ($LastExitCode -ne 0) {
Write-Error $output -ErrorAction Stop
}
Invoke-VerboseCommand -Command { git checkout --progress --force "$BranchTag" }
}

Function Get-GitRepositoryUri {
$Uris = @($Env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI, $Env:SYSTEM_TEAMPROJECT, '_git') | %{ $_.Trim('/') }
Return $Uris -join '/'
}

Function Test-SameTfsServer {
param([string]$Uri)
$DefaultUri = $Env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI
# we don't care the protocol, either http or https
$DefaultUri = $DefaultUri -replace '^https?:', ''
$Uri = $Uri -replace '^https?:', ''
$escapedPattern = [regex]::Escape($DefaultUri)
Return $Uri -match "^($($escapedPattern))"
}

try {
# try to find in PATH environment
Get-Command -Name git -CommandType Application -ErrorAction Stop | Out-Null
} catch [System.Management.Automation.CommandNotFoundException] {
# try to find git in default location
If (-not (Test-Path -Path "$($env:ProgramFiles)\Git\bin\git.exe" -PathType Leaf)) {
Write-Error "Git command line not found or not installed" -ErrorAction Stop
}
Set-Alias -Name git -Value $env:ProgramFiles\Git\bin\git.exe -Force | Out-Null
}

$GitRepositoryUri = Get-GitRepositoryUri
Write-Host "##vso[task.setvariable variable=Build.Repository.GitUri]$GitRepositoryUri"
$RepositoryUrl = $RepositoryUrl -replace ([regex]::Escape('$(Build.Repository.GitUri)')), $GitRepositoryUri
$BuildDirectory = $Env:AGENT_BUILDDIRECTORY
$GitDirectory = [System.IO.Path]::Combine($BuildDirectory, 'git')
Write-Host "##vso[task.setvariable variable=Build.GitDirectory]$GitDirectory"
If ([string]::IsNullOrWhiteSpace($RepositoryPath)) {
# set default repository path
$RepositoryName = $RepositoryUrl.Substring($RepositoryUrl.TrimEnd('/').LastIndexOf('/') + 1)
$RepositoryPath = "`$(Build.GitDirectory)\$RepositoryName"
}
$RepositoryPath = $RepositoryPath -replace ([regex]::Escape('$(Build.GitDirectory)')), $GitDirectory

# ensure containing git folder exists
$RepositoryFolder = [System.IO.Path]::GetDirectoryName($RepositoryPath)
If (-not (Test-Path -Path "$RepositoryFolder" -PathType Container)) {
Write-Host "Creating git directory: $RepositoryFolder"
New-Item -Path "$RepositoryFolder" -ItemType Directory -Force | Out-Null
}

$CurrentDirectory = (Get-Location).Path
If (Test-Path -Path "$RepositoryPath" -PathType Container) {
If (@('true', 'yes').Contains($Clean.ToLower())) {
Write-Host "Cleaning directory $RepositoryPath"
Remove-Item -Path "$RepositoryPath" -Recurse -Force | Out-Null
Start-CloneGitRepository -Path "$RepositoryPath" -Uri "$RepositoryUrl" -BranchTag $BranchTag
}
Else {
Update-GitRepository -Path "$RepositoryPath" -BranchTag $BranchTag
}
}
Else {
Start-CloneGitRepository -Path "$RepositoryPath" -Uri "$RepositoryUrl" -BranchTag $BranchTag
}

Set-Location "$CurrentDirectory"
Binary file added GitDownloader/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions GitDownloader/task.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"id": "56033F91-AEB8-4316-B19A-BCB721F4705B",
"name": "GitDownloader",
"friendlyName": "Git Repository Downloader",
"description": "Download additional git repository from public repository or this TFS",
"helpMarkDown": "This task will download git repository as an addition to default source. Consider this task as workaround where TFS build with git repository can only download 1 repository. This task assumes that git is already installed in %ProgramFiles%\\Git within TFS build agent's machine.",
"category": "Utility",
"visibility": [
"Build",
"Release"
],
"runsOn": ["Agent"],
"author": "Fakhrulhilal Maktum",
"version": {
"Major": 0,
"Minor": 2,
"Patch": 8
},
"instanceNameFormat": "Fetch git: $(Repository)",
"groups": [
{
"name": "advanced",
"displayName": "Advanced",
"isExpanded": false
}
],
"inputs": [
{
"name": "RepositoryUrl",
"type": "string",
"label": "Repository URL",
"defaultValue": "",
"required": true,
"helpMarkDown": "Repository URL to download. Use $(Build.Repository.GitUri) to refer relative URI for git URL, ex: https://your-onpremise-tfs.com/tfs/DefaultCollection/TeamProject/_git/GitRepo or just $(Build.Repository.GitUri)/GitRepo. This repository should be able to be cloned without authentication."
},
{
"name": "RepositoryPath",
"type": "string",
"label": "Repository Path",
"defaultValue": "",
"required": false,
"helpMarkDown": "Full path to store the git repository. If not specified, then it will be located in $(Build.GitDirectory)\\[repo name]."
},
{
"name": "BranchTag",
"type": "string",
"label": "Branch/Tag",
"defaultValue": "master",
"required": true,
"helpMarkDown": "Branch/tag to checkout. By default, it will download single branch/tag and will checkout to that branch/tag automatically."
},
{
"name": "Clean",
"type": "boolean",
"label": "Clean",
"defaultValue": "false",
"required": false,
"helpMarkDown": "If this is true, will remove previously downloaded repository and use clone command to download. Otherwise, it will stash everything and pull the remote branch/tag."
}
],
"execution": {
"Powershell": {
"target": "GitDownloader.ps1"
}
}
}
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Git Downloader

This task will download git repository as an addition to default source. Consider this task as workaround where TFS build with git repository can only download 1 repository. Then this task will download another git repository required by your TFS build definition. This task assumes that git is already installed in %ProgramFiles%\\Git within TFS build agent's machine.

## Parameters

1. Repository URL

URL of git repository. The repository should be public that can be downloaded without authentication. If it's the same location of main repository (specified in `Get sources`/first task of the build), then it's automatically uses the same credential.

Use `$(Build.Repository.GitUri)` to refer relative URI for git URL, examples:
- https://your-onpremise-tfs.com/tfs/DefaultCollection/TeamProject/_git/GitRepo
- https://youraccount.visualstudio.com/TeamProject/_git/GitRepo
- $(Build.Repository.GitUri)/GitRepo

2. Repository Path

Full path to store the git repository. If not specified, then it will be located in $(Build.GitDirectory)\\[repo name].

3. Branch/tag

Branch/tag to checkout. By default, it will download single branch/tag and will checkout to that branch/tag automatically.

4. Clean

If this is true, it will remove previously downloaded repository and use `git clone` command to download. Otherwise, it will stash everything and pull the remote branch/tag.

![screenshot](images/task.jpg "Task")

By default, this task expects public git repository that can be downloaded without authentication. But when the git repository is located in same TFS server, than it requires OAuth token for successful download. You can enable it in **Options** tab and check for **Allow scripts to access OAuth token** like screenshot below:
![enable OAuth token](images/enable_oauth_token.jpg "Options")

The git URL is considered the same TFS server when it's located in the same URL as TFS server (ignoring HTTP protocol). Supposed we have TFS public URL http://yourdomain/, so these URLs are considered the same TFS server:

- https://yourdomain/tfs/DefaultCollection/TeamProject/_git/GitRepo
- http://yourdomain/tfs/DefaultCollection/_git/TeamProject
- https://yourdomain/tfs/DefaultCollection/AnotherTeamProject/_git/GitRepo
- https://yourdomain/tfs/DefaultCollection/_git/AnotherTeamProject
- $(Build.Repository.GitUri)/GitRepo

And these are considered as different TFS server (means should be downloadable without authentication):

- https://yourdomain.root.domain.com/tfs/DefaultCollection/TeamProject/_git/GitRepo
- http://yourdomain.root.domain.com/tfs/DefaultCollection/_git/TeamProject
- https://yourdomain.root.domain.com/tfs/DefaultCollection/AnotherTeamProject/_git/GitRepo
- https://yourdomain.root.domain.com/tfs/DefaultCollection/_git/AnotherTeamProject

This extension relies on [`$(System.TeamFoundationCollectionUri)`](
https://docs.microsoft.com/en-us/vsts/build-release/concepts/definitions/build/variables#systemteamfoundationcollectionuri) variable to check for same server. To ensure which domain your TFS server is running on, try to create dummy build definition and create one PowerShell task containing this code:
```PowerShell
Write-Host "$Env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"
```
Binary file added images/enable_oauth_token.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/logo.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 added images/task.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions vss-extension.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"manifestVersion": 1,
"id": "GitDownloader",
"name": "Git Repository Downloader",
"version": "0.2.8",
"publisher": "fakhrulhilal-maktum",
"targets": [
{
"id": "Microsoft.VisualStudio.Services"
}
],
"description": "Download additional git repository from public repository or this TFS",
"categories": [
"Build and release"
],
"tags": [
"build",
"git"
],
"icons": {
"default": "images/logo.png"
},
"content": {
"details": { "path": "README.md"}
},
"files": [
{
"path": "GitDownloader"
},
{
"path": "images",
"addressable": true
}
],
"contributions": [
{
"id": "git-downloader",
"type": "ms.vss-distributed-task.task",
"targets": [
"ms.vss-distributed-task.tasks"
],
"properties": {
"name": "GitDownloader"
}
}
]
}

0 comments on commit 916ddba

Please sign in to comment.