I was looking for a way to download my azure devops repository as zip/tar file using REST Api. There is a provision to download the zip using console but I need to do it using API.
Similar to Github Repository Archive Download (Link)
I was looking for a way to download my azure devops repository as zip/tar file using REST Api. There is a provision to download the zip using console but I need to do it using API.
Similar to Github Repository Archive Download (Link)
You can ⬇ Download as Zip by using the following link:
https://dev.azure.com/{organization}/{project}/_apis/git/repositories/{repository}/items/items?path=/&versionDescriptor[versionOptions]=0&versionDescriptor[versionType]=0&versionDescriptor[version]={branch}&resolveLfs=true&$format=zip&api-version=5.0&download=true
Replace {organization}, {project}, {repository} & {branch} with your values
Adding to @Ahmed Magdy's answer:
GET: https://dev.azure.com/{organization}/{project}/_apis/git/repositories/{repository}/items/items?path=/&versionDescriptor[version]={branch}&resolveLfs=true&$format=zip&download=true
By Trial & Error, I have found versionDescriptor[versionOptions], versionDescriptor[versionType] and api-version params are not required. Hence removed from the above URL.
An Authorization Header is required:
Authorization: Basic <TOKEN-STRING>
This is how to get TOKEN-STRING:
TOKEN-STRING = base64-encode(":" + personal-access-token)
If personal-access-token is "abcxyz", in JS form:
TOKEN-STRING = btoa(":abcxyz");
This is working as of November 2021
This is not supported out of the box to archive repo. But it is doable. Please check this
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage;
using Newtonsoft.Json;
using RestSharp;
namespace AzureDevopsBackupFunction
{
public static class BackupFunction
{
private const string version = "api-version=5.1";
[FunctionName("BackupFunction")]
public static async Task Run([TimerTrigger("0 0 20 * * *")]TimerInfo myTimer, ILogger log) //, RunOnStartup = true
{
log.LogInformation($"DevOps BackupFunction function starting execution at: {DateTime.Now}");
// configure connections
string storageAccountKey = Environment.GetEnvironmentVariable("storageAccountKey", EnvironmentVariableTarget.Process);
string storageName = Environment.GetEnvironmentVariable("storageName", EnvironmentVariableTarget.Process);
string token = Environment.GetEnvironmentVariable("token", EnvironmentVariableTarget.Process);
string organization = Environment.GetEnvironmentVariable("organization", EnvironmentVariableTarget.Process);
string storageConnection = $"DefaultEndpointsProtocol=https;AccountName={storageName};AccountKey={storageAccountKey};EndpointSuffix=core.windows.net";
string devopsURL = $"https://dev.azure.com/{organization}/";
// make API request to get all projects
string auth = "Basic " + Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(string.Format("{0}:{1}", "", token)));
var clientProjects = new RestClient($"{devopsURL}_apis/projects?{version}");
var requestProjects = new RestRequest(Method.GET);
requestProjects.AddHeader("Authorization", auth);
var responseProjects = clientProjects.Execute(requestProjects);
if(responseProjects.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new Exception("API Request failed: " + responseProjects.StatusCode + " " + responseProjects.ErrorMessage);
}
Projects projects = JsonConvert.DeserializeObject<Projects>(responseProjects.Content);
// connect to Azure Storage
var storageAccount = CloudStorageAccount.Parse(storageConnection);
var client = storageAccount.CreateCloudBlobClient();
var container = client.GetContainerReference("devopsbackup");
await container.CreateIfNotExistsAsync();
foreach (Project project in projects.value)
{
log.LogInformation(project.name);
// get repositories
var clientRepos = new RestClient($"{devopsURL}{project.name}/_apis/git/repositories?{version}");
var requestRepos = new RestRequest(Method.GET);
requestRepos.AddHeader("Authorization", auth);
var responseRepos = clientRepos.Execute(requestRepos);
Repos repos = JsonConvert.DeserializeObject<Repos>(responseRepos.Content);
foreach (Repo repo in repos.value)
{
log.LogInformation("Repo: " + repo.name);
// get file mapping
var clientItems = new RestClient($"{devopsURL}_apis/git/repositories/{repo.id}/items?recursionlevel=full&{version}");
var requestItems = new RestRequest(Method.GET);
requestItems.AddHeader("Authorization", auth);
var responseItems = clientItems.Execute(requestItems);
Items items = JsonConvert.DeserializeObject<Items>(responseItems.Content);
log.LogInformation("Items count: " + items.count);
if (items.count > 0)
{
// get files as zip
var clientBlob = new RestClient($"{devopsURL}_apis/git/repositories/{repo.id}/blobs?{version}");
var requestBlob = new RestRequest(Method.POST);
requestBlob.AddJsonBody(items.value.Where(itm => itm.gitObjectType == "blob").Select(itm => itm.objectId).ToList());
requestBlob.AddHeader("Authorization", auth);
requestBlob.AddHeader("Accept", "application/zip");
var zipfile = clientBlob.DownloadData(requestBlob);
// upload blobs to Azure Storage
string name = $"{project.name}_{repo.name}_blob.zip";
var blob = container.GetBlockBlobReference(name);
await blob.DeleteIfExistsAsync();
await blob.UploadFromByteArrayAsync(zipfile, 0, zipfile.Length);
// upload file mapping
string namejson = $"{project.name}_{repo.name}_tree.json";
var blobjson = container.GetBlockBlobReference(name);
await blobjson.DeleteIfExistsAsync();
blobjson.Properties.ContentType = "application/json";
await blobjson.UploadTextAsync(responseItems.Content);
/* TODO:
* File mapping defines relationship between blob IDs and file names/paths.
* To reproduce a full file structure
* 1. Recreate all folders for <item.isFolder>
* 2. Extract all other items to <item.path>
*/
}
}
}
log.LogInformation($"DevOps BackupFunction function finished at: {DateTime.Now}");
}
}
struct Project
{
public string name;
}
struct Projects
{
public List<Project> value;
}
struct Repo
{
public string id;
public string name;
}
struct Repos
{
public List<Repo> value;
}
struct Item
{
public string objectId;
public string gitObjectType;
public string commitId;
public string path;
public bool isFolder;
public string url;
}
struct Items
{
public int count;
public List<Item> value;
}
}
All credit goes here. ANd also please consider upvoting this feature request
Couple of ways to do it.. Check this out, you can fine tune it:
$OrganizationName = "" # Fill this
$ProjectName = "" # Fill this
$Reponame = "" # Fill this
$user = "" # Fill this
$PAT = "" # Fill this
# Base64-encodes the Personal Access Token (PAT) appropriately
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$PAT)))
$headers = @{
'Authorization' = ("Basic {0}" -f $base64AuthInfo)
'Content-Type' = 'application/json'
}
$BaseUriWithProject=https://dev.azure.com/$OrganizationName/$ProjectName
# Get Repository
$listRepoUri = "$BaseUriWithProject/_apis/git/repositories?api-version=6.0"
$allRepo = Invoke-RestMethod -Uri $listRepoUri -Method GET -Headers $headers -UseBasicParsing
$repo = $allRepo.value | Where-Object -FilterScript {$_.Name -match $Reponame } | Select-Object Id
if($null -eq $repo){
Write-Error "No Repo"
}
# Get all items in repo
$itemuri = "$BaseUriWithProject/_apis/git/repositories/$($repo.id)/items?recursionLevel=Full&includeContentMetadata=true&download=true&api-version=6.0"
$allItems = Invoke-RestMethod -Uri $itemuri -Method GET -Headers $headers -UseBasicParsing
$files = $allItems.value | Where-Object -FilterScript {$_.gitObjectType -eq 'blob'}
# Download items
$headers += @{"Accept"="application/zip"}
$uri = "$BaseUriWithProject/_apis/git/repositories/$($repo.id)/blobs?api-version=6.0"
$body = $files.objectId | ConvertTo-Json
Invoke-RestMethod -Uri $uri -Method POST -Headers $headers -UseBasicParsing -Body $body -OutFile "repo.zip"