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.
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"
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.
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"
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.
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.
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}
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"); [...]
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.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.
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
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
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).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.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.
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).
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).
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
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
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:)
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.
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.
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 [...]
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.
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.
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.).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 »).
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 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 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
où PATCH est le nom complet du patch-set et DATE la date précise.
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.
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.
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
où PATCH est le nom complet du changeset et DATE la date actuelle.
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.
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.
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é ?
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 »
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 ».)
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 ? »).
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.
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 ?
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 :
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.
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.
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.
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 :
Comment pouvez-vous faire ?
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 :
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.
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)
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.
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.
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).
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.
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.
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
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
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
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.
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.
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
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
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 ».
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.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 »).
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.
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.
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.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.
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 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.
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.
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 :
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
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.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.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.
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.
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.
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.
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
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).
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]
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.
% 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.
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.
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
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.
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).
Pour résumer, une configuration pratique et efficace implique :
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.
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.
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.
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 |
É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
#!/bin/sh if [ "$1" == "commit" ]; then tla push-mirror lord@emf.net--2003-example \ lord@emf.net--2003-example-MIRROR; fi
#!/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
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.
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.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 :
new_name.link-orig
Le fichier original est un lien symbolique. new_name.link-orig est un fichier texte contenant la cible de ce lien terminé par un retour à la ligne.
Ce fichier n'est présent que si la cible du lien a changé, ou si le lien a été remplacé par un fichier normal.
new_name.link-mod
Le fichier modifié est un lien symbolique et ce fichier est un fichier texte contenant la cible pour le lien terminé par un retour à la ligne.
Ce fichier n'est présent que si le lien cible a changé, ou si le lien remplace un fichier normal.
new_name.original
C'est une copie complète du fichier de l'arborescence originale, dont les permissions sont (si possible) préservées.
Ce fichier n'est présent que si le fichier a été remplacé par un lien symbolique, ou si le contenu du fichier ne peut pas être traité par diff(1).
new_name.modified
C'est une copie complète du fichier de l'arborescence modifiée, dont les permissions sont (si possible) préservées.
Ce fichier n'est présent que si le fichier remplace un lien symbolique, ou si son contenu ne peut pas être traité par diff(1).
new_name.patch
C'est le résultat d'un diff entre le fichier orignal et le fichier modifié. Une version courrante de diff (GNU diff) génère un résultat non-standard en oubliant une copie des lignes du résultat qui sont identiques entre le fichier original et le fichier modifié. Ainsi, les fichiers .patch peuvent avoir le même bogue. Heureusement, la version courrante de patch (GNU patch) accepte de recevoir un tel résultat.
new_name.meta-orig
new_name.meta-mod
Fichier de « metadata » (actuellement uniquement les permissions) modifiées entre deux versions du fichier. Ces fichiers contiennent la sortie du programme file-metadata avec les options --symlink --permissions, utilisable pour comparer deux sortie similiraires, et pour être utilisé comme options et arguments à set-file-metadata.
new_name/=dir-meta-orig
new_name/=dir-meta-mod
Répertoire « metadata » (actuellement seulement les permissions) des modifications entre deux versions des répertoires contenant ces fichiers. Ces fichiers contiennent la sortie du programme file-metadata avec les options --symlink --permissions, utilisable pour comparer deux sorties similaires, et être utilisé comme options et arguments à set-file-metadata.
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.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.
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).
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$)