SVNBOOK Chap8 Using the APIs
De Framalang Wiki.
Cette page fait partie du projet Version control with subversion.
| Pseudo | Code | Rôle | Statut |
|---|---|---|---|
| Hotshot92 | Traduction | Fait | |
| SVF | Relecture | Fait | |
| Validation |
Source : http://svnbook.red-bean.com/en/1.5/svn-book.html#svn.developer.usingapi
Sommaire |
Using the APIs
Developing applications against the Subversion library APIs is fairly straightforward. Subversion is primarily a set of C libraries, with header (.h) files that live in the subversion/include directory of the source tree. These headers are copied into your system locations (e.g., /usr/local/include) when you build and install Subversion itself from source. These headers represent the entirety of the functions and types meant to be accessible by users of the Subversion libraries. The Subversion developer community is meticulous about ensuring that the public API is well documented—refer directly to the header files for that documentation.
When examining the public header files, the first thing you might notice is that Subversion's datatypes and functions are namespace-protected. That is, every public Subversion symbol name begins with svn_, followed by a short code for the library in which the symbol is defined (such as wc, client, fs, etc.), followed by a single underscore (_), and then the rest of the symbol name. Semipublic functions (used among source files of a given library but not by code outside that library, and found inside the library directories themselves) differ from this naming scheme in that instead of a single underscore after the library code, they use a double underscore (_ _). Functions that are private to a given source file have no special prefixing and are declared static. Of course, a compiler isn't interested in these naming conventions, but they help to clarify the scope of a given function or datatype.
Another good source of information about programming against the Subversion APIs is the project's own hacking guidelines, which you can find at http://subversion.tigris.org/hacking.html. This document contains useful information, which, while aimed at developers and would-be developers of Subversion itself, is equally applicable to folks developing against Subversion as a set of third-party libraries. [53]
Développer des applications utilisant les API des bibliothèques Subversion est plutôt simple. Subversion est d'abord un ensemble de bibliothèques en langage C, avec des fichiers d'en-têtes (.h) situés dans le répertoire Subversion/include de l'arborescence des sources. Ces en-têtes sont copiés dans votre arborescence système (par exemple /usr/local/include) quand vous compilez et installez Subversion à partir des sources. Ces en-têtes contiennent l'ensemble des fonctions et des types censés être accessibles aux utilisateurs des bibliothèques Subversion. La communauté des développeurs Subversion apporte beaucoup d'attention à la disponibilité et la qualité de la documentation des API publiques — reportez-vous directement aux fichiers d'en-têtes pour cette documentation.
Quand vous examinez les fichiers d'en-tête publics, la première chose que vous remarquerez est que les types de données et les fonctions ont un espace de nommage réservé. Cela veut dire que tous les noms de symboles Subversion publics commencent par svn_, suivi d'un code indiquant la bibliothèque dans laquelle le symbole est défini (par exemple wc, client, fs etc.), suivi d'un unique caractère souligné (_) puis du reste du nom du symbole. Les fonctions semi-publiques (utilisées par plusieurs fichiers au sein d'une bibliothèque mais pas par du code extérieur à cette bibliothèque, on peut les trouver au sein des répertoires de la bibliothèque) suivent une règle de nommage légèrement différente dans le sens où, au lieu d'un unique caractère souligné (_) après le code indiquant la bibliothèque, elles utilisent deux caractères souligné consécutifs (__). Les fonctions qui sont propres à un fichier source (c'est-à-dire privées) n'ont pas de préfixe particulier et sont déclarées avec le mot-clé static. Bien sûr, un compilateur n'a que faire de ces conventions de nommage, mais elles sont une aide précieuse pour clarifier la portée d'une fonction ou d'un type de données particuliers.
Une autre bonne source d'informations sur la programmation avec les API Subversion est constituée par les bonnes pratiques de programmation au sein du projet lui-même, que vous pouvez trouver à l'adresse suivante : http://subversion.tigris.org/hacking.html (pages en anglais). Ce document contient des informations particulièrement utiles qui, bien que destinées aux développeurs (ou aux personnes désireuses de le devenir) de Subversion lui-même, peuvent également s'appliquer à tous ceux qui développent des applications utilisant Subversion comme bibliothèque tierce [53].The Apache Portable Runtime Library
Along with Subversion's own datatypes, you will see many references to datatypes that begin with apr_—symbols from the Apache Portable Runtime (APR) library. APR is Apache's portability library, originally carved out of its server code as an attempt to separate the OS-specific bits from the OS-independent portions of the code. The result was a library that provides a generic API for performing operations that differ mildly—or wildly—from OS to OS. While the Apache HTTP Server was obviously the first user of the APR library, the Subversion developers immediately recognized the value of using APR as well. This means that there is practically no OS-specific code in Subversion itself. Also, it means that the Subversion client compiles and runs anywhere that the Apache HTTP Server does. Currently, this list includes all flavors of Unix, Win32, BeOS, OS/2, and Mac OS X.
In addition to providing consistent implementations of system calls that differ across operating systems, [54] APR gives Subversion immediate access to many custom datatypes, such as dynamic arrays and hash tables. Subversion uses these types extensively. But perhaps the most pervasive APR datatype, found in nearly every Subversion API prototype, is the apr_pool_t—the APR memory pool. Subversion uses pools internally for all its memory allocation needs (unless an external library requires a different memory management mechanism for data passed through its API), [55] and while a person coding against the Subversion APIs is not required to do the same, she is required to provide pools to the API functions that need them. This means that users of the Subversion API must also link against APR, must call apr_initialize() to initialize the APR subsystem, and then must create and manage pools for use with Subversion API calls, typically by using svn_pool_create(), svn_pool_clear(), and svn_pool_destroy().
À côté des types de données propres à Subversion, vous verrez de nombreuses références à des types de données qui commencent par apr_ — ce sont les symboles de la bibliothèque pour la portabilité d'Apache (Apache Portable Runtime en anglais, soit APR). APR est un jeu de bibliothèques Apache, originellement extraites du code source du serveur pour essayer de séparer ce qui dépendait du système d'exploitation de ce qui n'en dépendait pas. Au final, on obtient une bibliothèque qui fournit une API permettant d'effectuer des opérations qui changent un peu (ou beaucoup) en fonction du système d'exploitation. Alors que le serveur HTTP Apache était le premier utilisateur (et pour cause) de la bibliothèque APR, les développeurs Subversion ont immédiatement perçu les avantages qu'il y a à utiliser APR. Cela signifie qu'il n'y a pratiquement aucun code spécifique à un système d'exploitation dans Subversion en tant que tel. Cela veut aussi dire que le client Subversion peut être compilé et exécuté partout où un serveur Apache peut l'être. Actuellement, cette liste comprend toutes les variantes d'Unix, Win32, BeOS, OS/2 et Mac OS X.
En plus de fournir des implémentations fiables des appels systèmes qui diffèrent d'un système d'exploitation à l'autre [54], APR fournit à Subversion un accès direct à de nombreux types de données personnalisés tels que les tableaux dynamiques et les tables de hachage. Subversion utilise abondamment ces types de données, et le type de données APR le plus utilisé, que l'on retrouve dans presque tous les prototypes de l'API Subversion, est apr_pool_t — le réservoir de mémoire ("memory pool" en anglais) APR. Subversion utilise les réservoirs de mémoire en interne pour tous ses besoins d'allocation mémoire (à moins qu'une bibliothèque externe ne requiert un autre mécanisme de gestion de la mémoire pour les données transmises via son API ) [55] et, bien qu'une personne qui utilise l'API Subversion ne soit pas obligée d'en faire autant, elle doit fournir des réservoirs aux fonctions de l'API qui en ont besoin. Cela implique que les utilisateurs de l'API Subversion doivent également inclure l'APR lors de l'édition de liens, doivent appeler apr_initialize() pour initialiser le sous-système APR et doivent ensuite créer et gérer des réservoirs de mémoire pour les appels à l'API Subversion, généralement en utilisant svn_pool_create(), svn_pool_clear() et svn_pool_destroy().POINT OF INTEREST : Programming with Memory Pools
- Programming with Memory Pools
- Programmer avec les réservoirs de mémoire
- Almost every developer who has used the C programming language has at some point sighed at the daunting task of managing memory usage. Allocating enough memory to use, keeping track of those allocations, freeing the memory when you no longer need it—these tasks can be quite complex. And of course, failure to do those things properly can result in a program that crashes itself, or worse, crashes the computer.
- Higher-level languages, on the other hand, either take the job of memory management away from you completely or make it something you toy with only when doing extremely tight program optimization. Languages such as Java and Python use garbage collection, allocating memory for objects when needed, and automatically freeing that memory when the object is no longer in use.
- APR provides a middle-ground approach called pool-based memory management. It allows the developer to control memory usage at a lower resolution—per chunk (or “pool”) of memory, instead of per allocated object. Rather than using malloc() and friends to allocate enough memory for a given object, you ask APR to allocate the memory from a memory pool. When you're finished using the objects you've created in the pool, you destroy the entire pool, effectively de-allocating the memory consumed by all the objects you allocated from it. Thus, rather than keeping track of individual objects that need to be de-allocated, your program simply considers the general lifetimes of those objects and allocates the objects in a pool whose lifetime (the time between the pool's creation and its deletion) matches the object's needs.
- Presque tous les développeurs qui ont essayé le langage C se sont heurtés à la tâche dantesque de gestion de la mémoire. Allouer suffisamment de mémoire pour l'exécution, garder une trace de ces allocations, libérer la mémoire quand elle n'est plus utilisée — ces tâches peuvent devenir particulièrement complexes. Et, bien sûr, si cette gestion est mal faite, cela peut conduire à un plantage du programme, voire de l'ordinateur.
- Les langages de plus haut niveau, quant à eux, soit vous débarrassent complètement de cette tâche, soit vous laissent jouer avec uniquement quand vous faites des optimisations particulièrement pointues de votre programme. Des langages tels que Java ou Python utilisent un ramasse-miette, qui alloue de la mémoire aux objets en cas de besoin et la libère automatiquement quand l'objet n'est plus utilisé.
- APR fournit une approche à mi-chemin appelée gestion de mémoire par réservoir. Cela permet au développeur de contrôler l'utilisation de la mémoire à une résolution plus faible — par morceau (dit "réservoir") de mémoire au lieu d'une gestion par objet. Plutôt que d'utiliser malloc() et compagnie pour allouer la mémoire à un objet donné, vous demandez à APR d'allouer de la mémoire à l'intérieur d'un réservoir de mémoire. Quand vous avez fini d'utiliser les objets que vous avez créés dans un réservoir, vous détruisez le réservoir tout entier, ce qui libère effectivement la mémoire consommée par tous les objets alloués. Ainsi, plutôt que de gérer individuellement la mémoire qui doit être allouée et libérée pour chaque objet, votre programme n'a plus qu'à se préoccuper de la durée de vie globale des objets et alloue ces objets dans un réservoir dont la durée de vie (le temps entre la création et la suppression du dit réservoir) correspond aux besoins des objets.
URL and Path Requirements
With remote version control operation as the whole point of Subversion's existence, it makes sense that some attention has been paid to internationalization (i18n) support. After all, while “remote” might mean “across the office,” it could just as well mean “across the globe.” To facilitate this, all of Subversion's public interfaces that accept path arguments expect those paths to be canonicalized—which is most easily accomplished by passing them through the svn_path_canonicalize() function—and encoded in UTF-8. This means, for example, that any new client binary that drives the libsvn_client interface needs to first convert paths from the locale-specific encoding to UTF-8 before passing those paths to the Subversion libraries, and then reconvert any resultant output paths from Subversion back into the locale's encoding before using those paths for non-Subversion purposes. Fortunately, Subversion provides a suite of functions (see subversion/include/svn_utf.h) that any program can use to do these conversions.
Also, Subversion APIs require all URL parameters to be properly URI-encoded. So, instead of passing file:///home/username/My File.txt as the URL of a file named My File.txt, you need to pass file:///home/username/My%20File.txt. Again, Subversion supplies helper functions that your application can use—svn_path_uri_encode() and svn_path_uri_decode(), for URI encoding and decoding, respectively.
Subversion a été conçu pour effectuer à distance des opérations de suivi de versions. À ce titre, les possibilités d'internationalisation (i18n) ont fait l'objet d'une attention toute particulière. Après tout, "à distance" peut vouloir dire depuis un ordinateur situé "dans le même bureau" mais aussi "à l'autre bout de la planète". Pour faciliter cette prise en compte, toutes les interfaces publiques de Subversion qui acceptent des chemins comme argument s'attendent à ce que ces chemins soient rendus canoniques — la façon la plus facile de le faire étant de les passer en argument à la fonction svn_path_canonicalize() — et encodés dans le format UTF-8. Cela signifie, par exemple, que tout nouveau programme client qui pilote l'interface libsvn_client doit d'abord convertir les chemins depuis le codage local vers UTF-8 avant de fournir ces chemins à la bibliothèque Subversion, puis de reconvertir tout chemin renvoyé par Subversion vers le codage local avant d'utiliser ce chemin à des fins externes à Subversion. Heureusement, Subversion fournit un ensemble de fonctions (voir subversion/include/svn_utf.h) que tout programme peut utiliser pour réaliser ces conversions.
De plus, les API Subversion demandent que toutes les URL passées en paramètres respectent le format URI. Ainsi, au lieu de désigner par file:///home/utilisateur/Mon fichier.txt l'URL d'un fichier nommé «Mon fichier.txt» dans le répertoire «home/utilisateur», vous devez utiliser file:///home/utilisateur/Mon%20fichier.txt. Là encore, Subversion fournit des fonctions utiles à votre application — svn_path_uri_encode() et svn_path_uri_decode() pour encoder et décoder, respectivement, des URI.Using Languages Other Than C and C++
Using Languages Other Than C and C++
If you are interested in using the Subversion libraries in conjunction with something other than a C program—say, a Python or Perl script—Subversion has some support for this via the Simplified Wrapper and Interface Generator (SWIG). The SWIG bindings for Subversion are located in subversion/bindings/swig. They are still maturing, but they are usable. These bindings allow you to call Subversion API functions indirectly, using wrappers that translate the datatypes native to your scripting language into the datatypes needed by Subversion's C libraries.
Significant efforts have been made toward creating functional SWIG-generated bindings for Python, Perl, and Ruby. To some extent, the work done preparing the SWIG interface files for these languages is reusable in efforts to generate bindings for other languages supported by SWIG (which include versions of C#, Guile, Java, MzScheme, OCaml, PHP, and Tcl, among others). However, some extra programming is required to compensate for complex APIs that SWIG needs some help translating between languages. For more information on SWIG itself, see the project's web site at http://www.swig.org/.
Si vous désirez utiliser les bibliothèques Subversion à partir d'un autre langage que le C (par exemple un programme Python ou Perl), Subversion offre cette possibilité via le générateur simplifié d'interface et d'encapsulation ("Simplified Wrapper and Interface Generator" ou SWIG en anglais). Les interfaces SWIG de Subversion sont situées dans le répertoire subversion/bindings/swig. Elles sont toujours en cours d'évolution mais sont utilisables. Elles vous permettent d'appeler les fonctions de l'API Subversion indirectement, en utilisant des interfaces qui traduisent les types de données natifs de votre langage de programmation vers les types de données utilisés par les bibliothèques C de Subversion.
Des efforts significatifs ont été fournis pour produire des interfaces SWIG pleinement fonctionnelles pour Python, Perl et Ruby. D'une certaine manière, le travail effectué pour réaliser les interfaces vers ces langages est réutilisable pour produire des interfaces vers d'autres langages supportés par SWIG (ce qui inclut, entre autres, des versions de C#, Guile, Java, MzScheme, OCaml, PHP et Tcl). Cependant, vous aurez besoin d'un peu de programmation supplémentaire pour aider SWIG à faire les traductions entre les langages pour les API complexes. Pour plus d'informations sur SWIG lui-même, visitez le site Web du projet à l'adresse suivante : http://www.swig.org/ (site en anglais).
Subversion also has language bindings for Java. The javahl bindings (located in subversion/bindings/java in the Subversion source tree) aren't SWIG-based, but are instead a mixture of Java and hand-coded JNI. Javahl covers most Subversion client-side APIs and is specifically targeted at implementors of Java-based Subversion clients and IDE integrations.
Subversion's language bindings tend to lack the level of developer attention given to the core Subversion modules, but can generally be trusted as production-ready. A number of scripts and applications, alternative Subversion GUI clients, and other third-party tools are successfully using Subversion's language bindings today to accomplish their Subversion integrations.
It's worth noting here that there are other options for interfacing with Subversion using other languages: alternative bindings for Subversion that aren't provided by the Subversion development community at all. You can find links to these alternative bindings on the Subversion project's links page (at http://subversion.tigris.org/links.html), but there are a couple of popular ones we feel are especially noteworthy. First, Barry Scott's PySVN bindings (http://pysvn.tigris.org/) are a popular option for binding with Python. PySVN boasts of a more Pythonic interface than the more C-like APIs provided by Subversion's own Python bindings. And if you're looking for a pure Java implementation of Subversion, check out SVNKit (http://svnkit.com/), which is Subversion rewritten from the ground up in Java.
Subversion fournit également une interface vers le langage Java. L'interface javahl (située dans subversion/bindings/java dans l'arborescence des sources Subversion) n'est pas basée sur SWIG mais est un mélange de Java et de JNI codé à la main. Javahl couvre le plus gros des API du client Subversion et se destine principalement aux développeurs d'environnements de développement intégrés (IDE) et de clients Subversion en Java.
Les interfaces Subversion vers les langages de programmation ne sont pas suivies avec le même niveau d'exigence que les modules du cœur de Subversion mais peuvent généralement être utilisées en production. De nombreuses applications, de nombreux scripts, des clients graphiques alternatifs et des outils tierces utilisent aujourd'hui sans problème les interfaces vers les langages de programmation afin d'intégrer les fonctionnalités de Subversion.
Veuillez tout de même noter qu'il existe d'autres options pour s'interfacer avec Subversion dans d'autres langages : les interfaces pour Subversion qui ne sont pas fournies par la communauté de développement Subversion. Vous pouvez trouver des liens vers ces interfaces alternatives sur la page de liens externes du projet Subversion (à l'adresse http://subversion.tigris.org/links.html) et, en particulier, nous accordons une mention spéciale à deux d'entre elles. D'abord, l'interface PySVN de Barry Scott (http://pysvn.tigris.org/) est une interface reconnue vers Python. PySVN se targue d'une interface plus « pythonique » que les API « orientées C » fournies par l'interface standard de Subversion vers Python. Et si vous recherchez une implémentation 100 % Java de Subversion, jetez un œil à SVNKit (http://svnkit.com/), qui est une ré-écriture complète de Subversion en Java.
POINT OF INTEREST : SVNKit Versus javahl
- SVNKit Versus javahl
- SVNKit ou javahl ?
- In 2005, a small company called TMate announced the 1.0.0 release of JavaSVN—a pure Java implementation of Subversion. Since then, the project has been renamed to SVNKit (available at http://svnkit.com/) and has seen great success as a provider of Subversion functionality to various Subversion clients, IDE integrations, and other third-party tools.
- The SVNKit library is interesting in that, unlike the javahl library, it is not merely a wrapper around the official Subversion core libraries. In fact, it shares no code with Subversion at all. But while it is easy to confuse SVNKit with javahl, and easier still to not even realize which of these libraries you are using, folks should be aware that SVNKit differs from javahl in some significant ways. First, SVNKit is not developed as open source software and seems to have at any given time only a few developers working on it. Also, SVNKit's license is more restrictive than that of Subversion. Finally, by aiming to be a pure Java Subversion library, SVNKit is limited in which portions of Subversion can be reasonably cloned while still keeping up with Subversion's releases. This has already happened once—SVNKit cannot access BDB-backed Subversion repositories via the file:// protocol because there's no pure Java implementation of Berkeley DB that is file-format-compatible with the native implementation of that library.
- That said, SVNKit has a well-established track record of reliability. And a pure Java solution is much more robust in the face of programming errors—a bug in SVNKit might raise a catchable Java Exception, but a bug in the Subversion core libraries as accessed via javahl can bring down your entire Java Runtime Environment. So, weigh the costs when choosing a Java-based Subversion implementation.
- En 2005, une petite entreprise du nom de TMate annonçait la sortie de la version 1.0.0. de JavaSVN — une implémentation 100 % Java de Subversion. Depuis, le projet a été renommé en SVNKit (disponible sur le site http://svnkit.com/) et connaît un grand succès en étant intégré dans de nombreux clients Subversion, IDE ou autres outils tiers.
- La bibliothèque SVNKit est intéressante dans le sens où, contrairement à la bibliothèque javahl, elle ne se contente pas d'encapsuler les bibliothèques officielles du cœur de Subversion. En fait, elle ne partage aucun code avec Subversion. Cependant, bien qu'il soit facile de confondre SVNKit et javahl, et même encore plus facile de ne pas savoir laquelle de ces bibliothèques vous utilisez, vous devez être conscient que SVNKit diffère de javahl sur certains points particulièrement importants. D'abord, SVNKit n'est pas un logiciel libre et il semble qu'il ne soit développé que par une équipe de quelques personnes. La licence de SVNKit est aussi plus restrictive que celle de Subversion. Enfin, en voulant être une bibliothèque Subversion écrite uniquement en Java, SVNKit est limité dans sa capacité à cloner les fonctionnalités de Subversion au fur et à mesure de la sortie de nouvelles versions de ce dernier. Ce problème est déjà apparu une fois : SVNKit ne peut pas accéder à des dépôts Subversion utilisant une base de données BDB via le protocole file:// car il n'existe pas d'implémentation 100 % Java de Berkeley DB qui soit compatible avec le format de fichier de l'implémentation native de cette bibliothèque.
- Ceci dit, SVNKit est unaninement reconnu pour sa fiabilité. Et une solution 100 % Java est beaucoup plus robuste vis-à-vis des erreurs de programmation — un bogue dans SVNKit générera une exception Java que vous pourrez intercepter tandis qu'un bogue dans une bibliothèque du cœur de Subversion utilisée par javahl peut mettre par terre tout votre environnement d'exécution Java. En conclusion, pesez le pour et le contre avant de choisir une implémentation en Java de Subversion.
Code Samples
Example 8.1, “Using the Repository Layer” contains a code segment (written in C) that illustrates some of the concepts we've been discussing. It uses both the repository and filesystem interfaces (as can be determined by the prefixes svn_repos_ and svn_fs_ of the function names, respectively) to create a new revision in which a directory is added. You can see the use of an APR pool, which is passed around for memory allocation purposes. Also, the code reveals a somewhat obscure fact about Subversion error handling—all Subversion errors must be explicitly handled to avoid memory leakage (and in some cases, application failure).
L'exemple 8.1, "Utiliser la couche dépôt" contient un bout de code (écrit en C) qui illustre plusieurs concepts que nous venons d'aborder. Il utilise à la fois l'interface du dépôt et celle du système de fichiers (comme dénoté par les préfixes svn_repos_ et svn_fs_ des noms de fonctions) pour créer une nouvelle révision dans laquelle un répertoire est ajouté. Vous pouvez y observer l'utilisation du réservoir de mémoire APR qui est utilisé pour les besoins d'allocation mémoire. En outre, le code révèle le côté obscur de la gestion des erreurs de Subversion — toutes les erreurs Subversion doivent être explicitement prises en compte pour éviter des fuites de mémoire (et dans certains cas, le plantage de l'application).
Example 8.1. Using the Repository Layer
/* Convert a Subversion error into a simple boolean error code.
*
* NOTE: Subversion errors must be cleared (using svn_error_clear())
* because they are allocated from the global pool, else memory
* leaking occurs.
*/
#define INT_ERR(expr) \
do { \
svn_error_t *__temperr = (expr); \
if (__temperr) \
{ \
svn_error_clear(__temperr); \
return 1; \
} \
return 0; \
} while (0)
/* Create a new directory at the path NEW_DIRECTORY in the Subversion
* repository located at REPOS_PATH. Perform all memory allocation in
* POOL. This function will create a new revision for the addition of
* NEW_DIRECTORY. Return zero if the operation completes
* successfully, nonzero otherwise.
*/
static int
make_new_directory(const char *repos_path,
const char *new_directory,
apr_pool_t *pool)
{
svn_error_t *err;
svn_repos_t *repos;
svn_fs_t *fs;
svn_revnum_t youngest_rev;
svn_fs_txn_t *txn;
svn_fs_root_t *txn_root;
const char *conflict_str;
/* Open the repository located at REPOS_PATH.
*/
INT_ERR(svn_repos_open(&repos, repos_path, pool));
/* Get a pointer to the filesystem object that is stored in REPOS.
*/
fs = svn_repos_fs(repos);
/* Ask the filesystem to tell us the youngest revision that
* currently exists.
*/
INT_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
/* Begin a new transaction that is based on YOUNGEST_REV. We are
* less likely to have our later commit rejected as conflicting if we
* always try to make our changes against a copy of the latest snapshot
* of the filesystem tree.
*/
INT_ERR(svn_repos_fs_begin_txn_for_commit2(&txn, repos, youngest_rev,
apr_hash_make(pool), pool));
/* Now that we have started a new Subversion transaction, get a root
* object that represents that transaction.
*/
INT_ERR(svn_fs_txn_root(&txn_root, txn, pool));
/* Create our new directory under the transaction root, at the path
* NEW_DIRECTORY.
*/
INT_ERR(svn_fs_make_dir(txn_root, new_directory, pool));
/* Commit the transaction, creating a new revision of the filesystem
* which includes our added directory path.
*/
err = svn_repos_fs_commit_txn(&conflict_str, repos,
&youngest_rev, txn, pool);
if (! err)
{
/* No error? Excellent! Print a brief report of our success.
*/
printf("Directory '%s' was successfully added as new revision "
"'%ld'.\n", new_directory, youngest_rev);
}
else if (err->apr_err == SVN_ERR_FS_CONFLICT)
{
/* Uh-oh. Our commit failed as the result of a conflict
* (someone else seems to have made changes to the same area
* of the filesystem that we tried to modify). Print an error
* message.
*/
printf("A conflict occurred at path '%s' while attempting "
"to add directory '%s' to the repository at '%s'.\n",
conflict_str, new_directory, repos_path);
}
else
{
/* Some other error has occurred. Print an error message.
*/
printf("An error occurred while attempting to add directory '%s' "
"to the repository at '%s'.\n",
new_directory, repos_path);
}
INT_ERR(err);
}
Exemple 8.1. Utilisation de la couche dépôt
/* Convertit une erreur Subversion en un simple code d'erreur booléen
*
* NOTE: Les erreurs Subversion doivent être effacées (en utilisant
* svn_error_clear()) parce qu'elle sont allouées depuis le
* réservoir global, sinon cela produit une fuite de mémoire.
*/
#define INT_ERR(expr) \
do { \
svn_error_t *__temperr = (expr); \
if (__temperr) \
{ \
svn_error_clear(__temperr); \
return 1; \
} \
return 0; \
} while (0)
/* Crée un nouveau répertoire NOUVEAU_REP dans le dépôt Subversion
* situé à CHEMIN_DEPOT. Effectue toutes les allocations mémoire dans
* RESERVOIR. Cette fonction créera une nouvelle révision pour l'ajout
* de NOUVEAU_REP. Elle retourne zéro si l'opération se termine
* correctement, une valeur différente de zéro sinon.
*/
static int
cree_nouveau_rep(const char *chemin_depot,
const char *nouveau_rep,
apr_pool_t *reservoir)
{
svn_error_t *err;
svn_repos_t *depot;
svn_fs_t *fs;
svn_revnum_t derniere_rev;
svn_fs_txn_t *transaction;
svn_fs_root_t *racine_transaction;
const char *chaine_conflit;
/* Ouvre le dépôt situé à chemin_depot.
*/
INT_ERR(svn_repos_open(&depot, chemin_depot, reservoir));
/* Obtient un pointeur sur l'objet du système de fichiers qui est
* stocké dans CHEMIN_DEPOT.
*/
fs = svn_repos_fs(depot);
/* Demande au système de fichiers de nous fournir le numéro de la
* révision la plus récente.
*/
INT_ERR(svn_fs_youngest_rev(&derniere_rev, fs, reservoir));
/* Commence une nouvelle transaction qui est basée sur DERNIERE_REV.
* Nous aurons moins de chance de voir notre propagation rejetée pour
* cause de conflit si nous effectuons toujours nos changements à partir du
* dernier instantané de l'arborescence du système de fichiers.
*/
INT_ERR(svn_repos_fs_begin_txn_for_commit2(&transaction, depot,
derniere_rev,
apr_hash_make(reservoir),
reservoir));
/* Maintenant qu'une nouvelle transaction Subversion est commencée,
* obtient l'objet racine qui représente cette transaction.
*/
INT_ERR(svn_fs_txn_root(&racine_transaction, transaction, reservoir));
/* Crée un nouveau répertoire sous la racine de la transaction, au
* chemin NOUVEAU_REP.
*/
INT_ERR(svn_fs_make_dir(racine_transaction, nouveau_rep, reservoir));
/* Propage la transaction, créant une nouvelle révision du système de
* fichiers incluant le nouveau répertoire.
*/
err = svn_repos_fs_commit_txn(&chaine_conflit, depot,
&derniere_rev, transaction, reservoir);
if (! err)
{
/* Pas d'erreur ? Excellent ! Indique brièvement la réussite
* de l'opération.
*/
printf("Le répertoire '%s' a été ajouté en tant que nouvelle "
"révision '%ld'.\n", nouveau_rep, derniere_rev);
}
else if (err->apr_err == SVN_ERR_FS_CONFLICT)
{
/* Oh-oh. La propagation a échoué pour cause de conflit (il semble
* que quelqu'un d'autre a effectué des changements dans la même
* zone du système de fichiers que celle que nous avons essayé de
* modifier). Affiche un message d'erreur.
*/
printf("Un conflit s'est produit pour le chemin '%s' lors de"
" l'ajout du répertoire '%s' au dépôt '%s'.\n",
chaine_conflit, nouveau_rep, chemin_depot);
}
else
{
/* Une autre erreur s'est produite. Affiche un message d'erreur.
*/
printf("Une erreur s'est produite lors de l'ajout du "
"répertoire '%s' au dépôt '%s'.\n",
nouveau_rep, chemin_depot);
}
INT_ERR(err);
}
Note that in Example 8.1, “Using the Repository Layer”, the code could just as easily have committed the transaction using svn_fs_commit_txn(). But the filesystem API knows nothing about the repository library's hook mechanism. If you want your Subversion repository to automatically perform some set of non-Subversion tasks every time you commit a transaction (e.g., sending an email that describes all the changes made in that transaction to your developer mailing list), you need to use the libsvn_repos-wrapped version of that function, which adds the hook triggering functionality—in this case, svn_repos_fs_commit_txn(). (For more information regarding Subversion's repository hooks, see the section called “Implementing Repository Hooks”.)
Now let's switch languages. Example 8.2, “Using the Repository layer with Python” is a sample program that uses Subversion's SWIG Python bindings to recursively crawl the youngest repository revision, and to print the various paths reached during the crawl.
Notez que dans l'exemple 8.1, "Utiliser la couche dépôt", le code aurait tout aussi bien pu propager la transaction en utilisant svn_fs_commit_txn(). Mais l'API du système de fichiers ignore tout des mécanismes de procédures automatiques de la bibliothèque du dépôt. Si vous voulez que votre dépôt Subversion effectue automatiquement certaines tâches externes à Subversion chaque fois qu'une transaction est propagée (par exemple envoyer un mail qui décrit les changements effectués dans la transaction vers la liste de diffusion des développeurs), vous devez utiliser la version de la fonction encapsulée dans libsvn_repos, qui ajoute la fonctionnalité d'activation des procédures automatiques : svn_repos_fs_commit_txn() (pour davantage d'informations sur les procédures automatiques des dépôts Subversion, consultez la section intitulée "Mettre en place des procédures automatiques").
Maintenant, changeons de langage. L'exemple 8.2, "Utiliser la couche dépôt avec Python" est un programme de démonstration qui utilise l'interface SWIG vers Python pour parcourir récursivement la dernière révision du dépôt et afficher les différents chemins trouvés lors de ce parcours.Example 8.2. Using the Repository layer with Python
#!/usr/bin/python
"""Crawl a repository, printing versioned object path names."""
import sys
import os.path
import svn.fs, svn.core, svn.repos
def crawl_filesystem_dir(root, directory):
"""Recursively crawl DIRECTORY under ROOT in the filesystem, and return
a list of all the paths at or below DIRECTORY."""
# Print the name of this path.
print directory + "/"
# Get the directory entries for DIRECTORY.
entries = svn.fs.svn_fs_dir_entries(root, directory)
# Loop over the entries.
names = entries.keys()
for name in names:
# Calculate the entry's full path.
full_path = directory + '/' + name
# If the entry is a directory, recurse. The recursion will return
# a list with the entry and all its children, which we will add to
# our running list of paths.
if svn.fs.svn_fs_is_dir(root, full_path):
crawl_filesystem_dir(root, full_path)
else:
# Else it's a file, so print its path here.
print full_path
def crawl_youngest(repos_path):
"""Open the repository at REPOS_PATH, and recursively crawl its
youngest revision."""
# Open the repository at REPOS_PATH, and get a reference to its
# versioning filesystem.
repos_obj = svn.repos.svn_repos_open(repos_path)
fs_obj = svn.repos.svn_repos_fs(repos_obj)
# Query the current youngest revision.
youngest_rev = svn.fs.svn_fs_youngest_rev(fs_obj)
# Open a root object representing the youngest (HEAD) revision.
root_obj = svn.fs.svn_fs_revision_root(fs_obj, youngest_rev)
# Do the recursive crawl.
crawl_filesystem_dir(root_obj, "")
if __name__ == "__main__":
# Check for sane usage.
if len(sys.argv) != 2:
sys.stderr.write("Usage: %s REPOS_PATH\n"
% (os.path.basename(sys.argv[0])))
sys.exit(1)
# Canonicalize the repository path.
repos_path = svn.core.svn_path_canonicalize(sys.argv[1])
# Do the real work.
crawl_youngest(repos_path)
Exemple 8.2. Utiliser la couche dépôt avec Python
#!/usr/bin/python
"""Parcourir un dépôt en affichant les chemins des objets suivis en
versions."""
import sys
import os.path
import svn.fs, svn.core, svn.repos
def parcourir_rep_systemedefichiers(racine, repertoire):
"""Parcourt récursivement le REPERTOIRE situé sous RACINE dans le système de
fichiers. Renvoie la liste de tous les chemins sous et de REPERTOIRE."""
# Affiche le nom de ce chemin.
print repertoire + "/"
# Obtient les entrées du répertoire REPERTOIRE.
entrees = svn.fs.svn_fs_dir_entries(racine, repertoire)
# Pour chaque entrée
noms = entrees.keys()
for nom in noms:
# Calcule le chemin complet de l'entrée.
chemin_complet = repertoire + '/' + nom
# Si l'entrée est un répertoire, effectue une récursion. La
# récursion retournera une liste comprenant l'entrée et tous ses
# enfants, que l'on ajoutera à notre liste.
if svn.fs.svn_fs_is_dir(racine, chemin_complet):
parcourir_rep_systemedefichiers(racine, chemin_complet)
else:
# Sinon, c'est un fichier donc l'afficher maintenant
print chemin_complet
def parcourir_la_plus_recente_revision(chemin_depot):
"""Ouvre le dépôt situé à CHEMIN_DEPOT et effectue un parcours
récursif de la révision la plus récente."""
# Ouvre le dépôt situé à CHEMIN_DEPOT et obtient une référence de
# son système de fichiers suivi en versions.
objet_depot = svn.repos.svn_repos_open(chemin_depot)
objet_fs = svn.repos.svn_repos_fs(objet_depot)
# Obtient la révision la plus récente (HEAD).
rev_la_plus_recente = svn.fs.svn_fs_youngest_rev(objet_fs)
# Ouvre un objet racine représentant la révision la plus récente.
objet_racine = svn.fs.svn_fs_revision_root(objet_fs,
rev_la_plus_recente)
# Effectue le parcours récursif.
parcourir_rep_systemedefichiers(objet_racine, "")
if __name__ == "__main__":
# Vérifie que l'on est appelé correctement.
if len(sys.argv) != 2:
sys.stderr.write("Usage: %s CHEMIN_DEPOT\n"
% (os.path.basename(sys.argv[0])))
sys.exit(1)
# transforme la chaine en chemin canonique.
chemin_depot = svn.core.svn_path_canonicalize(sys.argv[1])
# Et c'est parti !
parcourir_la_plus_recente_revision(chemin_depot)
This same program in C would need to deal with APR's memory pool system. But Python handles memory usage automatically, and Subversion's Python bindings adhere to that convention. In C, you'd be working with custom datatypes (such as those provided by the APR library) for representing the hash of entries and the list of paths, but Python has hashes (called “dictionaries”) and lists as built-in datatypes, and it provides a rich collection of functions for operating on those types. So SWIG (with the help of some customizations in Subversion's language bindings layer) takes care of mapping those custom datatypes into the native datatypes of the target language. This provides a more intuitive interface for users of that language.
The Subversion Python bindings can be used for working copy operations, too. In the previous section of this chapter, we mentioned the libsvn_client interface and how it exists for the sole purpose of simplifying the process of writing a Subversion client. Example 8.3, “A Python status crawler” is a brief example of how that library can be accessed via the SWIG Python bindings to re-create a scaled-down version of the svn status command.
Le même programme en C aurait besoin de faire appel aux réservoirs de mémoire d'APR. Mais Python gère l'utilisation de la mémoire automatiquement et l'interface Subversion vers Python se plie à cette convention. En C, vous auriez utilisé des types de données personnalisés (tels que ceux fournis par la bibliothèque APR) pour représenter la table de hachage des entrées et la liste des chemins, mais Python sait gérer les tables de hachage (appelés "dictionnaires") et les listes nativement et possède une riche collection de fonctions pour travailler sur ces types de données. C'est pourquoi SWIG (avec l'aide de la couche d'interface vers les langages de programmation de Subversion, un peu modifiée) prend soin de faire correspondre ces types de données personnalisés aux types de données natifs du langage cible. On obtient ainsi une interface plus intuitive pour les utilisateurs de ce langage.
L'interface de Subversion vers Python peut également être utilisée pour effectuer des opérations dans la copie de travail. Dans la section précédente de ce chapitre, nous avons mentionné l'interface libsvn_client et la façon dont elle a été conçue dans le seul but de faciliter l'écriture d'un client Subversion. L'exemple 8.3, "Une version en Python de status" est un court exemple d'utilisation de cette bibliothèque via l'interface Python SWIG pour re-créer une version à petite échelle de la commande svn status.Example 8.3. A Python status crawler
#!/usr/bin/env python
"""Crawl a working copy directory, printing status information."""
import sys
import os.path
import getopt
import svn.core, svn.client, svn.wc
def generate_status_code(status):
"""Translate a status value into a single-character status code,
using the same logic as the Subversion command-line client."""
code_map = { svn.wc.svn_wc_status_none : ' ',
svn.wc.svn_wc_status_normal : ' ',
svn.wc.svn_wc_status_added : 'A',
svn.wc.svn_wc_status_missing : '!',
svn.wc.svn_wc_status_incomplete : '!',
svn.wc.svn_wc_status_deleted : 'D',
svn.wc.svn_wc_status_replaced : 'R',
svn.wc.svn_wc_status_modified : 'M',
svn.wc.svn_wc_status_merged : 'G',
svn.wc.svn_wc_status_conflicted : 'C',
svn.wc.svn_wc_status_obstructed : '~',
svn.wc.svn_wc_status_ignored : 'I',
svn.wc.svn_wc_status_external : 'X',
svn.wc.svn_wc_status_unversioned : '?',
}
return code_map.get(status, '?')
def do_status(wc_path, verbose):
# Build a client context baton.
ctx = svn.client.svn_client_ctx_t()
def _status_callback(path, status):
"""A callback function for svn_client_status."""
# Print the path, minus the bit that overlaps with the root of
# the status crawl
text_status = generate_status_code(status.text_status)
prop_status = generate_status_code(status.prop_status)
print '%s%s %s' % (text_status, prop_status, path)
# Do the status crawl, using _status_callback() as our callback function.
revision = svn.core.svn_opt_revision_t()
revision.type = svn.core.svn_opt_revision_head
svn.client.svn_client_status2(wc_path, revision, _status_callback,
svn.core.svn_depth_infinity, verbose,
0, 0, 1, ctx)
def usage_and_exit(errorcode):
"""Print usage message, and exit with ERRORCODE."""
stream = errorcode and sys.stderr or sys.stdout
stream.write("""Usage: %s OPTIONS WC-PATH
Options:
--help, -h : Show this usage message
--verbose, -v : Show all statuses, even uninteresting ones
""" % (os.path.basename(sys.argv[0])))
sys.exit(errorcode)
if __name__ == '__main__':
# Parse command-line options.
try:
opts, args = getopt.getopt(sys.argv[1:], "hv", ["help", "verbose"])
except getopt.GetoptError:
usage_and_exit(1)
verbose = 0
for opt, arg in opts:
if opt in ("-h", "--help"):
usage_and_exit(0)
if opt in ("-v", "--verbose"):
verbose = 1
if len(args) != 1:
usage_and_exit(2)
# Canonicalize the repository path.
wc_path = svn.core.svn_path_canonicalize(args[0])
# Do the real work.
try:
do_status(wc_path, verbose)
except svn.core.SubversionException, e:
sys.stderr.write("Error (%d): %s\n" % (e.apr_err, e.message))
sys.exit(1)
Exemple 8.3. Une version en Python de status
#!/usr/bin/env python
"""Parcourir un répertoire d'une copie de travail en affichant les
informations d etat."""
import sys
import os.path
import getopt
import svn.core, svn.client, svn.wc
def generer_code_etat(etat):
"""Traduit la valeur d'état vers un code à un caractère en
utilisant la même logique que le client Subversion en ligne de
commande."""
association_etat = { svn.wc.svn_wc_status_none : ' ',
svn.wc.svn_wc_status_normal : ' ',
svn.wc.svn_wc_status_added : 'A',
svn.wc.svn_wc_status_missing : '!',
svn.wc.svn_wc_status_incomplete : '!',
svn.wc.svn_wc_status_deleted : 'D',
svn.wc.svn_wc_status_replaced : 'R',
svn.wc.svn_wc_status_modified : 'M',
svn.wc.svn_wc_status_merged : 'G',
svn.wc.svn_wc_status_conflicted : 'C',
svn.wc.svn_wc_status_obstructed : '~',
svn.wc.svn_wc_status_ignored : 'I',
svn.wc.svn_wc_status_external : 'X',
svn.wc.svn_wc_status_unversioned : '?',
}
return association_etat.get(etat, '?')
def trouver_etat(chemin_copie_travail, verbeux):
# Construit le "bâton" de contexte client.
ctx = svn.client.svn_client_ctx_t()
def _status_callback(path, etat):
"""Une fonction de renvoi ("callback") pour svn_client_status."""
# Affiche le chemin, moins la partie déjà présente
# dans la racine du parcours
text_status = generer_code_etat(etat.text_status)
prop_status = generer_code_etat(etat.prop_status)
print '%s%s %s' % (text_status, prop_status, path)
# Effectue le parcours des états, en utilisant _status_callback()
# comme fonction de renvoi ("callback").
revision = svn.core.svn_opt_revision_t()
revision.type = svn.core.svn_opt_revision_head
svn.client.svn_client_status2(chemin_copie_travail, revision,
_status_callback,
svn.core.svn_depth_infinity, verbeux,
0, 0, 1, ctx)
def utilisation_et_sortie(code_erreur):
"""Affiche le message d'utilisation et sort avec CODE_ERREUR."""
stream = code_erreur and sys.stderr or sys.stdout
stream.write("""Usage: %s OPTIONS CHEMIN_COPIE_TRAVAIL
Options:
--help, -h : Affiche ce message d'aide.
--verbose, -v : Affiche les états de tous les objets, sans exception
""" % (os.path.basename(sys.argv[0])))
sys.exit(code_erreur)
if __name__ == '__main__':
# Analyse les options de la ligne de commande.
try:
opts, args = getopt.getopt(sys.argv[1:], "hv", ["help", "verbose"])
except getopt.GetoptError:
utilisation_et_sortie(1)
verbeux = 0
for opt, arg in opts:
if opt in ("-h", "--help"):
utilisation_et_sortie(0)
if opt in ("-v", "--verbeux"):
verbeux = 1
if len(args) != 1:
utilisation_et_sortie(2)
# Transforme le chemin en chemin canonique.
chemin_copie_travail = svn.core.svn_path_canonicalize(args[0])
# Et c'est parti !
try:
trouver_etat(chemin_copie_travail, verbeux)
except svn.core.SubversionException, e:
sys.stderr.write("Erreur (%d): %s\n" % (e.apr_err, e.message))
sys.exit(1)
As was the case in Example 8.2, “Using the Repository layer with Python”, this program is pool-free and uses, for the most part, normal Python datatypes. The call to svn_client_ctx_t() is deceiving because the public Subversion API has no such function—this just happens to be a case where SWIG's automatic language generation bleeds through a little bit (the function is a sort of factory function for Python's version of the corresponding complex C structure). Also note that the path passed to this program (like the last one) gets run through svn_path_canonicalize(), because to not do so runs the risk of triggering the underlying Subversion C library's assertions about such things, which translates into rather immediate and unceremonious program abortion.

