Sulla carta la gestione di un repository è configurazione. Su AZ-400 è architettura: la domanda non è “dove clicco per negare il push”, ma “dato questo requisito di compliance, velocità e riuso, quale topologia e quali controlli reggono senza diventare debito operativo”. Diamo per acquisiti i meccanismi base e concentriamoci sulle decisioni di design.

Permessi: gerarchia, ereditarietà e cosa non si delega

In Azure Repos i permessi Git si impostano da Project settings → Repositories. La voce Git Repositories vale per tutti i repository del progetto; i singoli repository ereditano da lì, con uno switch per disattivare l’ereditarietà, e i branch ereditano a loro volta un sottoinsieme dei permessi di repository. Regola di design: assegna in alto ciò che è politica aziendale, deroga in basso solo con un requisito documentato. Ogni ereditarietà spezzata è un’isola da manutenere.

I default sono un buon punto di partenza: Contributors ha Contribute, Create branches, Create tags e Manage notes, mentre Create repository, Edit policies e Manage permissions restano ai Project Administrators. Tre permessi non sono assegnati a nessun gruppo, ed è deliberato: Bypass policies when completing pull requests, Bypass policies when pushing e Force push (rewrite history, delete branches and tags).

Il trade-off interessante sta nella coppia di bypass. In passato un unico permesso — Exempt from policy enforcement — concedeva insieme l’override sulla PR e il push diretto sul branch protetto. Oggi sono separati, e per lo scenario “serve una via d’uscita per l’hotfix notturno senza aprire una falla” il pattern è concedere Bypass policies when completing pull requests e negare Bypass policies when pushing: l’override resta tracciato dentro il flusso PR, il push diretto resta bloccato. Nota anche che dallo sprint 224 (Azure DevOps Services e Azure DevOps Server 2022.1 e superiori) Edit policies non viene più concesso automaticamente a chi crea un branch; e che un utente appena aggiunto in Microsoft Entra ID può non essere subito ricercabile nei campi identità: è propagazione, non un errore di permessi.

Su GitHub l’equivalente sono i ruoli di repository più i ruleset, che targettano branch e tag o agiscono come push ruleset sull’intera fork network, e sul piano Enterprise si definiscono a livello di organizzazione su più repository. Quando più ruleset si sovrappongono, per la stessa regola vince la versione più restrittiva.

Tag: un contratto di release, non un’etichetta

Azure Repos supporta tag annotated e lightweight, ma il portale web crea solo annotated (la descrizione è obbligatoria); i lightweight arrivano da riga di comando o da Visual Studio. La scelta non è stilistica: solo l’annotated porta tagger, messaggio e data, quindi è l’unico che regge come evidenza di audit. Se il requisito parla di tracciabilità, la risposta è annotated.

La vera decisione di governance riguarda la cancellazione. Creare un tag richiede Create tag, che i Contributors hanno già; cancellarlo richiede Force push, a livello di Repository o sul nodo All tags (che eredita dal repository se non impostato esplicitamente). Soprattutto, il creatore eredita Force push sul proprio tag: è la trappola, perché per tag di release immutabili non basta lasciare Force push non assegnato. Il design corretto è generare i tag da una pipeline con identità di servizio dedicata, così che il “creatore” non sia lo sviluppatore. E cancellare un tag già distribuito resta comunque un’operazione a metà: sopravvive in cloni e fork.

Ultimo dettaglio di ciclo di vita: con Label sources di una pipeline classica, quando la build viene eliminata (anche per retention) il tag sparisce con essa. Se la release deve sopravviverle, il tag va creato come step esplicito.

Mono-repo o multi-repo, e come si condivide fra repository

Il criterio non è la dimensione del codice ma il confine di versionamento. Il mono-repo ha senso quando i componenti vanno rilasciati insieme: commit atomico su più moduli, un solo set di policy, nessun disallineamento di versione. Il prezzo è che permessi e policy diventano grossolani — la sicurezza Git si ferma al repository e al branch, non alla cartella — quindi la segmentazione va ricostruita altrove: path filter sulle build validation e Automatically included reviewers per area di codice (su GitHub, CODEOWNERS, dove vince l’ultimo pattern che corrisponde e basta l’approvazione di uno qualsiasi degli owner).

