Archiver les changements

Jusqu'à présent, si vous avez suivi les exemples, nous avons créé une nouvelle archive et un projet hello-world. Dans cette archive, nous avons importé la version initiale de hello-world.

L'opération la plus courante que vous aimerez effectuer en tant que programmeur utilisant un système de contrôle de révision est d'archiver (commit) un ensemble de modifications. Dans ce chapitre, nous verrons les méthodes les plus basiques pour effectuer cela.

Le cycle modification/mise à jour du log

Mettons que ces bogues soient plus complexes qu'il ne le sont actuellement, voilà comment le travail pourrait se dérouler :

Change warld en world.

Mise à jour du message de log.

Ajoutez une note au fichier de log :

Summary: Réparation des bogues dans la chaine "hello world"
Keywords: 

Écriture correcte de "world" (et non "warld").

Ajout d'un retour à la ligne à la chaine.

Mise à jour du message de log à nouveau.

Summary: Réparation des bogues dans la chaine "hello world"
Keywords: 

Écriture correcte de "world" (et non "warld").

Ajout d'une nouvelle ligne à la chaine "hello world"

Oh mon dieu -- Qu'ai-je fait ?

Vous venez juste d'effectuer un long et difficile travail pour réparer ces bogues. Ne serait-ce pas une bonne idée que de revoir ce que vous avez fait avant de le publier ?

Pas de problème, arch est là pour ça :

tla changes --diffs
[....]
*** patched regular files

**** ./hw.c
[....] 
     @@ -4,7 +4,7 @@
      void
      hello_world (void)
      {
     -  (void)printf ("hello warld");
     +  (void)printf ("hello world\n");
      }
[....]

Aha ! maintenant nous savons. C'est le moment d'archiver ces modifications.

Enregistrer les changements dans l'archive

Maintenant, enregistrons ces changements dans l'archive.

Si vous n'avez pas tenu compte de notre conseil gratuit (voir Quelques conseils gratuits sur les messages de log), c'est maintenant le moment de créer le message de log (astuce : tla make-log).

Pour sauvegarder vos changements dans l'archive, simplement :

% tla commit
[....]

Après ce commit, il y a une nouvelle révision dans l'archive.

% tla revisions hello-world--mainline--0.1
base-0
patch-1

ou, en plus détaillé :

% tla revisions --summary hello-world--mainline--0.1
base-0
    initial import
patch-1
    Réparation des bogues dans la chaine "hello world"

Notre arborescence de patch-log a également été mise à jour :

% tla logs hello-world--mainline--0.1
base-0
patch-1

% tla logs --summary hello-world--mainline--0.1
base-0
    initial import
patch-1
    Réparation des bogues dans la chaine "hello world"

Comment ça marche ? -- Archiver une nouvelle révision

Que fait commit au niveau de l'archive ?

# cd dans le répertoire de la version dans laquelle nous travaillons
# on:
# 
% cd ~/{archives}
% cd 2003-example/
% cd hello-world/
% cd hello-world--mainline/
% cd hello-world--mainline--0.1/
% ls
% ls
+version-lock   =README         base-0          patch-1

Le sous-répertoire patch-1 est nouveau :

% cd patch-1

% ls
+revision-lock
hello-world--mainline--0.1--patch-1.patches.tar.gz
log

Comme d'habitude, le fichier de log est celui que vous avez écrit, avec quelques entêtes supplémentaires :

% cat log
Revision: hello-world--mainline--0.1--patch-1
Archive: lord@emf.net--2003-example
Creator: Tom (testing) Lord <lord@emf.net>
Date: Mon Jan 27 22:26:13 PST 2003
Standard-date: 2003-01-28 06:26:13 GMT
Summary: Réparation des bogues dans la chaine "hello world"
Keywords: 
New-files: \
  {arch}/hello-world/ [....] /patch-log/patch-1
Modified-files: hw.c
New-patches: \
  lord@emf.net--2003-example/hello-world--mainline--0.1--patch-1

Écriture correcte de "world" (et pas "warld").

Ajout d'un retour à la ligne à la fin de la chaine.

Le fichier .patches.tar.gz est appelé un changeset. Il décrit les changements que vous avez effectués par les différences entre la révision base-0 et la révision patch-1. Vous en apprendrez davantage sur la nature des changesets dans les chapitres suivants. Pour l'instant, vous pouvez considérer un changeset comme un équivalent de la sortie d'un diff -r qui serait utilisé pour comparer la révision base-0 d'avant vos changements, avec l'aborescence d'après vos changements (ou, comme dirait un utilisateur de arch : un « patch set on steroids »).

Dans l'arborescence du projet :

% cd ~/wd/hello-world

La commande commit a eu deux effets. Premièrement, elle ajouté un fichier de log dans {arch}/hello-world. Deuxièmement, elle a modifé {arch}/++pristine-trees pour contenir une copie mise en cache de la révision patch-1 à la place de la révision base-0.

Récupérer des révisions antérieures

Si vous avez suivi les exemples des chapitres précédents jusqu'ici, vous devriez avoir :

Votre première archive

qui est également votre archive par défaut :

% tla my-default-archive
lord@emf.net--2003-example

% tla whereis-archive lord@emf.net--2003-example
/usr/lord/examples/{archives}/2003-example

Deux révisions du projet hello-world

% tla revisions hello-world--mainline--0.1
base-0
patch-1

Dans ce chapitre, vous apprendrez comment récupérer les révisions d'une archive.

Récupérer la dernière révision

Vous devriez également avoir une arborescence en plus. Si c'est le cas, débarrassons-nous en :

% cd ~/wd

% ls
hello-world

% rm -rf hello-world

Supposons maintenant que vous vouliez récupérer les derniers sources du projet hello-world. Pour cela, vous utiliserez la commande get :

% tla get hello-world--mainline--0.1 hello-world
[...]


% ls 
hello-world

% ls hello-world
hw.c    main.c  {arch}

Récupérer une révision antérieure

Supposons que nous voulions récupérer une version antérieure du projet hello-world.

Notez que dans l'exemple précédent, nous avons simplement demandé une version particulière du projet :

% tla get hello-world--mainline--0.1 hello-world
          ^^^^^^^^^^^  ^^^^^^^^  ^^^ ^^^^^^^^^^^
               |           |      |       |
               |           |      |  target directory
               |           |      |
               |           |      |
               |           |   version number
               |           |
               |      branch name
               |
         category name

Nous pouvons récupérer une version antérieure en spécifiant son niveau de patch explicitement :

% tla get hello-world--mainline--0.1--base-0 hello-world-0
          ^^^^^^^^^^^  ^^^^^^^^  ^^^  ^^^^^^ ^^^^^^^^^^^^^
               |           |      |      |        |
               |           |      |      |  target directory
               |           |      |      |
               |           |      | patch level name
               |           |      |
               |           |   version number
               |           |
               |      branch name
               |
         category name


% ls
hello-world     hello-world-0

% ls hello-world-0
hw.c    main.c  {arch}

Vous pouvez voir les modifications effectués entre base-0 et patch-1 avec, par exemple, diff -r :

% diff -r hello-world-0 hello-world
diff -r hello-world-0/hw.c hello-world/hw.c
7c7
<   (void)printf ("hello warld");
---
>   (void)printf ("hello world\n");
[...]

Comment ça marche ? -- Récupérer une révision avec get

Récupérer la révision base-0 est facile. Comme on s'en souvient, la révision base-0 est stockée dans un fichier tar compressé contenant l'arborescence complète (voir « Comment ça marche ? -- que fait import »). Quand vous demandez la récupération base-0, la commande get extrait simplement l'arborescence du fichier tar.

Récupérer la révision patch-1 se déroule en deux étapes. Souvenez-vous que patch-1 est stocké comme un changeset qui décrit les différences entre base-0 et patch-1 (voir « Comment ça marche ? -- enregistrer une nouvelle révision »). Néanmoins, get procède en récupérant la révision base-0, et ensuite récupère le changeset patch-1, puis utilise ce changeset pour modifier l'arborescence base-0 qui devient alors l'arborescence patch-1. En interne, get utilise la commande tla dopatch pour appliquer le changeset. Mais si vous êtes familier avec les patchsets diff/patch, vous pouvez considérer dopatch comme un patch on steroids.

Supponsons qu'au lieu d'archiver simplement un changement vous en ayez archivé plusieurs : pas seulement la révision patch-1, mais patch-2, patch-3 et ainsi de suite. En l'occurence, get va appliquer chaque changeset dans l'ordre pour créer la révision demandée.

Note

En fait, get est un petit peu plus compliqué que ce qui est décrit ici. D'une part, certaines optimisations peuvent éviter à get d'appliquer une longue liste de changesets. D'autre part, il peut y avoir des révisions créées par tag au lieu de commit, pour lesquelles différentes règles s'appliquent. Vous en apprendrez plus sur ces exceptions dans les prochains chapitres.

Archives partagées et publiques

Dans les chapitres précédents, vous avez appris à créer votre première archive, démarrer un projet, récupérer l'arborescence initiale et les modifications consécutives, et récupérer les révisions précédentes.

Dans ce chapitre vous allez apprendre à créer une archive disponible sur le réseau et vous initier au partage d'archive entre plusieurs développeurs.

Inscrire une archive pour un accès en réseau

Souvenez-vous, une archive a à la fois un nom logique et un emplacement physique.

% tla archives
lord@emf.net--2003-example
^^^^^^^^^^^^^^^^^^^^^^^^^^
  |     /usr/lord/{archives}/2003-example
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |                     |
  |              archive location        
  |
archive name

(voir « créer une nouvelle archive »)

Certaines archives peuvent être accessibles à travers le réseau, pour l'instant à partir des procoles suivants :

FTP
SFTP
WebDAV
plain HTTP

Plus loin dans ce chapitre, vous apprendrez comment créer de telles archives.

Pour l'instant, vous devez savoir que pour accéder à une telle archive, vous devez inscrire son nom et son emplacement physique, en utilisant une URL pour l'emplacement physique.

Par exemple, pour accéder à une archive par HTTP ou WebDAV :

% tla register-archive lord@emf.net--2003b \
        http://regexps.srparish.net/{archives}/lord@emf.net--2003b

ou par FTP :

% tla register-archive lord@regexps.com--2002 \
        ftp://ftp.regexps.com/{archives}/lord@regexps.com--2002

Vous pouvez voir que ces commandes ont eu un effet :

% tla archives
lord@emf.net--2003b
        http://regexps.srparish.net/{archives}/lord@emf.net--2003b
lord@emf.net--2003-example
        /usr/lord/examples/{archives}/2003-example
lord@regexps.com--2002
        ftp://ftp.regexps.com/{archives}/lord@regexps.com--2002

Travailler avec plusieurs archives à la fois

Après avoir enregistré de nouvelles archives, comment y accéder ?

La manière la plus simple est de rendre l'archive souhaitée comme archive par défaut.

% tla my-default-archive lord@emf.net--2003

