Example

The Azure DevOps pipeline definition below demonstrates automated repository reconstruction from historical snapshots and highlights advanced aspects of migration not covered in the main strategy discussion:

  • Sequential platform processing – extracts snapshots using the JadeGit version corresponding to the snapshot’s platform version, ensuring historical accuracy and avoiding compatibility issues.

  • Branch management – preserves original commits where appropriate to maintain a deterministic repository history.

  • Registry initialization – initializes the registry in the last code-only snapshot for each platform, producing an empty database image.

  • Conditional extraction – processes snapshots only if available per platform; otherwise, the most recently processed snapshot may be upgraded to form an empty database image for the target platform version.

  • Database image publishing – publishes empty database images during migration for local test environments and automated CI testing later. Development images are also prepared and published during the pipeline, based on the empty images, with JadeGit preinstalled, ready for developers to create local workspaces.

This example provides a practical reference for implementing migration in a real-world pipeline and demonstrates where migration activities intersect with database image creation, supporting workflows beyond full JadeGit adoption.

parameters:
# Branch to reset/replay snapshots on ('main' or 'migration')
- name: branch
  type: string
# Target database name for snapshots and registry (simple name like 'demo' without an org/env prefix)
- name: database
  type: string
# Platforms with historical snapshots; each includes display name, semantic version, and charset
- name: platforms
  type: object
  default:
  - name: Jade 2022 SP4 (ANSI)
    version: 22.0.05
    charset: ansi
  - name: Jade 2025 R1 (ANSI)
    version: 25.0.01
    charset: ansi
# Name of the repository to clone and update
- name: repository
  type: string