Il multi-repo dà isolamento nativo dei permessi, cicli di rilascio indipendenti e cloni leggeri, al costo di dover versionare i contratti fra componenti. È qui che si decide il meccanismo di condivisione.

  • Submodule: accoppia il consumatore a un commit preciso dell’altro repository. In Azure Pipelines Check out submodules funziona su submodule pubblici non autenticati, oppure contenuti nello stesso progetto (o stessa organizzazione GitHub, o stesso account Bitbucket Cloud) e referenziati con URL relativo: con URL assoluto il checkout non avviene. Fuori da questi casi servono credenziali esplicite in uno step di script, e l’agente parla HTTPS, non SSH.
  • Multi-repo checkout: più repository dello stesso progetto nella stessa pipeline. Adatto a build che compongono, non a distribuire una libreria.
  • Package feed (Azure Artifacts): la scelta giusta quando la condivisione è binaria e versionata. I nuovi feed andrebbero creati scoped al progetto, e un feed organization-scoped non è convertibile. Se una pipeline di un altro progetto deve leggere un feed project-scoped, i ruoli vanno concessi sia al Project Collection Build Service sia al Build Service del progetto chiamante; con un feed organization-scoped basta il primo.

Regola di design: submodule per il codice che devi compilare insieme, package feed per quello che consumi come dipendenza. Il secondo disaccoppia i cicli di rilascio, il primo li lega.

Build validation sulla pull request

È la policy che trasforma la PR in un gate. Le leve sono quattro, e ognuna è un trade-off.

Trigger. Automatic accoda una build alla creazione della PR e a ogni push sul branch sorgente; Manual lascia l’accodamento all’utente. In entrambi i casi la policy valuta il risultato: Manual si giustifica solo su build costose.

Policy requirement. Required blocca il completamento della PR finché la build non passa; Optional segnala il fallimento ma lascia completare. Optional serve a introdurre gradualmente un controllo nuovo, non a tenere un gate a metà per sempre.

Build expiration. È la decisione più sottile, perché arbitra fra freschezza e throughput. Immediately when the branch is updated porta lo stato a fallito e riaccoda a ogni aggiornamento del branch protetto: massima garanzia, insostenibile su branch molto attivi. After N hours invalida solo se la build passata supera la soglia, ed è il compromesso quando il branch protetto cambia spesso. Never riduce le build al minimo ma rischia PR completate su una base divergente.

Path filter. Su un mono-repo è ciò che rende praticabile il modello: più policy di build validation sullo stesso branch, ciascuna con il proprio path filter, invece di una build monolitica su ogni PR.

Due effetti collaterali: appena imposti una policy Required su un branch diventano automatiche la PR obbligatoria e l’impossibilità di eliminarlo — con sole policy Optional il push diretto resta permesso e il branch resta cancellabile, ed è proprio questo che le rende utili per introdurre un controllo gradualmente; e le policy si applicano in base al branch di destinazione, quindi metterle su branch temporanei rompe l’eliminazione automatica dopo il merge.

A scala, il vero problema è la configurazione ripetuta su decine di repository. Azure Repos permette di definire policy su tutti i repository, o su un branch specifico attraverso i repository, da Project settings → Repositories → All Repositories → Policies; quelle impostate lì valgono anche come default per i repository creati in seguito. È la risposta a ogni requisito “stessa regola ovunque, senza replicare la configurazione”.

Come cade all’esame

  • “Via d’uscita per gli hotfix, senza permettere push diretti sui branch protetti” → concedi Bypass policies when completing pull requests, nega Bypass policies when pushing.
  • “I tag di release non devono poter essere cancellati da chi li crea” → il creatore eredita Force push sul proprio tag: genera i tag da pipeline con identità di servizio.
  • “Stessa policy di reviewer minimi su main in tutti i repository, inclusi quelli futuri” → policy cross-repository da All Repositories → Policies, non configurazione repo per repo.
  • “Mono-repo, ma ogni team valida solo la propria area”path filter su più policy di build validation, più Automatically included reviewers (o CODEOWNERS); i permessi Git non scendono sotto il branch.
  • “Il branch protetto cambia di continuo e le PR vengono completate su build stantie” → build expiration After N hours, non Never e non Immediately.
  • “Condividere una libreria fra progetti diversi disaccoppiando i rilasci”Azure Artifacts, feed project-scoped con ruoli sia al Project Collection Build Service sia al Build Service del progetto chiamante; non submodule.
  • “Il submodule non viene scaricato dalla pipeline” → deve stare nello stesso progetto ed essere referenziato con URL relativo.