% tla categories
[...categories de l'archive distante...]

Il n'est pas toujours souhaitable de changer l'archive par défaut. Pour l'instant, remettons par défaut l'archive que l'on a utilisée pour les exemples.

% tla my-default-archive lord@emf.net--2003-example

Sélectionner une archive avec -A

Toutes les commandes qui opèrent sur des archives acceptent l'option -A qui permet d'outrepasser la valeur par défaut :

% tla my-default-archive
lord@emf.net--2003-example

% tla categories -A lord@emf.net--2003
[... categories de lord@emf.net--2003 ...]

Note

L'option -A a priorité par rapport à l'archive par défaut mais est surpassée par un nom de projet pleinement qualifié (voir ci-après).

Nom de projet pleinement qualifié

Les commandes qui acceptent un nom de projet prennent en compte l'utilisation d'un nom pleinement qualifié. C'est à dire formé en faisant précéder le nom du projet par le nom de l'archive suivie d'une barre oblique.

category name:
tla                     => lord@emf.net--2003/tla

branch name:
tla--devo               => lord@emf.net--2003/arch--tla

version name:
tla--devo--1.0          => lord@emf.net--2003/tla--devo--1.0

revision name:
tla--devo--1.0--patch-1 => lord@emf.net--2003/tla--devo--1.0--patch-1

Dans cet exemple :

% tla my-default-archive
lord@emf.net--2003-example

% tla branches lord@emf.net--2003/hello-world
[... branches de hello-world dans lord@emf.net--2003 ...]

Note

Un nom de projet pleinement qualifié est prioritaire sur l'option -A et l'archive par défaut.

Archives en lecture seule

Le système d'exploitation et les contrôles d'accès au niveau du serveur peuvent être utilisés pour limiter l'accès à certains ou à tous les utilisateurs en lecture seule. Par exemple, FTP est généralement configuré pour que les utilisateurs anonymes puissent lire, mais pas modifier l'archive.

Créer des miroirs locaux, distants, et des archives distantes

Un « miroir » est une archive dont le contenu est dupliqué à partir d'une autre archive. Vous ne pouvez pas archiver vers un miroir de la manière habituelle, vous pouvez seulement mettre à jour cette copie à partir de sa source.

Il y a deux utilisations principales des miroirs d'archives : une est de faire une copie locale d'un miroir distant (pour pouvoir accéder à son contenu sans passer par le réseau); l'autre est de faire une copie distance d'une archive locale (pour que d'autres puissent y accéder).

Créer un miroir local d'une archive distante

Supposons que, pour avoir un accès le plus rapide possible, ou pour pouvoir travailler dessus en étant déconnecté, vous vouliez créer un miroir local d'une archive distante pour y accéder localement plutôt que par le réseau.

Supposons que vous vouliez le faire avec lord@emf.net--2003b, il y a trois étapes (supposons que $remote_location soit quelque chose du genre http://my.site.com//archives/lord@emf.net--2003b).

Premièrement, enregistrer l'archive distance sous un nouveau nom, formé en ajoutant --SOURCE à son nom :

% tla register-archive lord@emf.net--2003b-SOURCE $remote_location

Ensuite, créer le miroir local :

% tla make-archive --mirror-from lord@emf.net--2003b-SOURCE $local_location

Cette commande va en même temps inscrire lord@emf.net--2003b comme nom du miroir local.

Une fois que l'archive distance à été inscrite, vous pouvez mettre à jour votre miroir en répétant l'étape tla archive-mirror.

Si vous ne voulez pas créer un miroir de l'archive entière, vous pouvez en option limiter le miroir à certaines catégories, branches, ou versions. (voir tla archive-mirror -H).

Créer un miroir distant d'une archive locale

Supposons que vous ayez une archive locale mine@somewhere.com, et que vous souhaitiez « publier » un miroir de cette archive sur Internet pour que d'autres puissent la lire.

Admetons que vous ayez déjà inscrit mine@somewhere.com, vous pouvez créer un miroir distant avec :

% tla make-archive --mirror mine@somewhere.com $remote_location

Arch va écrire directement sur $remote_location, il faut donc y avoir un accès en écriture comme avec SFTP, mais pas comme avec HTTP.

Vous pouvez initialiser ou mettre à jour le contenu de ce miroir distant avec :

% tla archive-mirror mine@somewhere.com

Une situation courante pour de nombreuses personnes est de pouvoir installer des fichiers statiques sur un site web, mais sans pouvoir y fournir un accès WebDAV. Même dans ces conditions, vous pouvez toujours publier une archive arch avec, cependant, quelques subtilités.

Premièrement, lors du make-archive, vous devez utiliser une option suplémentaire :

% tla make-archive --listing --mirror mine@somewhere.com \
                $remote_location

L'option --listing permet de conserver un fichier .listing à jour sur le miroir, ce qui permet, du coup, d'autoriser les gens à accéder à l'archive à partir d'un simple HTTP (sans le support WebDAV).

Deuxièmement, il peut arriver que le fichier .listing ne soit plus à jour (par exemple, si vous tuez une commande archive-mirror au mauvais moment. Si vous savez ou si vous soupçonnez que c'est arrivé, vous pouvez réparer l'archive en question en lançant archive-fixup comme dans notre exemple :

% tla archive-fixup mine@somewhere.com-MIRROR

Créer un dépot distant

Même si la création de miroir est courrante pour les dépots distants, il est possible de créer un dépot distant qui ne soit pas un miroir, et ainsi pouvoir archiver sur celui-ci directement.

On peut créer un dépot distant avec cette commande :

% tla make-archive $archive_name $remote_location

Ou créer un dépôt distant avec un fichier .listing :

% tla make-archive --listing $archive_name $remote_location

Mélanger des modes d'accès différents

Rien n'empêche de rendre une archive disponible à partir de méthodes différentes. Par exemple, vous pouvez rendre une archive de votre système de fichier local accessible par FTP, vous devrez indiquer aux autres utilisateurs de l'inscrire en tant que FTP (avec une url ftp:)

Coopération à base de update/commit

Dans les chapitres précédents, vous avez appris à ajouter un projet à une archive, archiver l'arborescence initiale, archiver les changements effectués sur les sources, et récupérer les révisions de cette archive.

Dans le chapitre précédent, vous avez appris à rendre une archive accessible par le réseau.

Ce chapitre va commencer à explorer la manière dont plusieurs programmeurs peuvent partager une archive dans laquelle chacun d'eux effectue des modifications sur des projets particuliers.

Notez en premier lieu qu'il y a de nombreuses manières de partager des archives et d'organiser la coopération entre les développeurs d'un même projet. Nous démarrerons par la technique la plus simple.

Alice et Bob travaillent sur main

Supposons qu'Alice et Bob soient tous les deux en train de travailler sur hello-world et qu'ils partagent la même archive. Dans les exemples suivants, nous allons jouer le rôle de chacun d'eux.

Pour commencer, chaque programmeur va avoir besoin de sa propre arborescence :

% cd ~/wd

% [ ... effacez les répéretoires laissés par les exemples précédents ...]


% tla get hello-world--mainline--0.1  hello-world-Alice
[....]

% tla get hello-world--mainline--0.1  hello-world-Bob
[....]

L'objectif d'Alice est d'ajouter quelques mentions légales à chaque fichier. Lorsque c'est fait (mais pas encore archivé par commit), les fichiers sont ainsi :

% cd ~/wd/hello-world-Alice

% head -3 main.c
/* Copywrong 1998 howdycorp inc.  All rights reversed.*/

extern void hello_world (void);

% head hw.c
/* Copywrong 1998 howdycorp inc.  All rights reversed. */

#include <stdio.h>

Bob, pendant ce temps, a ajouté un commentaire indispensable à main:

% cd ~/wd/hello-world-Bob

% cat main.c
extern void hello_world (void);

int
main (int argc, char * argv[])
{
  hello_world ();

  /* Exit with status 0
   */
  return 0;
}

Notez que les deux programmeurs ont maintenant des versions modifiées de hello-world, mais aucun d'eux n'a les modifications de l'autre.

Bob archive en premier

Supposons que Bob soit le premier à essayer d'archiver ses modifications. Pour se rappeler, voici les deux étapes :

En premir lieu, Bob prépare un message de log :

% cd ~/wd/hello-world-Bob

% tla make-log
++log.hello-world--mainline--0.1--lord@emf.net--2003-example

[Bob edits the log message.]

% cat ++log.hello-world--mainline--0.1--lord@emf.net--2003-example
Summary: commented return from main
Keywords: 

Added a comment explaining how the return from `main'
relates to the exit status of the program.

Ensuite il appel commit:

% tla commit
[...]

Alice ne peux pas archiver maintenant

Maintenant c'est au tour d'Alice

% cd ~/wd/hello-world-Alice

% tla make-log
++log.hello-world--mainline--0.1--lord@emf.net--2003-example

[Alice edits the log message.]

% cat ++log.hello-world--mainline--0.1--lord@emf.net--2003-example
Summary: added copywrong statements
Keywords: 

Added copywrong statements to the source files so 
that nobody can steal HowdyCorp's code.

Et ensuite elle essaye d'archiver :

% tla commit
commit: tree is not up-to-date 
  (missing latest revision is 
    lord@emf.net--2003b--2003-example/hello-world--mainline--0.1--patch-2)

Le problème ici est que les modifications de Bob ont déjà été archivées, mais l'arborescence d'Alice ne contient pas ces modifications.

Étudions pourquoi Alice ne peut pas archiver

La commande commit indique à Alice qu'elle n'est « pas à jour ». Ça signifie que son arborescence ne contient pas certaines modifications archivées.

Elle peut examiner la situation plus en détail en demandant ce qui manque à son arborescence :

% tla missing
patch-2

ou plus en détail :

% tla missing --summary
patch-2
    commented return from main

dans lequel vous pourrez reconnaître le « Summary: » du message de log de Bob.

Elle peut consulter le message de log de Bob en entier :

% tla cat-archive-log hello-world--mainline--0.1--patch-2
Revision: hello-world--mainline--0.1--patch-2
Archive: lord@emf.net--2003-example
Creator: Tom (testing) Lord <lord@emf.net>
Date: Wed Jan 29 12:46:50 PST 2003
Standard-date: 2003-01-29 20:46:50 GMT
Summary: commented return from main
Keywords: 
New-files: {arch}/hello-world/[....]
Modified-files: main.c
New-patches: \
  lord@emf.net--2003-example/hello-world--mainline--0.1--patch-2

Added a comment explaining how the return from `main'
relates to the exit status of the program.

En regardant les entêtes de ce message, Alice peut voir, par exemple, que Bob a modifié le fichier main.c.

Dans des chapitres ultérieurs, nous explorerons davantages de commandes qu'Alice peut utiliser pour étudier les modifications que Bob a effectuées, mais pour l'instant, regardons comment Alice peut intégrer ces modifications dans son arborescence.

La commande update

Alice a besoin d'associer ses modifications avec celles de Bob avant de pouvoir les archiver. La méthode la plus facile est d'utiliser la commande update.

% cd ~/wd

% tla update hello-world-Alice
[....]

Maintenant elle va retrouver les modifications de Bob dans son arborescence :

% cd hello-world-Alice

% cat main.c
/* Copywrong 1998 howdycorp inc.  All rights reversed. */

extern void hello_world (void);

int
main (int argc, char * argv[])
{
  hello_world ();

  /* Exit with status 0
   */
  return 0;
}

/* arch-tag: main module of the hello-world project
 */

Du fait, il ne manque plus rien :

% tla missing
[rien]

La commande commit peut être utilisée joyeusement :

% tla commit
[....]

Note

Si vous avez suivis les exemples jusqu'ici, vous devriez toujours avoir une arborescence hello-world-Bob contenant les modifications de Bob, mais pas ceux d'Alice. Essayez différentes commandes sur ce répertoire par curiosité (missing, update, changes etc.).

Comment ça marche ? -- La commande update

Une explication complète du fonctionnement de la commande update dépasserait le cadre de ce chapitre. Vous serez capable de comprendre en détail la commande update dans les chapitres suivants (« changeset » et « patch-logs »).

Pour l'instant, si vous êtes familier avec diff et patch, vous pouvez considérer cette commande comme cela :

Lorsque update est lancée dans l'arborescence d'Alice, elle constate que l'archive est au niveau de la révision patch-2, mais que l'arborescence à été récupérée à partir d'un get de la révision patch-1. update fonctionne en 3 étapes :

Premièrement, elle utilise une commande appellée mkpatch (qui est une sorte de diff évolué) pour calculer un changeset (une sorte de patch-set) décrivant les modifications effectuées par Alice sur son arborescence.

Deuxièmement, elle récupère une copie de la révision patch-2 et remplace l'arborescence d'Alice avec celle-ci.

Troisièmement, update utilise dopatch (une sorte de patch) pour appliquer le changeset, créé lors de la première étape, à l'arborescence.

Vous vous demandez sûrement comment les conflits sont traités. Les exemples précédents étaient choisis pour éviter les conflits. Ne vous inquiétez pas -- nous allons aborder le sujet bientôt (voir « Problème de patch -- comment les conflits sont traités »).

Introduction aux changesets

Il est souvent utile de pouvoir comparer deux arborescences (généralement du même projet) et voir précisément quelles sont les différences entre elles. L'enregistrement de ces différences est appelé changeset ou delta.

Les changesets représentent un concept central de arch -- la plupart des outils de arch effectuent des opérations avec les changesets.

Si vous avez un changeset entre une « vieille arborescence » et une « nouvelle arboresence », vous pouvez « appliquer un changeset » sur la « vieille arboresence » et obtenir ainsi la « nouvelle arboresence » -- en d'autres termes, vous pouvez appliquer automatiquement les changements décrits dans le changeset. Si vous avez une troisième arborescence vous pouvez également appliquer ce patch et voir ce que donnerait ces changements sur lui.

arch inclu des outils sophistiqués pour créer et appliquer des changesets.

mkpatch

mkpatch génère un changeset décrivant les différences entre deux arborescences. La commande de base est :

% tla mkpatch ORIGINAL MODIFIED DESTINATION

qui compare l'arborescence ORIGINAL et MODIFIED

mkpatch crée un nouveau répertoire, DESTINATION, et enregistre le changeset dedans.

Quand mkpatch compare les deux arborescences, il utilise les identifiants (inventory ids). Par exemple, il considère deux répertoires ou deux fichiers comme étant « le même répertoire (ou fichier) » s'ils ont le même identifiant -- même s'ils sont placés à des endroits différents. (voir « Identifiants d'inventaire pour les sources ».)

Un changeset produit par mkpatch décrit quels fichiers et répertoires ont été ajoutés, déplacés, renommés ou modifiés (et comment ils ont été modifiés), ainsi que les fichiers dont les permissions auraient été changées (et comment). Lorsqu'il s'agit de fichiers textes classiques, mkpatch génère un « context diff » décrivant les différences. mkpatch peut comparer des fichiers binaires (en sauvegardant une copie complète de l'ancienne et de la nouvelle version si elles sont différentes) ainsi que les liens symboliques (en sauvegardant l'ancienne et la nouvelle cible, si elles sont différentes).

Une description détaillée du format d'un changeset est décrit dans l'appendice (voir « Le format arch d'un changeset »)

dopatch

dopatch est utilisé pour appliquer un changeset à une arborescence.

% tla dopatch PATCH-SET TREE

Si l'arborescence TREE est exactement la même que l'arboresence « originale » utilisée par mkpatch, alors TREE sera modifiée pour que l'arboresence devienne strictement identique à l'arborescence « modifiée » utilisée par mkpatch, avec une exception (détaillée plus loin).

« Exactement la même » signifie que la structure de l'arborescence est identique, que les liens symboliques sont identiques, que les fichiers textes sont identiques, et que les permissions des fichiers sont identiques. Les dates de modifications, les fichiers ayant plusieurs liens (en dur), et les propriétaires des fichiers ne sont pas forcément préservés.

L'exception à la règle « exactement la même » est qu'au cas où l'application du patch entraîne des supressions de fichiers ou répertoires dans l'arboresence TREE, ces fichiers et répertoires seront sauvegardés dans un sous-répertoire de TREE avec un nom particulierement visible à l'oeuil grâce à sa syntaxe :

++removed-by-dopatch-PATCH--DATE

PATCH est le nom complet du patch-set et DATE la date précise.

Problème de patch -- Comment sont gérés les conflits

Que se passe-t-il si l'arborescence à patcher de dopatch n'est pas exactement la même que l'originale utilisée par mkpatch ?

Ci-dessous, une description de ce à quoi on peut s'attendre. La documentation complète du fonctionnement de dopatch est incluse dans le code source.

dopatch fait un inventaire de l'arborescence à patcher. Il utilise les identifiants (inventory ids) pour décider quels fichiers et répertoires attendus sont présents ou manquants de l'arborescence et voir où chaque fichier et répertoire se trouve dans l'arborescence.

Patchs simples

Si le changeset contient un patch classique ou un metadata patch pour un lien, répertoire ou fichier, et que ce fichier est présent dans l'arborescence, dopatch applique le patch de manière habituelle. Si le patch s'applique proprement, le fichier modifié, le lien, ou le répertoire est laissé à sa place.

Si le patch ne peut pas s'appliquer proprement, dopatch va laisser le fichier orignal avec un suffixe .orig (le fichier original de l'arborescence devant être patché, sans aucune modification) et créer un fichier .rej (contenant la partie du patch qui n'a pas pu être appliquée)

Si le conflit concernait un fichier binaire, il n'y aurait pas eu de fichier partiellement patché. À la place nous aurions eu :

.orig -- le fichier original de l'arborescence à patcher,
         sans aucune modification

.rej  -- une copie complète du fichier de l'arborescence
         modifiée, avec les même permission que le fichier ``.orig``

.patch-orig -- une copie complète du fichier original
               utilisé par ``mkpatch``, avec ses permissions originales.

                -ou-

               le lien symbolique de l'arborscence utilisé
               par ``mkpatch`` avec ses permissions originales.

Si le conflit concernait un lien symbolique, il n'y aurait pas eu de fichier partiellement patché. À la place nous aurions eu :

.orig -- le fichier non modifié de l'arborescence originale

.rej -- un lien symbolique sur la cible attendue par le patch avec
        les mêmes permissions que le fichier ``.orig``

.patch-orig -- une copie complète du fichier original utilisé par
               ``mkpatch``, avec ses permissions originales.

                 -ou-

               le lien symbolique de l'arborescence utilisée
               par ``mkpatch`` avec ses permissions originales.

Patchs sur des fichiers manquants

Tous les patchs à appliquer sur des fichiers ou répertoires manquants sont enregistrés dans un sous-répertoire placé à la racine de l'arborescence à patcher.

==missing-file-patches-PATCH-DATE

PATCH est le nom complet du changeset et DATE la date actuelle.

Réorganisation des répertoires et nouveaux répertoires

Les répertoires sont ajoutés, supprimés, et réorganisés de la manière attendue, même si vous ne savez pas que c'est ce que vous attendriez.

Supposons que lorsque la commande mkpatch a été appelée, l'arborescence ORIGINAL était :

Répertoire ou fichier:      Id:

a/x.c                       id_1
a/bar.c                     id_2

mais l'arborescence MODIFIED était :

a/x.c                       id_1
a/y.c                       id_2

avec une modification sur chaque fichiers. Le patch va renommer le fichier avec l'identifiant id_2` en ``y.c, et modifier le contenu des fichiers avec les identifiants id_1 et id_2.

Supposons, par exemple, que vous ayez cette arborescence :

a/foo.c                     id_1
a/zip.c                     id_2

et que vous appliquiez le patch à cette arborescence. Après le patch, vous aurez :

a/foo.c                     id_1
a/y.c (ancien zip.c)        id_2

avec les modifications sur chacun des fichiers.

Voici un exemple plus subtil et la manière de gérer ces conflits :

Suposons que l'arborescence originale utilisée par mkpatch était :

Répertoires et fichiers:    Id:

./a                         id_a
./a/b                       id_b
./a/b/c                     id_c

Finalement supposons que que l'arborescence soit :

./x                         id_a
./x/b                       id_b
./x/c                       id_new_directory
./x/c/b                     id_different_file_named_b
./x/c/q                     id_c

Après l'application du patch nous aurons :

./x                         id_a
      Étant donné que le patch ne fait aucune modification sur le
      répertoire ayant id_a

./x/c.orig                  id_new_directory
./x/c.rej                   id_c
      Étant donné que le patch veut transformer le répertoire id_c
      en un sous-répertoire nommé ``c`` du répertoire ``id_a``, mais que cette
      arborescence a déjà un autre répertoire ici, avec l'identifiant
      ``id_new_directory``.

./x/c.rej/b                 id_b
      Étant donné que le patch veut renomer le répertoire avec
      l'identifiant ``id_b`` en un sous-répertoire nommé ``b`` du répertoire avec l'identifiant
      ``id_c``.

./x/c.orig/b                id_different_file_named_b
      Étant donné que le patch effectue des modifications sur ce fichier,
      il reste dans son répertoire parent.

Exploration des Changesets

Le chapitre précédent introduisait la notion de changeset et des commandes mkpatch et dopatch (variations sur le thème des programmes traditionels diff et patch).

Dans ce chapitre, nous allons étudier d'une manière plus détaillée comment les changesets sont utilisés dans les archives, comment ils sont utilisés par les commandes commit et update, et ce que cela peut nous apporter pour utiliser arch au mieux.

Comment ça marche ? -- commit enregistre un changeset dans une archive

Supposons que vous ayez récupéré la dernière révision d'un projet (avec get), écrit un message de log, et archivé vos modifications (avec commit). Que s'est-il passé ?

  1. Génère un changeset décrivant les modifications apportées par rapport à la dernière révision ;
  2. Crée un répertoire dans l'archive pour la nouvelle révision ;
  3. Enregistre votre message de log et le changeset dans l'archive.

Sachant cela, vous aurez peut-être envie de revenir en arrière et de revoir la section précédente « Comment ça marche ? -- archiver (commit) une nouvelle révision »

get-changeset récupère un changeset d'une archive

Nous avons appris plus tôt que la commande cat-archive-log récupérait un message de log d'une archive (voir « Étudions pourquoi Alice ne peux pas faire de commit »)

Vous pouvez aussi récupérer un changeset d'une archive.

% cd ~/wd

% tla get-changeset hello-world--mainline--0.1--patch-1 patch-1
[...]

get-changeset récupère le changeset de l'archive et, dans ce cas, l'enregistre dans un répertoire appelé patch-1

(Le format des changesets est décrit dans « Le format arch d'un changeset ».)

Utiliser show-changeset pour examiner un changeset

Le format d'un changeset est optimisé pour être utilisé par des programmes, pas par des personnes. Il est difficile pour un humain d'analyser un changeset. Au lieu de ça, vous pouvez obtenir un rapport du patch au format diff en utilisant :

% tla show-changeset --diffs patch-1

[...]

Si vous avez suivi les exemples précédents, vous reconnaitrez le format du rapport de show-changeset de la commande changes expliquée plus tôt (voir « Oh mon dieu -- Qu'ai-je fait ? »).

Bien utiliser les commit -- L'idée d'un changeset « propre »

Lorsque vous archivez (commit) un ensemble de modifications, il est généralement « de bon usage » de s'assurer qu'il s'agit d'un changeset « propre ».

Un changeset « propre » ne doit contenir que des modifications concernant un seul dessein. Si, par exemple, vous avez plusieurs bugs à corriger, ou plusieurs fonctionnalités à ajouter, essayer de ne pas les mélanger dans un seul commit.

Relecture facile

Il est plus facile pour quelqu'un de comprendre un changeset s'il ne concerne qu'une seule chose.

Fusion facile

Comme nous le verrons dans les chapitres suivants, dans certaines circonstances, nous allons avoir besoin d'examiner une collection de changesets pour en sélectionner quelques unes. Peut-être voudrez vous récupérer la correction A mais pas la fonctionnalité B. Si un changeset n'a qu'un seul dessein, ce genre de cueillette (cherrypicking) sera plus pratique.

Introduction à replay -- Une alternative à update

update n'est pas la seule manière de mettre à jour un développement. Une autre option est la commande replay :

% cd ~/wd/project-tree
% tla replay
[....]

Que fait cette commande exactement ?

Un update supplémentaire

Supposons que nous avons récupéré une vieille version de hello-world :

% cd ~/wd
% tla get hello-world--mainline--0.1--patch-1 hw-patch-1
[...]

Il est facile de voir que l'arborescence n'est pas à jour :

% cd hw-patch-1
% tla missing
patch-2
patch-3

Maintenant supposons que nous ayons fait quelques changements dans hw-patch-1 et ensuite update. Que se passe-t-il ?

Les changements locaux sont calculés par rapport au patch-1.

En d'autres termes un changeset est créé et correspond aux changements apportés à la copie originale de la révision patch-1 pour obtenir l'arborescence actuelle (hw-patch-1).

Une copie du patch-3 est récupérée.

update démarre avec la copie originale de la révision patch-3.

Le changeset est appliqué sur l'arborescence du patch-3.

Les changements calculés en premier lieu sont appliqués à cette nouvelle arborescence.

Il y a cependant une autre méthode possible :

La commande replay

Nous avons une copie du patch-1, avec éventuellement quelques changements :

% cd hw-patch-1
% tla missing
patch-2
patch-3

Souvenons-nous que les révisions patch-2 et patch-3 correspondent chacune à un changeset spécifique, stocké dans l'archive (voir « Comment ça marche ? -- commit enregistre une nouvelle révision »)

Nous pourrions effectuer les changements à notre arborescence locale en utilisant get-changeset pour récupérer chaque changeset, et utiliser dopatch pour les appliquer (voir : « get-changeset récupère un changeset d'une archive »). C'est un travail assez pénible, alors que arch fournit une solution automatique pour accomplir la même chose :

% cd ~/wd/hw-patch-1
% tla replay
[....]
% tla missing
[no output]

replay va faire exactement ce que nous avons décrit : récupérer les « patchs » de l'archive et les appliquer un par un. Un mot cependant : si un de ces patchs engendre des conflits, replay va s'arrêter là et vous laisser corriger les conflits. Vous pourrez alors reprendre où replay s'était arrêté en relançant replay une seconde fois.

Comment ça marche ? -- replay

Si vous avez suivi tout le tutoriel jusqu'ici, le fonctionnement de replay devrait être tout simplement évident. En fait, c'est exactement comme nous l'avons décrit au dessus. replay utilise missing pour trouver quels sont les changements manquants à votre arborescence, get-changeset pour récupérer ces changesets, et dopatch pour les appliquer. Il y a pas mal « d'écritures » à effectuer pour faire ça -- et ces « écritures » sont automatiquement effectuées pour vous par replay.

Sélection des fichiers à archiver (commit)

Précédement, vous avez appris à archiver tous les changements d'une arborescence d'un coup (voir: « Archiver les changements »)

Vous avez aussi lu à quel point il est important de faire des changesets « propres » (voir: « Bien archiver (commit) -- L'idée d'un changeset propre »)

Ce chapitre vous montre une petite astuce que vous pouvez utiliser dans une situation bien spécifique mais assez courante.

La petite correction express

Supposons que sur un projet conséquent vous ayez une grande arborescence et que vous êtes en train d'effectuer des changements complexes. Vous avez modifié de nombreux fichiers, mais il y en a beaucoup d'autres qui n'ont pas été touchés.

Subitement, vous vous rendez compte d'un bug trivial sur un fichier non modifié.

Ce que vous aimeriez faire c'est :

  1. Arrêter et réparer ce bug trivial
  2. Archiver cette correction triviale.
  3. Retourner travailler sur les changements complexes.

Comment pouvez-vous faire ?

La solution brutale pour la petite correction express

Il y a une solution « brutale » à ce problème.

Tout simplement :

Récupérer une copie fraiche de la dernière révision

En d'autre termes créer une seconde arborescence sans vos changements.

Réparer le bug trivial dans cette nouvelle arborescence puis archiver

Maintenant vous avez archivé un changement propre avec la correction du bug trivial uniquement.

Utilisez update ou replay pour mettre à jour votre arborescence originale

Ce qui va ajouter la correction du bug trivial à votre arborescence en cours.

Ça fonctionne, mais c'est un petit peu exagéré. Avez-vous réellement besoin de créer une nouvelle arborescence rien que pour réparer ce bug trivial ?

Parfois cette exagération est méritée. Par exemple, si votre projet à comme politique de lancer quelques tests avant chaque archivage. Dans ce cas, oui, vous devez réellement créer une nouvelle arborescence.

Parfois cette éxagération est indispensable. Par exemple, si la réparation du bug oblige à modifier des fichiers que vous avez déjà modifié, alors, la solution brutale sera l'approche la plus simple (mais quand même, jettez un oeuil à tla undo --help et tla redo --help).

Mais il y a une solution plus simple qui peut parfois être utilisée :

Résoudre la petite correction express avec commit

Comme nous le verrons, commit permet de ne prendre en compte que les changements de quelques fichiers.

Si votre correction ne modifie que les fichers file-a.c et file-b.c, alors après avoir préparé un message de log, vous pourrez archiver uniquement ces fichiers :

% tla commit -- file-a.c file-b.c

Notez que les fichiers archivés de cette manière ne doivent pas être des nouveaux fichiers, même si ces fichiers ont été renomés, le commit ne va enregistrer que les modifications internes de ces fichiers, pas les renomages.

La correction express -- Il y a plusieurs façons de faire

Dans le texte précédent, nous avons étudié une solution « brutale » au problème de la correction express qui impliquait de récupérer la totalité de l'arborescence du projet.

Deux autres commandes, tla undo et tla redo, fournissent une autre alternative « brutale » avec quelques avantages. (Essayez tla undo --help et tla redo --help)

Branches élémentaires -- Gérer les changements privés

Dans ce chapitre, nous allons commencer à explorer le concept de branche auquel vous êtes peut-être habitué avec d'autres gestionaire de révisions.

Si vous êtes déjà familiarisé avec ce concept, soyez conscient que le système de branche dans arch va certainement beaucoup plus loin que ce à quoi vous êtes habitué.

Que vous soyez ou non familiarisé avec ce concept, n'ayez crainte -- nous allons démarrer doucement.

Un scénario de branche -- Le besoin de changements privés

Supposons pour le moment que le projet hello-world diffuse ses sources au public, sur un miroir en lecture seule (voir « Archives privées et publiques »).

Précédement, vous (une personne extérieure au projet hello-world) décidez que vous voulez utiliser leur programme, mais que vous avez besoin d'y apporter quelques modifications localement.

Comme exemple ludique, supposons que vous ayez décidé que dans votre environnement, dire « hello world » n'est pas acceptable -- vous avez réellement besoin d'une ponctuation correcte « hello, world ».

Maintenant, voici le problème : bien sûr, vous pouvez télécharger leurs sources et effectuer cette modification. Mais pendant ce temps, le projet continue. Ils continuent d'effectuer des changements. Vous serez donc perpétuellement dans l'obligation de télécharger les sources les plus récents et d'y appliquer vos modifications.

Ce chapitre va expliquer comment arch vous aidera à automatiser cette tache.

Création d'une branche dans une archive locale, à partir d'un projet extérieur

Dans l'exemple qui suit, vous aller changer de rôle. À la place de « jouer » Alice et Bob, les programmeurs du projet hello-world, vous aller « jouer » le rôle de Candice : une troisième personne.

Commençons par donner à Candice sa propre archive, et en faire son archive par défaut :

% tla make-archive candice@candice.net--2003-candice \
     ~/{archives}/2003-candice

% tla my-default-archive candice@candice.net--2003-candice
default archive set (candice@candice.net--2003-candice)

(Vous pouvez voir ce que ces commandes font en lisant « Création d'une nouvelle archive ».)

Candice a besoin de créer un projet hello-world dans sa propre archive. Elle peut utiliser :

% tla archive-setup  hello-world--candice--0.1

Elle n'a pas besoin d'utiliser le même nom de projet qu'Alice et Bob, en fait, dans notre cas elle choisit un non de branche différent. (Pour revoir ces commandes, voir « Création d'un nouveau projet ».)

Quand Alice et Bob créaient leurs archive, ils utilisaient import pour créer la première révision. Vu que nous allons créer une branche, nous allons utiliser une commande différente:

% tla tag \
    lord@emf.net--2003-example/hello-world--mainline--0.1--patch-1 \
    hello-world--candice--0.1
[....]

Certaines choses méritent d'être notées à propos de cette commande.

Premièrement, notez que nous utilisons un nom de révision complet pour se référer à la révision patch-1 d'Alice et Bob. Du fait que cette révision ne se trouve pas dans l'archive par défaut. (voir « Travailler avec plusieurs archives en même temps ».)

Notez que nous avons spécifié explicitement patch-1 comme révision. Si nous n'avions pas indiqué le suffixe patch-1, la commande tag aurait déduit que nous voulions la dernière révision de l'archive d'Alice et Bob (qui se trouve être le patch-3).

Quel effet a eu tag ?

Après avoir utilisé tag, Candice a maintenant une nouvelle révision dans son archive :

% tla revisions --summary hello-world--candice--0.1
base-0
    tag of lord@emf.net--2003-example/hello-world--mainline--0.1--patch-1

Elle peut récupérer cette révision de la manière habituelle :

% tla get hello-world--candice--0.1 hw-candice
[...]

% ls hw-candice
hw.c                main.c          {arch}

Friandise de arch

Si vous avez bien suivi, vous remarquerez que Candice a créé une branche dans son archive à partir d'une révision stockée dans une autre archive. Dans notre exemple, il se trouve que toutes ces archives sont sur le même sytème de fichier mais ce n'est pas indispensable : Candice aurait très bien pu créer sa branche même si elle devait accéder à celle d'Alice et Bob à travers le réseau.

Mise en cache d'une révision issue d'un tag

Candice a utilisé tag pour créer une branche à partir de l'archive d'Alice et Bob. Dans ce cas, arch a créé ce que l'on appelle une révision en cache de la révision base-0 du dépôt de Candice (qui, ayant été créée avec tag, est identique à lord@emf.net--2003-example/hello-world--mainline--0.1--patch-1.) C'est le fonctionnement par défaut de tla à présent; cependant l'ancien comportement (pas de mise en cache des révisions) peut être reproduit en spécifiant l'option --no-cacherev de tla tag.

Que ce serait-il passé sans la mise en cache de la révision ?

Lorsque Candice a utilisé get pour récupérer cette révision, arch a pris en compte le fait qu'il s'agissait d'une branche, et donc -- en l'absence de cachedrev -- il serait allé chercher les sources dans l'archive d'Alice et Bob.

la question apparaît : que ce serait-il passé si l'archive d'Alice et Bob « n'était plus » ? En l'occurence, ce qui se serait passé, c'est que Candice n'aurait pas pu faire de get de sa branche.

Elle aurait pu s'en sortir, à la main en mettant en cache dans son archive toutes les informations nécessaires pour construire sa révision, c'est à dire un cachedrev :

% tla cacherev hello-world--candice--0.1--base-0
[...]

et confirmer que ça a marché avec :

% tla cachedrevs hello-world--candice--0.1
hello-world--candice--0.1--base-0

Ainsi, arch n'aurait plus besoin de compter sur l'archive d'Alice et Bob pour récupérer la révision base-0.

Explorer la nouvelle branche

Précédement, Candice créait sa branche et utilisait get pour la récupérer. Examinons cette arborescence :

% cd ~/wd/hw-candice

% tla log-versions
candice@candice.net--2003-candice/hello-world--candice--0.1
lord@emf.net--2003-example/hello-world--mainline--0.1

Notez que l'arborescence de Candice contient à la fois les logs de patchs des version d'Alice et Bobs, et à la fois ceux de sa propre branche :

% tla logs --summary \
        lord@emf.net--2003-example/hello-world--mainline--0.1
base-0
    initial import
patch-1
    Fix bugs in the "hello world" string


% tla logs --summary hello-world--candice--0.1
base-0
    tag of \
    lord@emf.net--2003-example/hello-world--mainline--0.1--patch-1

Il n'y a plus de changements sur la branche de Candice :

% tla missing hello-world--candice--0.1
[no output]

mais souvenez vous qu'Alice et Bob en sont déjà au patch-3 :

% tla missing -A lord@emf.net--2003-example \
    hello-world--mainline--0.1
patch-2
patch-3

Effectuer une modification locale

Après le tag initial, Candice peut archiver les changements de sa branche avec la méthode habituelle.

Supposons maintenant qu'elle ait modifié hw.c qui contient donc en partie :

% cat hw.c
[...]
void
hello_world (void)
{
  (void)printf ("hello, world\n");
}
[...]

et qu'elle a préparé un message de log :

% cat ++log.hello-world--candice--0.1--lord@emf.net--2003-candice
Summary: Punctuated the output correctly
Keywords: 


This program should say "hello, world" not "hello world".

Maintenant elle peut archiver de la manière habituelle, en créant sa propre révision patch-1 :

% tla commit
[....]

% tla revisions --summary hello-world--candice--0.1
base-0
    tag of \
    lord@emf.net--2003-example/hello-world--mainline--0.1--patch-1
patch-1
    Punctuated the output correctly

Mise à jour à partir d'une version issue d'une branche

Entre temps, Alice et Bob ont créé leurs révisions patch-2 et patch-3. Comment Candice peut-elle appliquer ces changements à sa branche ?

À ce niveau, arch fournit réellement beaucoup de techniques. En utilisant les commandes précédement étudiées, elle pourait utiliser soit update soit replay. Dans cet exemple, nous allons montrer l'utilisation de replay.

% cd ~/wd/hw-candice

% tla replay -A lord@emf.net--2003-example \
        hello-world--mainline--0.1
[...]

Notez que nous avons utilisé l'argument -A pour indiquer de quelle archive nous allons récupérer les changements, et le nom de la version pour indiquer quels changements nous voulons. Dans ce cas, replay appliquera les changesets de patch-2 et patch-3 à l'arborescence de Candice.

Cette utilisation de replay est une forme de « fusion » (merging) : Les changements locaux de Candice ont été fusionnés avec les changements d'Alice et Bob sur leur mainline.

Note

Si vous avez suivi les exemples, vous devriez examiner hw.c et noter que les changements de Candice sur la chaine de printf et l'ajout de la notice copywrong sont toutes les deux incluses.

Note

Vous devriez également récupérer une deuxième copie de la révision patch-1 et expérimenter en effectuant la même fusion en utilisant update au lieu replay. Vous devriez regarder tla update -help et voir ainsi quelles sont les options et arguments adéquats.

Notez également que, jusqu'à présent, nous avons seulement appliqué les changements sur l'arborescence du projet de Candice -- ceux-ci n'ont pas été enregistrés dans l'archive de Candice. Pour enregistrer la fusion dans son archive, elle devra créer un message de log et archiver par la méthode habituelle (voir « Archiver les changements »).

Il y a, cependant, encore une convenance à respecter. Quand Candice écrit son message de log, elle voudra vraisemblablement noter qu'il y a eu une fusion et ce qu'elle a entraîné. Arch comporte une commande dont la sortie est idéale pour ce message de log :

% cd ~/wd/hw-candice

% tla log-for-merge
Patches applied:

  * lord@emf.net--2003-example/hello-world--mainline--0.1--patch-3
     added copywrong statements

  * lord@emf.net--2003-example/hello-world--mainline--0.1--patch-2
     commented return from main

Comment ça marche ? -- tag et les branches élémentaires

Qu'a fait tag ? Regardons l'archive de Candice :

% cd ~/{archives}
% cd 2003-candice
% cd hello-world
% cd hello-world--candice
% cd hello-world--candice--0.1

% ls
+version-lock       base-0          patch-1
patch-2

Ce qui nous intéresse ici, c'est la révision base-0 -- celle créée par le tag:

% cd base-0

% ls
CONTINUATION
hello-world--candice--0.1--base-0.patches.tar.gz
hello-world--candice--0.1--base-0.tar.gz
log

% cat CONTINUATION
lord@emf.net--2003-example/hello-world--mainline--0.1--patch-1

Le fichier CONTINUATION identifie que cette révision provient d'un tag. Son contenu nous indique de quelle révision nous avons créé la branche.

Le changeset de cette révision (...patches.tar.gz) a aussi été créé par le tag. Si vous examinez ce changeset (souvenez-vous de get-changeset et de show-changeset) vous verrez que la seule chose qu'il fait est d'ajouter un log dans la liste des patch-logs.

Le fichier source (...base-0.tar.gz) a été créé par archive-cache-revision. Il contient une copie complete de la révision base-0 de Candice. Étant donné que ce fichier est là, get n'est pas obligé d'aller voir dans l'archive d'Alice et Bob pour construire cette révision.

Patch-logs et l'historique de l'arborescence d'un projet

Dans le chapitre précédent, nous avons commencé à apprendre le système de branche et de fusion. Nous avons vu comment des commandes comme missing, update, et replay pouvaient être utilisées pour surveiller et appliquer les changements des multiples branches d'un projet.

Dans ce chapitre, nous allons étudier quelques aspects des « patch logs » : le mécanisme utilisé pour surveiller l'historique de l'arborescence d'un projet, y compris la partie de l'historique qui est utilisée pour effectuer des fusions de manière intelligente.

Souvenez-vous d'abord lorsque nous avons rencontré les patch-logs dans les précédents chapitres (par exemple, lors de l'initialisation d'un projet, dans « Démarrer un nouveau projet »). Dans ce chapitre, les patch-logs seront expliqués en profondeur.

L'arborescence d'un projet contient des patch-logs

Souvenez vous qu'à chaque importation initiale, révision de tag, et révision de changeset dans une archive, un message de log est associé. Ce message consite en un entête et un texte que vous envoyez dans des commandes telles que import et commit, en plus d'autres entêtes générés automatiquement par arch.

Lorsqu'un projet est importé pour la première fois dans une archive, le patch-log de cette nouvelle révision est ajouté à l'arborescence. Quand un commit est effectué, lors d'une opération d'archivage, le log de la nouvelle révision est ajouté à l'arborescence. Si vous récupérez (get) une révision créée par la commande tag, vous verrez qu'il contient également un patch-log pour cette révision de tag.

Les patch-logs s'accumulent. Ainsi, par exemple, chaque commit ajoute un nouveau log et tout les logs précédents sont préservés. Chaque révision de tag inclus non seulement le log de ce tag, mais aussi tous les logs hérités de la révision « taggés »

Revenons à nos exemples précedents, regardons la révision patch-2 de Alice et Bob :

% cd ~/wd

[... supprimer les répertoires des exemples précédents ...]

% tla get -A lord@emf.net--2003-example \
            hello-world--mainline--0.1--patch-2 \
            hw-AnB-2

[...]

% cd ~/hw-AnB-2

Premièrement, notons que les patch-logs sont triés par le nom arch de la version. Cette arborescence a des logs pour une version seulement :

% tla log-versions
lord@emf.net--2003-example/hello-world--mainline--0.1

Dans cette version, il y a des logs pour l'importation initiale, et deux changesets :

% tla logs -A lord@emf.net--2003-example \
              --summary \
              hello-world--mainline--0.1
base-0
    initial import
patch-1
    Fix bugs in the "hello world" string
patch-2
    commented return from main

Examinons un de ces logs en particulier :

% tla cat-log -A lord@emf.net--2003-example \
                hello-world--mainline--0.1--patch-2
Revision: hello-world--mainline--0.1--patch-2
Archive: lord@emf.net--2003-example
Creator: Tom (testing) Lord <lord@emf.net>
Date: Wed Jan 29 12:46:50 PST 2003
Standard-date: 2003-01-29 20:46:50 GMT
Summary: commented return from main
Keywords: 
New-files: \
  {arch}/[...]/hello-world--mainline--0.1/[...]/patch-log/patch-2
Modified-files: main.c
New-patches: \
  lord@emf.net--2003-example/hello-world--mainline--0.1--patch-2

Added a comment explaining how the return from `main'
relates to the exit status of the program.

nous pouvons voir, par exemple, que le changeset patch-2 modifie le fichier main.c et ajoute un nouveau fichier, le log lui-même (dont le nom est tronqué dans l'exemple affiché ci-dessus).

D'autres exemples méritent d'être étudiés sur l'arborescence de Candice. Souvenez-vous qu'elle a utilisé tag pour créer une branche (« fork ») à partir de la révision patch-1 d'Alice et Bob. Ainsi nous voyons :

% cd ~/wd

% tla get -A candice@candice.net--2003-candice \
            hello-world--candice--0.1--patch-2 \
            hw-C-0

[...]

% cd ~/hw-C-0

% tla log-versions
candice@candice.net--2003-candice/hello-world--candice--0.1
lord@emf.net--2003-example/hello-world--mainline--0.1

% tla logs  -A lord@emf.net--2003-example \
                --summary \
                hello-world--mainline--0.1
base-0
    initial import
patch-1
    Fix bugs in the "hello world" string


% tla logs  -A candice@candice.net--2003-candice \
                --summary \
                hello-world--candice--0.1
base-0
    tag of \
      lord@emf.net--2003-example/hello-world--mainline--0.1--patch-1

Comment ça marche ? -- missing (ce qui manque)

Dans les chapitre précédents, vous avez appris comment la commande missing pouvait vous indiquer les modifications archivées, mais pas encore présentes dans une arborescence donnée (voir « Étudions pourquoi Alice ne peut pas faire de commit (archiver) »).

Il devrait être assez facile de comprendre comment ces commandes fonctionnent. Arch peut trouver la liste de toutes les révisions d'une version donnée en utilisant la commande revisions :

% tla revisions -A lord@emf.net--2003-example \
                  hello-world--mainline--0.1
base-0
patch-1
patch-2
patch-3

Ce sont les logs de l'archive. Arch peut trouver la liste des révisions pour laquelle le projet a des logs avec logs :

% tla logs -A lord@emf.net--2003-example \
               hello-world--mainline--0.1
base-0
patch-1
patch-2

La différence entre ces deux listes est le résultat de missing :

% tla missing -A lord@emf.net--2003-example \
              hello-world--mainline--0.1
patch-3

Le concept d'historique des changements et l'ascendance du projet

Les patch-logs indiquent des informations pertinentes sur l'historique d'une arborescence. Il y a deux visions qui méritent d'être mentionnées : l'« historique des changements », et « l'ascendance d'un projet ».

Historique des changements

Lorsqu'une arborescence a un log pour le commit d'un changeset, cela signifie que les modifications de ce commit ont été appliquées à l'arborescence : le commit de ce changeset fait partie de l'« l'historique des changements » de cette arborescence. Si le changeset était une correction de bogue, par exemple, c'est une indication comme quoi ce bogue a été corrigé dans cette arborescence.

Note

Le fait qu'un changeset donné fasse partie de l'historique des changement de cette arborescence n'est pas une preuve absolue que les modifications de ce changeset sont présentes dans l'arborescence. Par exemple, ces changements ont pu être annulés par un changement ultérieur. Néamoins, l'historique des changements d'une arborescence est un outil utile pour explorer et comprendre son état.

Ascendance d'un projet

De manière informelle, nous dirons qu'une révision archivée est une « ascendance » d'un projet donné si elle a des patch-logs pour toutes les révisions de la version de cette révision archivée jusqu'à la version archivée elle-même.

Ainsi, par exemple, la révision de Candice issue du tag a la révision patch-1 d'Alice et Bob comme un ascendant puisqu'elle a les logs des révisions d'Alice et Bob :

base-0
patch-1

Et la révision patch-2 de Candice, qui fusionne les changements patch-2 et patch-3 d'Alice et Bob, a l'ensemble de ces révisions comme ascendants (voir « Mise à jour à partir d'une version issue d'une branche »).

Changelogs automatiques

La commande tla changelog génère un ChangeLog dans le style GNU à partir d'un patch-log :

% cd ~/wd

% tla get -A candice@candice.net--2003-candice \
            hello-world--candice--0.1 \
            hw-C-latest
[....]

% cd ~/wd/hw-C-latest

% tla changelog
# do not edit -- automatically generated by arch changelog
# arch-tag: automatic-ChangeLog-- [...]
#

2003-01-30 GMT  Tom (testing) Lord <lord@emf.net>       patch-2

    Summary:
      merge from mainline sources
    Revision:
      hello-world--candice--0.1--patch-2

    Patches applied:
    
      * lord@emf.net--2003-example/hello-world--mainline--0.1--patch-3
         added copywrong statements
    
      * lord@emf.net--2003-example/hello-world--mainline--0.1--patch-2
         commented return from main
    

    new files:
     {arch}/ [...] /hello-world--mainline--0.1 [...] /patch-2
     {arch}/ [...] /hello-world--mainline--0.1 [...] /patch-3

    modified files:
     hw.c main.c

    new patches:
     lord@emf.net--2003-example/hello-world--mainline--0.1--patch-2
     lord@emf.net--2003-example/hello-world--mainline--0.1--patch-3


2003-01-30 GMT  Tom (testing) Lord <lord@emf.net>       patch-1

    Summary:
      Punctuated the output correctly
    Revision:
      hello-world--candice--0.1--patch-1

    
    This program should say "hello, world" not "hello world".
    

    modified files:
     hw.c


2003-01-30 GMT  Tom (testing) Lord <lord@emf.net>       base-0

    Summary:
      tag of lord@emf.net--2003-example/hello-world--mainline--0.1--patch-1
    Revision:
      hello-world--candice--0.1--base-0

    (automatically generated log message)
    

    new patches:
     lord@emf.net--2003-example/hello-world--mainline--0.1--base-0
     lord@emf.net--2003-example/hello-world--mainline--0.1--patch-1

Notez que ce ChangeLog généré inclut un tagline. Si vous sauvegardez la sortie de cette commande changelog dans une arborescence, soit en utilisant un tagline ids ou en lui donnant un identifiant explicite qui correspond au tagline id, une commande telle que commit va automatiquement conserver le ChangeLog à jour.

Développement multi-branches -- La coopération sous forme de fusion en étoile (star-merge)

Dans les chapitres précédents, nous avons développé un exemple sur le projet hello-world.

Alice et Bob, les premiers programmeurs du projet, créaient une archive et quelques révisions.

Candice, un utilisateur du projet, créait sa propre archive, démarrait une branche du projet hello-world, et commençait à maintenir ses propres modifications locales.

Dans ce chapitre, nous allons commencer à étudier une situation plus typique et réaliste des projets libres. Ici, nous considèrerons Alice et Bob comme les responsables du projet public, et Candice comme une contributrice importante du projet. Nous allons identifier les besoins en gestion de révisions engendrés par cette organisation, et regarder quelques commandes arch qui pourrons nous aider à les satisfaire.

Passer de branches élémentaires à un développement multi-branches

Jusqu'ici, si vous avez suivis les exemples, Candice a une branche élémentaire. Elle a créé une branche à partir de la branche principale (mainline), effectué quelques modifications, et a gardé sa branche à jour par rapport à la branche principale d'Alice et Bob.

Nous supposons, maintenant, qu'Alice et Bob veulent fusionner les modifications d'Alice dans la branche principale.

Bien, ce travail de fusion a déjà été effectué. La dernière révision de Candice est exactement ce qu'Alice et Bob souhaitent. Ils peuvent incorporer cette fusion dans leur branche principale très simplement, en enregistrant la dernière révision de Candice dans leur propre branche principale :

% tla get -A candice@candice.net--2003-candice \
            hello-world--candice--0.1 \
            hw-C
[...]


% cd hw-C

% tla set-tree-version -A lord@emf.net--2003-example \
            hello-world--mainline--0.1

% tla make-log
++log.hello-world--mainline--0.1--lord@emf.net--2003-example

[... edit log file (consider `tla log-for-merge') ... ]

% cat ++log.hello-world--mainline--0.1--lord@emf.net--2003-example
Summary: merge from Candice's Branch
Keywords: 

Patches applied:

  * candice@candice.net--2003-candice/hello-world--candice--0.1--patch-2
     merge from mainline sources

  * candice@candice.net--2003-candice/hello-world--candice--0.1--patch-1
     Punctuated the output correctly

  * candice@candice.net--2003-candice/hello-world--candice--0.1--base-0
     tag of 
      lord@emf.net--2003-example/hello-world--mainline--0.1--patch-1

% tla commit
[....]

Note

Notez attentivement l'« astuce » que nous avons utilisée. La dernière révision d'Alice était exactement ce qu'Alice et Bob souhaitaient -- ils ont associés un get avec un set-tree-version pour faire en sorte que l'arborescence de Candice puisse facilement être archivée dans leur branche principale.

Développement multi-branches, simple

Considérons ce qui ce passe lorsqu'un développement se poursuit sur les deux branches. Pour cela, nous allons introduire une nouvelle chose : un système de diagramme composé des branches et des fusions entre elles.

Après nos exemples, nous avons cette situation :

mainline--0.1                    candice--0.1
-------------                    ------------
  base-0             -----------> base-0 (a tag)
  patch-1  ---------'             patch-1
  patch-2             ----------> patch-2
  patch-3  ----------'  --------'
  patch-4  <-----------'

qui nous indique que la branche candice est un tag du patch-1 provenant de la branche principale; qu'au niveau du patch-2 de la branche candice, il y a eu une fusion de tous les patchs de la mainline jusqu'au patch-3; et finalement que le patch-4 fusionne tout jusqu'au patch-2 de la branche candice.

Lorsque nous avons un diagramme dans lequel aucune des lignes de fusion ne se croisent, on considère que c'est un « développement multi-branches, simple ».

La signification d'un développement multi-branches, simple, est qu'il s'agit d'un model où deux équipes peuvent travailler d'une manière asynchrone sur un même projet. Dans chaque équipe -- dans chaque branche -- les programmeurs utilisent un style de coopération basé sur update/commit (voir « Le style de coopération update/commit »). Ainsi, les changements dans chaque branches n'ont aucun effet sur les autres jusqu'à la fusion des deux branches.

Introduction au problème de fusion du développement multi-branches

Supposons qu'il y ait beaucoup plus de travaux dans chacune des branches mainline et candice, nous amenant à ça :

mainline--0.1                    candice--0.1
-------------                    ------------
  base-0             -----------> base-0 (a tag)
  patch-1  ---------'             patch-1
  patch-2             ----------> patch-2
  patch-3  ----------'  --------' patch-3
  patch-4  <-----------'          patch-4
  patch-5
  patch-6


  % tla revisions --summary -A candice@candice.net--2003-candice \
                    hello-world--candice--0.1
  base-0
      tag of 
      lord@emf.net--2003-example/hello-world--mainline--0.1--patch-1
  patch-1
      Punctuated the output correctly
  patch-2
      merge from mainline sources
  patch-3
      added a period to output string
  patch-4
      capitalized the output string



  % tla revisions --summary -A lord@emf.net--2003-example \
                    hello-world--mainline--0.1
  base-0
      initial import
  patch-1
      Fix bugs in the "hello world" string
  patch-2
      commented return from main
  patch-3
      added copywrong statements
  patch-4
      merge from Candice's Branch
  patch-5
      fixed the copyrwrong for hw.c
  patch-6
      fixed the copyrwrong for main.c

Considérons le scénario suivant dans lequel notre but est de fusionner les nouveaux travaux de la branche mainline dans la branche candice. En d'autres termes, nous voulons arriver à ça :

mainline--0.1                    candice--0.1
-------------                    ------------
  base-0             -----------> base-0 (a tag)
  patch-1  ---------'             patch-1
  patch-2             ----------> patch-2
  patch-3  ----------'  --------' patch-3
  patch-4  <-----------'          patch-4
  patch-5               --------> patch-5
  patch-6  ------------'

Comment pouvons-nous effectuer cette fusion ? Démarrons après la dernière révision après la fusion de candice (patch-4) :

% tla get -A candice@candice.net--2003-candice \
               hello-world--candice--0.1--patch-4 \
               hw-C-4
[....]

% cd hw-C-4

Voici deux techniques qui ne marchent pas :

replay ne résoud pas le problème de fusion du développement multi-branches

replay va essayer d'appliquer tous les changements qui manquent (missing) de mainline dans l'arborscence candice. La liste des changesets est indiquée :

% tla missing --summary \
              -A candice@candice.net--2003-example \
              hello-world--mainline--0.1
patch-4
    merge from Candice's Branch
patch-5
    fixed the copyrwrong for hw.c
patch-6
    fixed the copyrwrong for main.c

Le problème est que le patch-4 apparaît dans la liste. C'est une fusion qui inclut tous les changements de la branche candice jusqu'au niveau de son patch-2. Mais ces changements sont déjà présents dans la révision patch-4 de la branche candice -- ainsi replay va les appliquer plusieurs fois (ce qui va créer un conflit de patch).

Attention!

La commande replay ne vous empêchera pas d'effectuer plusieurs replay même si l'arborescence n'est plus dans un état cohérent. tla dans son état actuel ne fusionne pas les fichiers de rejet. Cela laisse ouvert la possibilité de perdre des rejets de patchs si un second replay est opéré avant que les rejets ne soit traités. (Un jour tla sera capable de fusionner plusieurs rejets en un seul).

Note

Pour les utilisateurs avancés

La commande replay a une option qui nous permet d'éviter le patch-4 de la branche principale. Cela résoudrait le problème, mais il y a quelques inconvénients. Premièrement, ça signifie que le patch-4 continuera d'être signalé comme manquant par la commande missing de la branche candice. Deuxièmement, rien ne nous garantit que le changeset patch-4 contient seulement la fusion de la branche candice. Si Alice et Bob ont effectués d'autres changements dans patch-4, et que nous évitons ce changeset, ces changements seront perdus.

update ne résoud pas le problème de fusion du développement multi-branches

Supposons que nous essayons d'utiliser update à partir de la branche mainline. Souvenez-vous que update va créer un changeset entre le plus jeune ascendant de mainline jusqu'à l'arborescence actuelle, et ensuite appliquer ce changeset à la dernière révision de mainline.

Nous avons une notation pour cela. Un changeset entre X et Y est indiqué :

delta(X, Y)

Dans ce cas, update va démarrer en créant un changeset entre la révision patch-3 de mainline et notre arborescence :

delta(mainline--0.1--patch-3, hw-C-4)

L'arborescence résultante à l'application du changeset de X à Y vers l'arborescence Z est indiquée :

delta(X, Y) [ Z ]

En d'autres termes, le résultat de la commande update dans notre exemple peut être décrite comme cela :

delta(mainline--0.1--patch-3, hw-C-4) [mainline--0.1--patch-6]

Cependant, il y a un problème. La révision patch-3 de mainline n'a pas été fusionnée avec la branche de candice. Ainsi, le changeset

delta(mainline--0.1--patch-3, hw-C-4)

va inclure, en plus des autres changements, les modifications entre patch-1 et patch-2 de la branche candice.

Malheureusement, l'arborescence sur laquelle le changeset sera appliquée, mainline--0.1--patch-6, a déjà été fusionnée avec base-0...patch-2 de la branche candice.

Comme avec replay, update va entraîner des conflits de fusion en effectuant des changements redondants.

Solution d'un cas du problème de développement multi-branches

En utilisant notre notation delta et nos diagrammes de fusions, regardons comment résoudre ce problème de fusion proprement.

Souvenez-vous que nous avons actuellement :

mainline--0.1                    candice--0.1
-------------                    ------------
  base-0             -----------> base-0 (a tag)
  patch-1  ---------'             patch-1
  patch-2             ----------> patch-2
  patch-3  ----------'  --------' patch-3
  patch-4  <-----------'          patch-4
  patch-5
  patch-6

et notre but est de créer une nouvelle fusion, pour le patch-5 de la branche de Candice :

                      --------> patch-5
patch-6  ------------'

Nous pourrions décider de démarrer avec la branche mainline et fusionner les changements de candice manquants, ou démarrer avec l'arborescence candice et fusionner les changements manquants de mainline. Prenons le dernier (fusion dans l'arborescence de candice).

Dans ce cas, la révision patch_6 de mainline-0.1 est « à jour » par rapport à la révision patch-2 de candice-0.1. Nous voulons appliquer tous les changements de cette révision jusqu'à la dernière révision de candice :

avec:
        ascendant := candice--0.1--patch-2
        fusion_dans := mainline--0.1--patch-6
        résultat   := candice--0.1--patch-4

réponse := delta(ascendant, fusion_dans)[résultat]

Les flèches dans le diagramme de fusion sont décisives pour obtenir la bonne réponse. Par exemple, supposons que la flèche du patch-2 de Candice vers la révision patch-4 de mainline ne soit pas là. Dans ce cas la réponse aurait été :

avec:
        ascendant := mainline--0.1--patch-3
        fusion_dans := mainline--0.1--patch-6
        résultat   := candice--0.1--patch-4

réponse := delta(ascendant, fusion_dans)[résultat]

Tracer les flèches pour une fusion donnée est un travail pénible. Il est automatique avec la commande star-merge :

star-merge -- Solution pour le problème de la fusion d'un développement multi-branches en général

Cela dépasserait le cadre de ce tutoriel d'expliquer la solution complète du problème de fusion d'un développement multi-branches en général. Les deux solutions montrées plus haut illustrent deux cas, mais des solutions légèrement différentes sont parfois nécessaires.

Ce que vous devriez savoir c'est que lorsque vous avez un développement multi-branches, simple (voir « Développement multi-branches, simple »), la commande star-merge sait fusionner les branches sans déclencher de faux conflits.

% tla get -A candice@candice.net--2003-candice \
        hello-world--candice--0.1--patch-4 \
        merge-temp

% tla star-merge lord@emf.net--2003/hello-world--mainline--0.1

Étiquettes symboliques

Lorsqu'un projet grossit et devient plus complexe, il est souvent utile de pouvoir donner un nom symbolique à une révision particulière de l'archive.

Par exemple, supposons que le projet hello-world ait de nombreuses révisions :

mainline
--------
base-0
patch-1
patch-2
....
patch-23

Il est possible qu'en cours de développement des publications de « snapshots » soient effectuées à partir de la mainline. Toutes les révisions ne deviennent pas des « snapshots », mais certaines oui.

Il serait intéressant d'indiquer une étiquette aux révisions qui deviennent des « snapshots » :

mainline
--------
base-0
patch-1         snapshot 0
patch-2
....
patch-12        snapshot 2
....
patch-23        snapshot 3

La commande tag introduite précédement, peut être utilisée à cette fin. (voir « Créer une branche d'un projet distant dans une archive locale »)

La première fois que nous avons rencontré la commande tag, c'était pour créer la révision base_0 d'une branche élémentaire. Elle peut également être utilisée pour créer une branche pour toutes les révisions ayant une étiquette.

Supposons que nous voulions créer une branche appelée hello-world--snapshots--0.1. Sous forme de diagrame, nous aurrons :

mainline                        snapshots
--------                        ---------
base-0                --------> base-0 (tag)
patch-1 -------------'  ------> patch-1 (tag)
patch-2                '
....                  '
patch-12 ------------'
....
patch-23

Pour créer l'étiquette snapshot pour le patch-23:

% tla tag hello-world--mainline--0.1--patch-23 \
            hello-world--snapshots--0.1

après quoi nous aurons :

mainline                        snapshots
--------                        ---------
base-0                --------> base-0 (tag)
patch-1 -------------'  ------> patch-1 (tag)
patch-2                ' -----> patch-2 (tag)
....                  ' '
patch-12 ------------' '
....                  '
patch-23 ------------'

En effet, la branche snapshots est une sorte de nom symbolique avec un historique. Nous pouvons récupérer la dernière révision nommée par ce symbole avec :

% tla get hello-world--snapshots--0.1

et les révisions précédentes par le nom spécifique de leurs révisions, ex. :

% tla get hello-world--snapshots--0.1--patch-1

Attention!

En général, vos branches devraient être soit des branches basées sur commit (toutes les révisions après base-0 sont créées par commit), soit des branches basées sur tag (toutes les révisions sont créées par tag). Les commandes telles que replay, update, et star-merge sont basées sur la présomption que vous suivez cette règle. Même s'il peut être tentant, dans d'obscures circonstances, de mélanger commit et tag sur une même branche -- c'est généralement peu recommandable.

Cueillette de changements ( cherrypicking )

Jusqu'à présent nous avons appris à coordonner, d'une manière asynchrone, des projets basés sur des branches élémentaires issues d'une seule branche principale. (voir « Branches élémentaires -- Gérer les changements privés » et « Développement multi-branche -- La coopération sous forme de fusion en étoile »)

Dans ce chapitre, nous évoquerons sommairement une troisième forme de branche utile quand le projet est constitué de multiples branches ( « fork ») de même niveau.

Supposons, d'une manière abstraite, que la branche principale d'Alice et Bob ait pris de l'ampleur :

mainline
--------
base-0
patch-1
....
patch-23
patch-24
patch-25
...
patch-42

À moment donné, peut-être à cause de controverse dans les choix fait par la branche mainline, un nouveau développeur, Derick, déclare une scission (« fork ») et démarre sa propre branche :

mainline                derick
--------                ------
base-0          ------> base-0
patch-1        '
....          '
patch-23 ----'
patch-24
patch-25
...
patch-42

On sait déjà que Derick peut utiliser update ou replay pour rester à jour avec la branche principale, mais s'il ne veut pas ? Comment faire si Derick veut les changements du patch-25 et patch-42, mais pas les autres patchs postérieurs au patch-23 de mainline ?

Derick peut appliquer des changements spécifiques de mainline en spécifiant la révision exacte qu'il souhaite, plutôt que de spécifier une version :

% cd ~/wd

% tla get hello-world--derick--0.1 derick

% cd derick

% tla replay -A lord@emf.net--2003-example \
         hello-world--mainline--0.1--patch-23

% tla replay -A lord@emf.net--2003-example \
         hello-world--mainline--0.1--patch-42

% tla missing -A lord@emf.net--2003-example \
         hello-world--mainline--0.1
patch-24
patch-25
...
patch-41


% tla logs -A lord@emf.net--2003-example \
         hello-world--mainline--0.1
base-0
patch-1
...
patch-22
patch-23
patch-42

La « cueillette » de changements de cette manière n'est pas nécessairement facile ou même pratique. Cela dépend, par exemple, si les changesets de mainline sont « propres ». (voir « Bien utiliser les commit -- L'idée d'un changeset « propre » »)

Néanmoins, pour certains projets, ceux caractérisés par de nombreuses scissions, cette technique peut être utile.

Note

Plusieurs révisions peuvent être réappliquées avec une seule commande, simplement en les indiquant toutes sur la même ligne de commande. La commande replay a également une option --list qui peut être utile pour en cueillir plusieurs à la fois. Si vous réappliquez souvent une série de révision, vous devriez regarder l'option --list dans tla replay --help.

Projets multi-arborescence et gestion de configuration

Vous pouvez définir des « méta-projets » composés de plusieurs projets traités séparément par arch. Cela permet de diviser un grand projet en plusieurs petits, plus faciles à gérer, chacun d'eux peut être développé indépendamment des autres, et chacun d'eux peut faire partie d'un ou plusieurs méta-projets.

Cela s'accomplit en écrivant des « spécifs de config », qui définissent le contenu d'un méta-projet et comment ils doivent être organisés dans l'arborescence.

Par exemple, arch lui-même est un méta-projet. L'arborescence contient :

dists/
  dists/src/
    dists/src/arch/
    dists/src/file-utils/
    dists/src/ftp-utils/
    dists/src/hackerlab/
    dists/src/shell-utils/

Chacun de ces répertoires est la racine de l'arborescence d'un projet (contient un sous-répertoire nommé « {arch} »).

Le répertoire le plus haut, dist contient également un sous-répertoire nommé configs. Dans ce sous-répertoire se trouve les fichiers de configuration du méta-projet. Par exemple :

dists/
  dists/configs/
    dists/configs/regexps.com/  # Tom's configuration files
      dists/configs/regexps.com/devo.arch
      dists/configs/regexps.com/release-template.arch

Ci-dessous, le contenu de devo.arch:

# 
# Check out an arch distribution from the devo branches.  
# Latest revisions.
#

./src                   lord@regexps.com--2002/package-framework--devo
./src/arch              lord@regexps.com--2002/arch--devo
./src/file-utils        lord@regexps.com--2002/file-utils--devo
./src/ftp-utils         lord@regexps.com--2002/ftp-utils--devo
./src/hackerlab         lord@regexps.com--2002/hackerlab--devo
./src/shell-utils       lord@regexps.com--2002/shell-utils--devo
./src/text-utils        lord@regexps.com--2002/text-utils--devo

Chaque ligne (non vide, non commentaire) est au format :

LOCATION             CONTENTS

ce qui signifie, pour créer le méta-projet, récupère la révision indiquée par CONTENTS et installe-la dans LOCATION. La zone CONTENTS peut être une branche (signifiant, prendre la dernière révision de la dernière version de cette branche), une version (signifiant, prendre la dernière révision de cette version), ou le nom d'une révision (signifiant cette révision, éxactement).

Pour récupérer une arborescence de arch complète, je récupère dists à partir de devo, puis j'utilise build-config:

% tla get dists--devo dists
[....]

% cd dists

% tla build-config regexps.com/dists.devo
[....]

Une fois que vous avez l'arborescence d'un méta-projet, voici quelques commandes utiles :

cat-config : affiche les informations de la configuration d'un
             projet-multiple

Cette commande peut être utile pour générer une liste de sous-projets sur lesquels on souhaite réitérer une commande :

% tla cat-config CFGNAME | awk '{print $1}' | xargs ...

De plus, l'option --snap peut être utilisée pour la création d'une configuration qui nomme les sous-projets par leur version plutôt que par leur révision. Elle examine l'arborescence du projet pour voir quelles révisions sont actuellement installées dans chaque LOCATIONs. Ensuite elle écrit une nouvelle configuration qui spécifie ces REVISIONS précisément. C'est utile, par exemple, pour enregistrer les version spécifiques que vous êtes prêt à utiliser pour une distribution.

Bibliothèque de révision, les bases

Dans de nombreux cas, il est utile d'avoir une bibliothèque contenant l'arborescence d'un grand nombre de révisions -- par exemple, toutes les révisions d'une version particulière. Pour être pratique, cependant, une telle bibliothèque doit être représentée d'une manière efficace.

Les liens Unix sont une solution naturelle pour stocker ce genre de bibliothèque. Chaque révision successive d'une série est une copie de la précédente, mais dont les fichiers non modifiés sont partagés par des liens physiques (hard-link).

Arch fournit des commandes pour aider à construire, maintenir et explorer de telles bibliothèques.

Comme effet de bord sympathique, le traitement de plusieurs commandes arch est accéléré si les révisions dont vous avez besoin se trouvent dans votre bibliothèque de révisions. Vous pourrez lire d'autres informations à ce propos dans le chapitre suivant.

L'emplacement de votre bibliothèque de révisions

Pour démarrer une nouvelle bibliothèque, commencez par créer un nouveau répertoire (DIR) et enregistrer son emplacement :

% tla my-revision-library DIR

Vous pouvez vérifier l'emplacement de votre bibliothèque avec :

% tla my-revision-library

ou effacer son inscription :

% tla my-revision-library -d DIR

Notez que vous pouvez avoir plusieurs bibliothèques de révisions : en fait, vous avez une liste des chemins (path) d'accès de toutes vos bibliothèques.

Format d'une bibliothèque de révision

Une bibliothèque de révision contient des sous-répertoires de la forme :

ARCHIVE-NAME/CATEGORY/BRANCH/VERSION/REVISION/

Chaque répertoire REVISION contient les sources complets d'une révision particulière, ainsi que quelques sous-répertoires et fichiers :

REVISION/,,patch-set/

        Le patch-set qui a créé cette révision à partir de son
        ascendant (sauf s'il s'agit d'une révision de base)

Même si les permissions des fichiers dans la bibliothèque de révision sont déterminées par celles du patch-set, vous ne devez jamais modifier les fichiers d'une bibliothèque de révision. En faisant cela vous pourriez rencontrer de sérieux problèmes avec certaines commandes de arch.

Ajouter manuellement une révision à une bibliothèque

Vous pouvez ajouter une révision donnée à votre bibliothèque avec :

% tla library-add REVISION

library-add ne va pas simplement ajouter REVISION à votre bibliothèque, mais également toutes les révisions précédentes (récurcivement) de la version de REVISION.

Si vous voulez ajouter seulement REVISION et aucune autre, utilisez l'option --sparse :

% tla library-add --sparse REVISION

Enlever une révision d'une bibliothèque

Pour enlever une révision particulière d'une bibliothèque, utilisez :

% tla library-remove REVISION

Soyez conscient de quelques limitations dans la version actuelle de arch : supposons que vous ayez ajouté trois révisions successives, A, B et C. Et vous enlevez B, puis remettez B. Maintenant il y a des chances pour que le partage de fichier entre B et C ne soit pas optimal, la bibliothèque sera plus grosse que nécessaire. (Vous pouvez arrangez ça en enlevant et remettant C).

Afficher le contenu d'une bibliothèque

La commande library-archives affiche toutes les archives enregistrées dans la bibliothèque :

% tla library-archives
ARCHIVE-NAME
ARCHIVE-NAME
...

De la même manière, vous pouvez afficher les catégories, branches, versions et révisions :

% tla library-categories [ARCHIVE]
% tla library-branches [ARCHIVE/CATEGORY]
% tla library-versions [ARCHIVE/BRANCH]
% tla library-revisions [ARCHIVE/VERSION]

Fichiers individuels dans une bibliothèque de révisions

Vous pouvez trouver un fichier individuel dans une bibliothèque avec :

% tla library-file FILE [REVISION]
PATH

ou obtenir son contenu avec :

% tla cat-library-file FILE [REVISION]
...file contents...

Ces deux commandes acceptent les options --id et --this. Avec --id, l'argument FILE est interprété comme un identifiant d'inventaire (inventory id), et le fichier ayant cet identifiant est trouvé.

Avec --this, FILE est interprété comme un fichier relatif au répertoire en cours, qui devrait faire parti de l'arborescence d'un projet. L'identifiant d'inventaire de ce fichier est retrouvé et le fichier correspondant sera trouvé dans REVISION.

Déterminer les prérequis d'un patch-set

% tla touched-files-prereqs REVISION

Cette commande examine le patch-set pour REVISION et tout les patch-sets précédents de la même version (il effectue les recherches dans votre bibliothèque plutôt que dans l'archive). Il affiche la liste des patchs qui recouvrent un ensemble de fichiers et répertoires -- en d'autres termes, il vous indique quels patchs peuvent être appliqués indépendament des autres. Cette commande a une option pour ne pas tenir compte de fichiers ayant un certain schéma (ex. "=README" ou "ChangeLog"). Il a une option pour exclure de l'affichage la liste des patchs qui ont déjà été appliqués à un projet donné. Il a une option pour afficher les fichiers qui sont recouverts.

Utilisation avancée des bibliothèques de révisions

Par défaut, quand vous récupérez (get) une révision d'une archive, arch enregistre une copie de base (« pristine copy ») de cette révision dans le répertoire {arch}.

Par défaut également, quand vous récupérez une révision (get), arch construit la révision en cherchant l'ascendant import ou l'ascendant le plus récent mis en cache -- puis applique les derniers patchs pour construire la révision que vous souhaitez.

get et les opérations similaires peuvent être accélérées et utiliser moins de place en utilisant les bibliothèques de révisions. Par exemple, si get trouve la révision que vous demandez dans une bibliothèque, il va la copier directement à partir de là (plutôt que de la construire à partir des patchs) et évitera de créer une copie locale (« pristine copy ») dans le répertoire {arch}.

Tout ça est très bien -- mais il peut devenir pénible d'avoir à se souvenir d'ajouter (library-add) pour chaque révisions à votre bibliothèque. Cette section vous montrera comment automatiser ce processus.

Bibliothèque de révision avide (greedy)

Dans le cas d'une « bibliothèque de révision avide », lorsque arch regarde si elle contient une révision donnée, et qu'elle ne la contient pas, arch l'ajoutera automatiquement.

Vous pouvez rendre une bibliothèque de révision avide avec la commande :

% tla library-config --greedy DIR

Bibliothèque de révision clairsemée (sparse)

Quand arch ajoute une révision à une bibliothèque avide, normalement il le fait à la manière habituelle de library-add : il ajoute également toutes les révisions précédentes de la même version.

Si vous ajoutiez une révision, dans une bibliothèque, à la main vous pourriez l'éviter en utilisant l'option --sparse de library-add. Pour obtenir ce comportement pour les révisions ajoutés automatiquement, utilisez :

% tla library-config --sparse DIR

qui signifie que si une révision est automatiquement ajoutée dans la bibliothèque située à DIR, elle sera ajoutée avec l'option --spare de la commande library-add utilisée.

Liens physiques de l'arborescence

Attention!

Pour éviter des confusions, n'utilisez pas le dispositif suivant si vous ne comprenez pas (a) ce qu'est un lien physique (« hard-link ») et (b) ce que signifie pour un éditeur de « casser un lien physique en éditant un fichier ». Si vous comprenez ces termes, et savez que l'éditeur que vous utilisez va effectivement casser le lien physique, utilisez le dispositif à votre aise.

Vous pouvez rapidement récupérer (get) une révision d'une bibliothèque sans la recopier, mais à la place en créant un lien physique vers celle-ci :

% tla get --link REVISION

La commande build-config a une option similaire :

% tla build-config --link REVISION

Cela peut préserver énormément d'espace disque et accélérer l'opération get.

(Lorsque vous utilisez une arborescence liée physiquement il y a bien sûr une petite chance pour que les choses n'aillent pas comme prévu et que vous modifiiez un fichier de la révision de la bibliothèque. Dans ce cas, arch le remarquera et affichera un message d'erreur vous demandant d'enlever et reconstruire la révision dans la bibliothèque).

En résumé

Pour résumer, une configuration pratique et efficace implique :

  1. Créer un ou plusieurs répertoires de bibliothèques de révision.
  2. Rendez au moins quelques unes de ces bibliothèques avides et éventuellement clairsemées.
  3. Utilisez l'option --link pour get et build-config

Lorsque vous travaillez de cette manière, arch va ajouter les révisions dans les bibliothèques automatiquement, il va chercher les bibliothèques à l'emplacement (device) approprié (pour les liens physiques). Parmis ceux-là il va chercher en premier pour une bibliothèque qui contient déjà la même version que la révision que vous souhaitez et, sinon, pour une bibliothèque avide.

Automatiser certaines procédures avec les crochets (hooks)

Dans certaines circonstances, il est très utile d'attacher des actions lors de la détection de changements dans une archive. Par exemple, vous voudrez envoyer un email de notification lorsqu'une nouvelle révision est archivée.

Ce processus est possible avec arch au moyen de crohets. Chaque fois que arch exécute une commande qui modifie une archive, arch va lancer ~/.arch-params/hook, qui doit être exécutable.

Arguments envoyés aux crochets

Lorque arch exécute une commande qui affecte une archive, il va lancer le crochet avec comme premier argument le nom de l'action effectuée. Si un utilisateur lance une commande (telle que make-archive) le crochet va être appelé plusieurs fois avec de multiples arguments (tels que make-archive, make-category, make-branche, et make-version).

Les arguments succeptibles d'être vus sont :

commit, import, make-archive, make-branch, make-category, make-version,
precommit et tag.

Toutes ces commandes sont appelées après que la commande en question ait été exécutée avec succès -- excepté pour precommit. Le crochet precommit, comme son nom le suggère, est lancé avant chaque commit, et s'il renvoi un non-zéro en sortie, le commit est annulé. Ainsi il peut être utilisé pour accepter les commits sous condition tels que la possibilité de compiler, le succès de tests unitaires, etc.

Variables d'environnement passées aux crochets

tla passe certaines variables aux crochets lorsque c'est approprié. Les variables passées par tla sont préfixées avec ARCH_. Les variables succeptibles d'être passées sont :

Nom Description Vue par Exemple
ARCHIVE Archive involved all lord@emf.net--2003-example
CATEGORY Name of category created make-category hello-world
BRANCH Name of branch created make-branch mainline
VERSION Name of version created make-version 0.1
REVISION Name of revision involved import,tag,commit patch-6
LOCATION Location of archive make-archive ~/{archives}/2003-example
TREE_ROOT Root of working arch tree commit,import ~/wd
TAGGED_ARCHIVE Archive being tagged off tag lord@emf.net--2003-example
TAGGED_REVISION Revision being tagged off tag tla--devo--1.2--patch-1

Crochets spécifiques aux archives

Étant donné que les crochets tels que precommit peuvent être spécifiques aux archives et mis à jour en fonction de chaque archive, il est recommandé de faire un script ~/.arch-params/hook tel que celui-la :

#!/bin/sh
hook="$ARCH_TREE_ROOT/{arch}/=hook"
if [ -x "$hook" ] ;  then
   "$hook" $@
fi

Un exemple d'utilisation de crochets

#!/bin/sh

if [ "$1" == "commit" ]; then
   tla push-mirror lord@emf.net--2003-example \
      lord@emf.net--2003-example-MIRROR;
   fi

Un exemple plus complexe d'utilisation des crochets

#!/bin/sh

case "$1" in
   commit)
      case "$ARCH_CATEGORY" in
         hello-world)
            case "$ARCH_BRANCH" in
              mainline)
                   RELEASETYPE="stable"
              ;;
              devel)
                 RELEASETYPE="unstable"
              ;;
             )
              
            echo "The $RELEASETYPE version of Hello, World been upgraded. \
               New versions are available at ftp.hello.com" |\
               mailto hello-users@hello.com -s "Hello upgraded"
         ;;
         goodbye-world)
            case "$ARCH_BRANCH" in
              mainline)
                   RELEASETYPE="stable"
              ;;
              devel)
                 RELEASETYPE="unstable"
              ;;
                 RELEASETYPE="[unknown]"
             )
            esac;
            echo "The stable version of Goodbye, Cruel World been upgraded. \
               New versions are available at ftp.hello.com" |\
               mailto goodbye-users@hello.com -s "Goodbye upgraded"
         ;;
       esac
   ;;
esac

Problème de fiabilité avec les crochets

Malheureusement, quelques lois de la physique fondamentale de l'univers font qu'il est impossible à arch de garantir qu'un crochet va être appelé une seule fois seulement pour chaque nouvelle catégorie, branche, version ou révision. Une interruption (normalement rare) au mauvais moment ou un problème du système peut entraîner une notification plusieurs fois pour une même opération sur l'archive.

En conséquence, les actions des crochets doivent être étudiées pour être robustes face à cette éventualité.

De plus, si arch a été lancé plusieurs fois simultanément, les crochets vont être lancés simultanément également. Cela signifie que les projets utilisant des crochets doivent faire attention à ce que ces derniers puissent être lancés simultanément.

Accélérer arch en mettant les révisions des archives en cache

Ce chapitre explique une technique pour accélérer l'accès à une archive arch.

Considérons une version arch qui contient de nombreuses révisions :

mainline
--------
base-0
patch-1
....
patch-23
patch-24
patch-25
...
patch-42

Supposons qu'un utilisateur (sans copie locale) veut récupérer (get) la révision patch-42. get va en premier lieu récupérer et décompresser la révision base-0, et ensuite récupérer chaque changeset patch-<N>, dans l'ordre, et les appliquer à l'arborescence.

Si la liste des changesets à appliquer est longue, ou la somme de leur taille très grande en comparaison de la taille de l'arborescence, alors l'implémentation de get est inutilement inefficace.

Un moyen d'accélérer get est en mettant les révisions en cache -- en stockant des copies « pré-construites » de quelques révisions dans l'archive.

Par exemple, la commande :

% tla cacherev -A lord@emf.net--2003-example \
        hello-world--mainline--0.1--patch-40

va construire la révision patch-40, en faire un paquet avec tar, et stocker une copie de ce paquet dans le répertoire du patch-40 de l'archive.

En conséquence, un get du patch-42 va en premier lieu récupérer la copie mise en cache de la révision patch-40, puis récupérer et appliquer les changesets patch-41 et patch-42 : évitant 40 changesets.

Note

Pour l'instant, c'est à vous de décider quelles révisions mettre en cache ou non. Vous pourriez décider, par exemple, de mettre en cache automatiquement certaines révisions à partir d'une tache cron ou à la main tout simplement lorsque vous constatez que get est trop lent. Dans le futur, nous espérons ajouter un moyen plus automatique pour mettre les révisions en cache.

Le format arch d'un changeset

Un changeset arch est un répertoire contenant un certain nombre de fichiers et répertoires. Ils sont décrit ci-dessous :

Fichiers :

orig-dirs-index
mod-dirs-index
orig-files-index
mod-files-index

Format :

<file path><tab><id>

Tri :

sort -k 2

Ils contiennent un index de tous les fichiers et répertoires ajoutés, déplacés, ou modifiés entre deux arborescences.

Fichiers :

original-only-dir-metadata
modified-only-dir-metadata

Format :

<metadata><tab><name>

Tri :

sort -t '<tab>' -k 2

Le champ <metadata><tab><name> contient la sortie du programme file-metadata avec l'option --permissions. Quelques exemples de sorties :

--permissions 777

Cette sortie peut également être utilisée pour les options et arguments du programme set-file-metadata. Les prochaines versions de arch ajouteront d'autres options (entre autres permissions).

Répertoires :

removed-files-archive
new-files-archive

Chacuns de ces répertoires contient une copie complète de tous les fichiers présents seulement dans l'arborescence originale (removed-files-archive) ou dans l'arborescence modifiée (new-files-archive). Chaque fichier sauvegardé est archivé au même emplacement que dans l'arborescence source, avec les permissions (au moins) préservées.

Répertoire :

patches

Ce répertoire contient une arborescence dont la structure des répertoires est la même que la structure des répertoires de l'arborescence modifiée. Il contient les données de modification pour les répertoires et les fichiers communs aux deux arborescences.

Pour un fichier new-name stocké dans l'arborescence modifié, le répertoire patches devrait contenir :

Note

Si un fichier normal (ou un lien symbolique) remplace un répertoire, ou vice et versa, il est enregistré en tant que fichier (ou lien) effacé (ou ajouté) dans une arborescence et ajouté (ou effacé) de l'autre.

Adaptation de la convention de nommage dans l'inventaire

Dans « inventaire d'une arborescence », vous avez appris comment la commande tla inventory classait les fichiers dans un projet suivant une convention de nommage. Cette section explique comment vous pouvez adapter cette convention de nommage.

Quand adapter la convention de nommage ?

Il est préférable d'effectuer l'adaptation de la convention de nommage au début : avant l'import de la première révision.

Si vous devez faire des changements plus tard, alors il est impératif que vos changements ne changent pas la classification des fichiers déjà présents dans les dernières révisions de votre projet au moment ou vous faites les changements (sinon, vous risquez d'obtenir un comportement étrange et indésirable).

Comment adapter une convention de nommage ?

Vous devrier commencer par revoir l'algorithme de la convention de nommage dans « La convention de nommage de arch ». Vous pouvez modifier cet algorithme en modifiant les expressions rationnelles (regexp) utilisées pour chaque test de catégorie.

Vous pouvez adapter la convention de nommage en modifiant le fichier ./{arch}/=tagging-method de votre arborescence. Ce fichier est créé et initialisé par la commande id-tagging-method, il contient une ligne qui nomme la méthode d'identification (names, explicit, tagline, (ou implicit, obsolète maintenant, mais utilisé dans quelques vieux projets, y compris arch lui-même)).

En particulier, =tagging-method peut contenir des lignes vides et de commentaires (lignes commençant par « # ») et directives, une par ligne. Les directives permises sont :

tagline
implicit
explicit
names
        spécifie la méthode d'identification utilisée pour 
        cette arborescence

exclude RE
junk RE
backup RE
precious RE
unrecognized RE
source RE
        spécifie une expression rationnelle à utiliser pour
        indiquer la catégorie des fichiers

Les expressions rationelles sont spécifiées dans la syntaxe « Posix ERE » (la même syntaxe utilisée par egrep, grep -E, et awk) et ont comme valeurs par défaut l'implémentation de la convention de nommage décrite dans « La convention de nommage de arch ».

Une directive d'expression rationnelle peut être utilisée plusieurs fois, dans chaque cas les expressions sont concaténées comme alternatives. Ainsi, par exemple :

source  .*\.c$
source  .*\.h$

est équivalent à :

source (.*\.c$)|(.*\.h$)