stages:
- stage: ${{ parameters.repository }}  # Stage named after repository
  dependsOn: []   # Independent stage
  jobs:
  - job: Migrate  # Core migration job
    workspace:
      clean: all  # Ensure a fresh workspace
    variables:
      # Folder to process snapshots
      - name: source
        value: $(Pipeline.Workspace)\source
      # Local repository path
      - name: repo
        value: $(Pipeline.Workspace)\repo
    timeoutInMinutes: 0  # Disable job timeout
    steps:
    # Disable automatic checkout; repository will be cloned manually
    - checkout: none

    # Clone bare repository
    - powershell: |
        git clone --bare -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" $(migrate.namespace).${{ parameters.repository }} $(repo)
      displayName: Clone Repo

    # Set author for any commits created during extract
    - powershell: |
        git config user.name "$(Build.RequestedFor)"
        git config user.email "$(Build.RequestedForEmail)"
      displayName: Config Repo
      workingDirectory: $(repo)

    # Reset branch to its initial commit to enable deterministic snapshot replay
    - powershell: |
        if (-not (git show-ref refs/heads/${{ parameters.branch }})) {
          Write-Host "Migration branch hasn't been initialized"
          exit 1
        }

        git update-ref ORIG_HEAD HEAD  # Preserve original HEAD
        git symbolic-ref HEAD refs/heads/${{ parameters.branch }}  # Switch to target branch
        
        $first = git rev-list --max-parents=0 HEAD  # Locate initial commit
        git update-ref HEAD "$first"  # Reset branch to initial commit
      displayName: Reset Branch
      workingDirectory: $(repo)

    # Iterate over each platform for snapshot processing
    - ${{ each platform in parameters.platforms }}:
      # Check if snapshot index exists for this platform; sets variable for conditional extraction
      - task: AzureCLI@2  
        displayName: "${{ platform.name }}"
        inputs:
          azureSubscription: $(migrate.subscription)
          scriptType: 'ps'
          scriptLocation: 'inlineScript'
          inlineScript: |
            $exists = az storage blob exists --account-name $(migrate.storage) --auth-mode login --container-name $(migrate.container) --name ${{ parameters.database }}/${{ platform.version }}-${{ platform.charset }}/snapshots/index.txt
            if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
            if (($exists | ConvertFrom-Json).exists) {
              Write-Host "Downloading snapshot index (extract required)"
              Write-Host "##vso[task.setvariable variable=extract]$true"
              New-Item -Path $(source)\${{ platform.version }}-${{ platform.charset }}\snapshots -Type Directory | Out-Null
              az storage blob download --account-name $(migrate.storage) --auth-mode login --container-name $(migrate.container) --file $(source)\${{ platform.version }}-${{ platform.charset }}\snapshots\index.txt --name ${{ parameters.database }}/${{ platform.version }}-${{ platform.charset }}/snapshots/index.txt
            } else {
              Write-Host "Snapshot index doesn't exist (extract not required)"
              Write-Host "##vso[task.setvariable variable=extract]$false"
            }

      # If any snapshots exists, download binaries for the platform version
      - task: AzureCLI@2
        displayName: "Download Binaries"
        condition: and(succeeded(), eq(variables.extract, true))
        inputs:
          azureSubscription: $(migrate.subscription)
          scriptType: 'ps'
          scriptLocation: 'inlineScript'
          inlineScript: |
            az storage copy -s "https://$(migrate.storage).blob.core.windows.net/$(migrate.container)/${{ parameters.database }}/${{ platform.version }}-${{ platform.charset }}/*" -d "$(source)\${{ platform.version }}-${{ platform.charset }}" --auth-mode login --recursive --exclude-path snapshots

      # If any snapshots exists, download JadeGit binaries corresponding to platform version
      - task: UniversalPackages@0
        displayName: "Download JadeGit"
        condition: and(succeeded(), eq(variables.extract, true))
        inputs:
          command: download
          vstsFeed: '$(jade.feed)'
          vstsFeedPackage: 'jadegit-dev-${{ platform.version }}-${{ platform.charset }}'
          vstsPackageVersion: '$(jadegit.version)'
          downloadDirectory: $(Agent.TempDirectory)

      # Sequentially extract snapshots for this platform, preserving historical commit alignment
      - task: AzureCLI@2
        displayName: "Extract Snapshots"
        condition: and(succeeded(), eq(variables.extract, true))
        inputs:
          azureSubscription: $(migrate.subscription)
          scriptType: 'ps'
          scriptLocation: 'inlineScript'
          inlineScript: |
            $source = "$(source)\${{ platform.version }}-${{ platform.charset }}"
            $env:Path = "$source\bin;$env:Path"
            Out-File -FilePath $(Agent.TempDirectory)\jade.ini  # Temporary config

            $last=0
            [array]$lines = Get-Content "$source\snapshots\index.txt"  # Read snapshot index
            foreach ($line in $lines) {
              $index++
              if (-not $line) { continue }

              Write-Host "Downloading snapshot for $line"
              if ($last) {
                Get-ChildItem $source\snapshots\$last | Remove-Item -Recurse -Force  # Remove previous snapshot folder
              }
              # Download snapshot for this release
              az storage copy -s "https://$(migrate.storage).blob.core.windows.net/$(migrate.container)/${{ parameters.database }}/${{ platform.version }}-${{ platform.charset }}/snapshots/$index/*" -d "$source\snapshots\$index" --auth-mode login --recursive
              if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

              # Determine if an original commit exists to preserve entity IDs
              $original = git rev-list --grep="$line" ORIG_HEAD
              if ($original) {
                $original_message = "original: $original"
                $original_commit = @("--original-commit", $original)
              } else {
                $original_message = "first time"
                $original_commit = @()
              }

              # Extract snapshot using JadeGit
              $(Agent.TempDirectory)\jadegit -p $source\snapshots\$index -i $(Agent.TempDirectory)\jade.ini extract --commit-message "$line" $original_commit
              $last=$index
            }

            # Prepare staging area for registry initialization and image creation
            New-Item -Path $(Build.StagingDirectory)\image -Type Directory -Force | Out-Null
            Get-ChildItem $(Build.StagingDirectory)\image | Remove-Item -Recurse -Force
            Get-ChildItem $source -Directory -Exclude snapshots | Move-Item -Destination $(Build.StagingDirectory)\image
            Move-Item -Path $source\snapshots\$last -Destination $(Build.StagingDirectory)\image\system
            Write-Host "##vso[task.setvariable variable=image_exists]$true"
          workingDirectory: $(repo)

      # Initialize registry in the last code-only snapshot for this platform, forming the empty database image to be published
      - powershell: |
          $env:Path="$(Build.StagingDirectory)\image\bin;$env:Path"
          New-Item -Path $(Build.StagingDirectory)\registry -Type Directory -Force | Out-Null
          $(Agent.TempDirectory)\jadegit -p $(Build.StagingDirectory)\image\system -i $(Agent.TempDirectory)\jade.ini registry init --name ${{ parameters.database }} --repo $(repo) --file $(Build.StagingDirectory)\registry\init.jgr
          $(Agent.TempDirectory)\jadegit -p $(Build.StagingDirectory)\image\system -i $(Agent.TempDirectory)\jade.ini registry load $(Build.StagingDirectory)\registry\init.jgr --force
          $(Agent.TempDirectory)\jadegit -p $(Build.StagingDirectory)\image\system -i $(Agent.TempDirectory)\jade.ini registry show
        displayName: "Initialize Registry"
        workingDirectory: $(repo)
        condition: and(succeeded(), eq(variables.extract, true))

      # Apply platform upgrade if required to align the image with the target platform version
      - template: ..\images\upgrade.yml
        parameters:
          path: $(Build.StagingDirectory)\image
          database: ${{ parameters.database }}
          version: ${{ platform.version }}
          charset: ${{ platform.charset }}
          condition: and(succeeded(), eq(variables.image_exists, true))

      # Publish empty database image
      - template: ..\images\publish.yml
        parameters:
          path: $(Build.StagingDirectory)\image
          config: empty
          database: ${{ parameters.database }}
          version: ${{ platform.version }}
          charset: ${{ platform.charset }}
          condition: and(succeeded(), eq(variables.image_exists, true))

    # Push reconstructed branch to remote repository
    - powershell: |
        git config push.autoSetupRemote true
        git push --force
      displayName: Push Repo
      workingDirectory: $(repo)

    # Publish registry artifact
    - publish: $(Build.StagingDirectory)\registry
      artifact: '${{ parameters.database }}_registry'
      displayName: Publish Registry

  # Prepare and publish development images based on the empty database image, with JadeGit preinstalled for developer workspaces
  - ${{ each platform in parameters.platforms }}:
    - template: ..\images\dev.yml
      parameters:
        database: ${{ parameters.database }}
        dependsOn:
          - Migrate
        displayName: "Dev - ${{ platform.name }}"
        version: ${{ platform.version }}
        charset: ${{ platform.charset }}

Last updated