RADE |
Source Code Manager |
The SCM MergerMerging concurrent changes |
Use Case |
AbstractThis article explains how concurrent changes happen and how to solve conflicting changes using the SCM Merger. |
Any change is performed inside a workspace that provides a isolated working
place. By the way it is possible for several users to do the same changes by
their sides and to find the two changes in the same workspace through data
exchanges.
There are two kinds of changes: those that can be performed concurrently and
those that only one user can do.
The default working mode depends on the action and the type of file involved:
These default modes can be overridden at any time by using special options:
When two conflicting changes become in the same workspace, this leads to a conflicting situation that must be solved in that workspace in order to obtain a consistent workspace content. Those situations are explained in the next section.
[Top]
|
The figure 1 shows a workspace tree in which two workspaces have
modified the same file (WS4 and WS6). These files can be delivered and
will go through workspaces and this will not lead to a conflict while the
two versions of the file don't come in the same workspace. For instance a
conflicting situation might occur in:
|
Like shown above a merging situation can occur in different workspaces and not necessarily in the workspaces where the conflicting changes were made. In that case it is not easy for the workspace owner to know how to solve the conflict by choosing one file version or merging the two versions. To help the user the adl_solve_merge [5] command displays information about the conflicting changes like the origin workspaces and the date at which they have been done.
[Top]
When the same file is modified in two workspaces, this leads to generate two versions of the same file. When such a merge is to be solved, the two file contents must be merged by choosing what has been modified in each version and creating a new version integrating all the changes.
When a merge is to be solved between two versions of the same file, there is in fact three versions involved:
There can be several intermediate versions of files between the ancestor version and the two conflicting versions because more than one user/workspace may have contributed to the modifications before a merge occurs. For that reason, the adl_solve_merge [5] command uses the three versions when solving a merge:
[Top]
When the same file is created in two workspaces, the second user who tries to create the file is warned that this file (complete path from the root workspace directory) is already registered in SCM. However this user can force the creation if he/she cannot wait for the other file to come into his/her workspace (through the workspace hierarchy). To avoid problem, the better solution is to import the first file with adl_import command.
The two files have no common origin and two files can be completely different even they have the same name.
When the two files are seen in the same workspace (adl_sync for example), SCM generates a conflict to be solved and projects the two files under two different names:
Note that more than two files can be conflicting, in that case, the files will be projected using different suffixes (%c1, %c2, %c3, ...).
C:\Temp\W1>adl_mk_elem Fw1/Mod1.m/src/File3.cpp Other SCM objects in the same folder whose names conflict with "File3.cpp": "File3.cpp" (File element) in the workspace W11 in the workspace tree T1 Looking for an existing SCM object named "File3.cpp" #ERR# ADLCMD - 0941: Other SCM objects whose names conflict with "File3.cpp" have been found. Choose another name, or use the -force option if you have found no conflicting object in the workspaces specified by the message. BEWARE: this option may later create projection conflicts with previously specified objects, in your workspace or in another one. ##### THE COMMAND ENTIRELY OR PARTIALLY FAILED ##### C:\Temp\W1>adl_sync Synchronizing the workspace "W11" in the workspace tree "T1". New current modifications: Fw1\Mod1.m\src\File3.cpp has been created Fw1\Mod1.m\src\File3.cpp%c1 has been moved from Fw1\Mod1.m\src\File3.cpp Moves created because of projection conflicts: Fw1\Mod1.m\src\File3.cpp%c1 Merges to solve: Fw1\Mod1.m\src\File3.cpp%c1 Move Extract adl_solve_merge Fw1\Mod1.m\src\File3.cpp%c1: Move Common ancestor: 0 - Fw1\Mod1.m\src\File3.cpp - PROJECTION CONFLICT Created by nle with adl_mk_elem on Tuesday, July 22, 2003 12:55:42 PM in the workspace W11, tree T1. Changes to merge: 1 - Fw1\Mod1.m\src\File3.cpp%c1 Seen as the current one in the workspace. Created by nle with adl_sync on Tuesday, July 22, 2003 01:04:33 PM in the workspace W11, tree T1. 2 - Fw1\Mod1.m\src\File3.cpp - PROJECTION CONFLICT Created by nle with adl_sync on Tuesday, July 22, 2003 01:04:33 PM in the workspace W11, tree T1. Enter - 0, 1... to choose one of these moves - L to list the changes again - N to go to the next merge - M to come back to the main menu - Q to quit Your choice: |
[Top]
When the same file is renamed to two different names by two users, it is not possible for SCM to choose a projected name when the corresponding deliveries come into the same workspace. In this case, the adl_solve_merge [5] command displays the original name and the two names and let the user chooses the correct name.
Note that renaming a file may cause other files to be modified, especially if it is a header file. However, in that case, the two users may have modified other files according to the header file name and this should lead to other conflicts (of file contents) in the same workspace.
A move conflict corresponds to the same file moved into two different directories. Like when solving a rename conflict, the adl_solve_merge command gives the original location and the two new locations and let the user chooses the correct one.
[Top]
Conflicts occur in workspaces and when this happens, SCM obliges the user to solve them before being allowed to continue to work. This rule has been set up to be sure that any user works on a consistent set of files.
|
Considering a workspace and a child workspace, the default working mode
forbids conflicts to occur at collect (in "prj" workspace) in
figure 2). It means that each promotion from any child workspace will be
analyzed to detect if the change set of modified files contains at least
one file already modified by another workspace and already collected in
the parent workspace. In this case, the promotion will be refused and the
user will be warned that some files are conflicting.
For instance, if the same file is modified in both "Dev1" and "Dev2" workspaces and if "Dev1" promotes and is collected in "Prj", "Dev2" won't be allowed to promote. The figure 2 shows the different steps of the process to let Dev2 promotes its changes:
|
This default working mode can be modified using the adl_set_ws [2] command. This command provides the -merge_at_collect option that is used for changing the mode associated with a given workspace. When a workspace allows merges to occur at collect, it means that any child workspace can promote a change (of content or move or rename) that will conflict with a change already present in the parent workspace.
To know the current behavior of a given workspace, run the adl_ds_ws command on this workspace:
E:\users\cga>adl_ds_ws toolssdfint Current workspace: ToolsSDFINT No current image Image(s): WINDOWS Windows Not refreshed \\addams\home\ToolsSDFINT UNIX Unix Refreshed /u/lego/SCMV5/ToolsSDFINT Workspace tree: TOOLSSDF Tool config key: TOOLSSDF (locally defined) Merge solving delay: Immediate (If necessary, the interactif merger is launched at the end of the command) Merge solving mode: Automatic (The basic merges are solved automatically without user interaction) Merge at collect: Forbidden Promotion request: Allowed from child workspaces to the current workspace Child promotion: Child workspaces must be synchronized with the last revision for promote Current revision: 147 No parent workspace (root workspace) Published: Yes last published on Wed May 24 16:02:27 2000 rev 147 Wed May 24 16:02:27 2000 PUBLIC Child collected: Yes |
Note that the default mode is to forbid merges at collect because the parent workspace is often managed by one user who is not responsible of the child workspaces and by the way who is not necessarily able to solve the merges. Conversely obliging a child workspace to solve a merge means that the user who solves merges is often the one who has done one conflicting change set and knows how to fix the conflicts.
[Top]
The adl_set_ws [2] command is used for choosing when the adl_solve_merge [5] command is launched:
It is also possible to control automatic merges (for instance when two different parts of a same file are modified by two workspaces). The adl_set_merge command provides two options for doing it:
Note: even if two versions of the same file can be merged automatically, this does not ensure that the result is correct. If you want to be sure that any merge produces a correct result, you must execute adl_set_ws -no_auto on your workspace(s) and solve manually any merge that occurs. It is interesting, useful and safe to allow automatic merges in case almost all programs managed in workspaces are covered by test cases.
[Top]
When the adl_solve_merge [5] command is launched, SCM :
- tries solve implicit merges and
- tries to solve automatic merges if the -auto option is set for the
workspace (see the -auto option in Immediate
or Deferred - Automatic or Manual paragraph).
E:\users\cga> adl_solve_merge MyRootLocalWs Solving implicit and automatic merges in workspace "MyRootLocalWs" in the workspace tree "treelocal". No merge can be solved. Solving manual merges in workspace "MyRootLocalWs" ... |
This means, that when it is possible, SCM can solve the merge according to the following rules:
When the same file is modified in two workspaces, this leads to generate two
versions of the same file.
When such a merge is to be solved, SCM firstly checks if it can solve it
implicitly.
SCM can solve a change of content merge implicitly if the two files fulfil
the 3 following conditions:
- the two files have the same content,
- the two files have the same size,
- the two files have the same date.
If the merge cannot be solved implicitly, SCM tries to solve it automatically if the workspace allows it (see the -auto option in Immediate or Deferred - Automatic or Manual paragraph).
For an automatic change of content merge, SCM launches the merger in an automatic mode.
The merger computes the 3 files (the common ancestor, version 1 and version
2) to get two sets of differences and compare them.
The first set of differences is computed by comparing the common ancestor and
the version 1.
The second set of differences is computed by comparing the common ancestor and
the version 2.
If all the differences computed can be solve automatically, the merger produces the result as following :
Ancestor | Version 1 | Version 2 | Result | Résolution |
---|---|---|---|---|
a block of lines | same block | same block | no change: same block | Automatic |
a block of lines | same block | some lines modified | conflicting version is chosen | Automatic |
a block of lines | same block | lines deleted | conflicting version is chosen | Automatic |
a block of lines | some lines modified | some lines modified | a manual merge is needed | (*) |
nothing | nothing | some lines added | conflicting version is chosen | Automatic |
nothing | some lines added | some lines added | a manual merge is needed | (*) |
a block of lines | some lines modified | lines deleted | a manual merge is needed | |
a block of lines | lines deleted | lines deleted | lines are deleted in the result file | Automatic |
Note (*): the merge can be automatic if the lines added or modified are the same in the two files.
Remark: this mode is the same than the one used by the graphic merger in the mode with recommendations.
[Top]
When the same object is created (adl_mk_xxx), deleted (adl_rm) or recreated (adl_unrm) in two different workspaces, SCM tries to solve the merge implicitly.
This is only possible when the merging conflicts are one of the following
cases:
- the same object has been deleted (adl_rm) in two workspaces,
- the same object has been recreated(adl_unrm) in two workspaces.
There is no automatic merge possible for this type of change.
[Top]
When the same object is renamed or moved int two different workspaces, SCM tries to solve the merge implicitly.
This is only possible if the object has finally:
- the same name, and
- the same folder
in the two workspaces.
There is no automatic merge possible for this type of change.
[Top]
SCM provides an interactive tool for solving merges when conflicting changes come into a given workspace.
The adl_solve_merge command can be launched by specifying the workspace where are the merges and it is not needed neither this workspace is the current working workspace nor to have selected a current working image. Versions of files are found in the central repository and possibly extracted in temporary files.
[Top]
When the adl_solve_merge [5] command is launched, it displays the list of merges to be solved followed by a first menu. This first menu is used for selecting one merge to solve, inhibit the use of the graphic tool or quit the adl_solve_merge command.
Here is an example showing a list of merges and the first menu for solving them:
Solving manual merges in workspace "MergeToolsSDF" Merge type: Change of content 1 - CATToolsInfrastructure.tst/TAFileName01.m/src/main.cpp TOOLSSDF 2 - EnoviaDMC/CATVBTDevEnovia.m/src/CATVBTCEnoviaAddin.cpp TOOLSSDF 3 - TCKManagement/TCKInstall.m/src/TCKInstall.cpp TOOLSSDF 4 - TestAuto/CATODTManager.m/src/mkodt.cpp TOOLSSDF Enter - 1, 2, ... to solve a merge - G not to use the graphic tool for text file contents - L to list the merges to solve again - Q to quit Your choice: |
The files or directories involved in each merge are displayed as they could
be projected in the workspace. Different types of merges are classified and the
lists are sorted in alphabetical order (upper and lower case are not
significant).
Each line is composed of three parts:
The merge lists are followed by a first level menu allowing the user to
When a merge is selected, whatever its type is (move, change of content, creation), the merger displays information about the conflict. The following example shows the selection of a merge on a file content:
For each version, the merger gives the date at which the version was created, the author of this version and the workspace where it was created. These information are useful when the current user is not responsible of the files to be merged and he/she can therefore ask the different authors for helping him/her to solve the merges. For instance:
Other sub-menus exist that appear regarding the current operation. They are described here after.
[Top]
This menu appears either because there is more than two conflicting versions or because the graphic tool is not used or because it was launched but the user has quitted it leaving some unresolved merges.
Choice Displayed in Menu | Purpose |
---|---|
1,2, ... to choose one of these contents | The different conflicting versions are displayed and one can be chosen as the merge result. |
D to change the directory | To change the current working directory if willing to give a file as the result file. |
P to project the files into the directory | Conflicting files can be extracted in a temporary directory from which other tools can be used for merging them and producing a result file. |
U to project useful additional contents | One or several additional versions can be projected in the temporary directory. If they exist, they correspond to close versions that have been already integrated in one of the conflicting versions. Comparing them with the conflicting versions may help to understand what are the last changes. |
F to enter the result file path | To give a file that will be registered as the result file. |
L to list the changes again | To display information about conflicting versions. |
N to go to the next merge | To leave the current merge unresolved and go to the next merge to solve. |
M to come back to the main menu | To leave the current merge unresolved and go back to the main menu to select another merge to solve. |
Q to quit | To stop the adl_solve_merge command possibly leaving some unresolved merges. |
[Top]
This menu appears each time a merge concerning a conflicting Move/Rename is selected from the main menu.
Choice Displayed in Menu | Purpose |
---|---|
0, 1... to choose one of these moves | The different conflicting names are displayed and one can be chosen as the final name. |
L to list the changes again | To display information about conflicting names. |
M to come back to the main menu | To leave the current merge unresolved and go back to the main menu to select another merge to solve. |
Q to quit | To stop the adl_solve_merge command possibly leaving some unresolved merges. |
[Top]
When merging different file contents a graphic tool is called by the adl_solve_merge command if the following conditions are satisfied:
As the graphic tool cannot handle more than two versions, its use is automatically inhibited by the adl_solve_merge command.
When the graphic tool is not inhibited, the previous information are displayed and the tool is automatically launched. It displays the contents of the two versions but the common ancestor is not visible (it is used for finding out differences). Please refer to the dedicated documentation for learning more about using the graphic merger tool [1].
If the graphic tool is not used or if it is run but is killed or is stopped without solving (and saving) all the conflicting parts, the merger creates a temporary directory in which the three versions (ancestor, version 1, version 2) are extracted. It is then possible for the user to run another merging tool or to select directly one of the two versions as the merge result. Here are the outputs of the adl_solve_merge command after canceling the graphic tool:
[Top]
This section is dedicated to explaining the purpose and usage of the 'U'
key in the previous menu.
This option can be used when a basic comparison between the two conflicting
versions leads to too many differences or when some parts that have been already
merged are proposed again for merging.
The purpose of the 'U' key is to search for the previous merges that might have been done between two precedent versions of the two conflicting versions. If such a merge exists, we can compare the version that was merged at that time with one of the current versions and we should reduce the number of differences to deal with.
The following example explains why the same differences are sometimes proposed in successive merges and how we can know what is really new.
Fig 3: workspace tree - step 1 |
Fig 4: file contents version graph - step 1 |
Step 1: the workspaces WS1 and WS2 are synchronized with
the workspace PRJ which contains the version 10 of the file foo.cpp.
The file is checked-out and modified in WS1, this creates the version 11. The same action is performed in the workspace WS2 and this leads to the creation of the version 12. As WS1 and WS2 were synchronized with the same level of PRJ, the version 10 becomes the origin version (common ancestor) of their own versions of foo.cpp. |
Fig 5: workspace tree - step 2 |
Fig 6: file contents version graph - step 2 | Step 2: the workspace WS2 promotes its modification and is collected by the workspace PRJ. The version 12 becomes the new current one in PRJ. |
Fig 7: workspace tree - step 3 |
Fig 8: file contents version graph - step 3 |
Step 3: the workspace PRJ publishes its content and WS1
synchronizes on this new level.
As the synchronization brings the version 12 of foo.cpp in WS1, there is a conflict between the local version 11 and the version 12 from WS2: this conflict leads to a merge. Assuming that the specific changes from WS2 has been merged with the ones in WS1, we have now a new version number 13. This version 13 is visible only in WS1 and replaces the version 11. |
Fig 9: workspace tree - step 4 |
Fig 10: file contents version graph - step 4 |
Step 4: the user in WS1 keeps working without delivering
his/her workspace but the other user checks-out again foo.cpp in WS2 and
add new changes in it.
There is a new version number 14 that is created and whose closest ancestor version is 12. At this time: the version 12 is visible in PRJ, 13 is visible in WS1 and 14 is visible in WS2 |
Fig 11: file contents version graph - steps 5,6 |
Step 5: the workspace WS2 is collected in PRJ and the new
current version of foo.cpp becomes the 14th.
Step 6: the workspace PRJ publishes and WS1 synchronizes on this new level. Like in step 3 the synchronization brings a new version of foo.cpp which is in conflict with the local one: there is a merge to solve between the versions 13 and 14. Like shown in the figure 11 the common ancestor is still the version 10, therefore the merging of 13 and 14 will be based on the differences between both and 10. So, even if nothing new has been done since the last merge, the user will have to merge again what was specific to the version 11.
|
|
Using the 'U' key:
The system will find that the version 12 has been already merged to produce the version 13, then:
How to proceed? The only way to manipulate these additional versions is to project them using the 'U' key, then go into the temporary directory and compare them with the other versions. It's up to the user
|
[Top]
To solve a a merge on conflicting names, there is just to choose one of the different names as the final name. For instance, the following example shows a conflict on a file that was first named "F3.cpp" and that was renamed into "Fic3.cpp" by one user and into "File3.cpp" by another one. Solving the merge will consists in entering either
[Top]
Such a merge is to be solved when two users have created two files using the same name. To avoid to loose one file, when the two files come in the same workspace, SCM renames one of them (using %c1 suffix) and generates a merge on the two files.
When a workspace refuses any merge at collect [2], there is no merge generated when two conflicting creations happen in this workspace and one of the file is automatically renamed (using the %c1 suffix).
When solving such a merge, SCM shows the characteristics of the three versions but only the current version can be selected for solving the merge:
To solve such a merge, select the version '1' to exit from the adl_solve_merge command and then you will find the two files projected with two names (the usual name and the same with a "%c1" suffix). Then compare the two files to know whether
The example here-after shows the solving of a conflict:
--------------------------------------------------------------- fw1/Data.doc/dd2.txt%c1: Move Common ancestor: 0 - fw1/Data.doc/dd2.txt - PROJECTION CONFLICT Created by "cga" in the workspace "dev2" on Fri Jan 19 15:37:01 2001. Changes to merge: 1 - fw1/Data.doc/dd2.txt%c1 Seen as the current one in the workspace. Created by "cga" in the workspace "dev2" on Fri Jan 19 15:38:26 2001. 2 - fw1/Data.doc/dd2.txt - PROJECTION CONFLICT Created by "cga" in the workspace "sample" on Fri Jan 18 10:24:32 2001. Enter - 0, 1... to choose one of these moves - L to list the changes again - M to come back to the main menu - Q to quit Your choice: 1 1 - fw1/Data.doc/dd2.txt%c1 Command successful. Freezing the revision "12" of the workspace "dev2" in the workspace tree "Demo_tree1". Command successful. $ > ls -l total 2 -r--r--r-- 1 cga scott 0 Jan 19 15:34 dd.txt -r--r--r-- 1 cga scott 0 Jan 19 15:37 dd2.txt -r--r--r-- 1 cga scott 0 Jan 19 15:36 dd2.txt%c1 drwxr-xr-x 3 cga scott 512 Jan 19 15:36 lrd $ > adl_mv dd2.txt%c1 dd3.txt Moving the object "fw1/Data.doc/dd2.txt%c1" to "fw1/Data.doc/dd3.txt" in the workspace "dev2" and the workspace tree "Demo_tree1". Command successful. |
[Top]
When a merge has been done but the user thinks that the result is wrong, it is possible to use the adl_forget_chg [3] command to restart the file in the state just before the merge, that is to say in the state where the adl_solve_merge [5] should be executed to solve remaining merges.
$ > adl_solve_merge ... here the merger tool is launched on the file XXX and the result saved ... Now the user thinks that what he has done is not correct and wants to restart the merger tool $ > adl_forget_chg XXX -chg content -last ... $ > adl_solve_merge ... |
[Top]
Solving merges is an operation that is performed at database level but the resulting files must be extracted from the database to the user file system (workspace's image(s)). Considering a current working image and running the adl_solve_merge [5] command:
... Enter - 1, 2... to solve a merge - R to refresh the image - G not to use the graphic tool for text file contents - L to list the merges to solve again - Q to quit Your choice: q Run the command adl_refresh $ |
Notice that the command adl_refresh can be used even there are still some merges to be solved.
[Top]
This directory is created when solving conflicting file contents without using the graphic merger [1].
Its name is:
$ADL_TMP/ADLMerge_WS_TREE_NUMBER
%ADL_TMP%\ADLMerge_WS_TREE_NUMBER
where
ADL_TMP
is an environment variable identifying a temporary
directory (for instance c:\TEMP or /tmp)WS
is the name of the workspace for which the adl_solve_merge
has been launchedTREE
is the name of the tree WS belongs toNUMBER
is a number (a counter of the number of merges solved
in WS since its creation)The adl_solve_merge [5] command projects in this directory all the files displayed when solving a merge. Their names are the original names but with a number inserted before each suffix (more convenient when using a desktop recognizing file suffixes like "NT Explorer"). The following example shows the projection of the conflicting versions of the file "admin.cpp": The number '0' identifies the "common ancestor", the number '1' identifies the version 1 (from the "dev2_cga" workspace) and the number '2' identifies the version 2 (from the "dev1_cga" workspace).
--------------------------------------------------------------- fw_cga\mod_cga.m\src\admin.cpp: Change of content Common ancestor: 0 - Type: Text file, created the Wed May 28 16:00:15 2000 By "cga" into the workspace "prj_cga" the Wed May 31 16:00:15 2000. Changes to merge: 1 - Type: Text file, created the Wed May 31 16:02:29 2000 Seen as the current one in the workspace. By "cga" into the workspace "dev2_cga" the Wed May 31 16:02:29 2000. 2 - Type: Text file, created the Wed May 31 16:01:35 2000 By "cga" into the workspace "dev1_cga" the Wed May 31 16:01:35 2000. Projection directory: C:\TEMP\ADLMerge_prj_cga_TOOLSTST_3 (current) Enter - 0, 1... to choose one of these contents - D to change the directory - P to project the files into the directory - U to project useful additional contents - F to enter the result file path - L to list the changes again - M to come back to the main menu - Q to quit Your choice: q E:\>dir C:\TEMP\ADLMerge_prj_cga_TOOLSTST_3 Volume in drive C has no label. Volume Serial Number is 98C9-78D0 Directory of C:\TEMP\ADLMerge_prj_cga_TOOLSTST_3 31/05/00 16:05 <DIR> . 31/05/00 16:05 <DIR> .. 31/05/00 16:05 0 admin.0.cpp 31/05/00 16:05 20 admin.1.cpp 31/05/00 16:05 20 admin.2.cpp |
The temporary directory is not destroyed when the adl_solve_merge command ends. There are several reasons for that:
[Top]
The tool that is used for merging files' contents is specified in the working environment by the ADL_MERGER variable. Another tool can be used in place of the default one by setting this variable with the path to this other tool. In addition the new tool must accept the arguments in the order they are passed to the default merger, here they are:
Position(s) on the command line |
Value | Usage |
1 | -adl | always present but can be ignored |
2 | <file name> | path to the common ancestor file |
3 | <file name> | path to the first version to be merged |
4 | <file name> | path to the second version to be merged |
5 | <file name> | path to the file that will be considered as the result file |
6 | <file name> |
the merging error code must be written in this file (0 if OK) |
7, 8, 9, 10, 11 | -batch -begcom debug -endcom fin | these arguments are optional and appear only when the adl_solve_merge command has been called in "automatic" mode (see adl_set_merge [6] command) |
12 , 13 | -info <file name> | optional and can be ignored |
14 | -norecom | optional and indicates that no common ancestor has been found. In this case the second parameter has been set with the path to the first version to be merged |
[Top]
adl_set_ws is used for setting merge behavior of a given workspace.
adl_ls_merge shows the merges.
adl_solve_merge provides a menu for solving merges. File's contents can be
merged either by using a graphic tool or by projecting the different versions,
merging them using any tool and giving the result file to the adl_solve_merge
command.
[Top]
Version: 4 [Nov 2003] | Add a paragraph about implicit and automatic merge |
Version: 3 [Jul 2003] | Add a sample for creation of the same file path |
Version: 2 [May 2001] | Advice for canceling a merge |
Version: 1 [May 2000] | Document creation |
[Top] |
Copyright © 2000, Dassault Systèmes. All rights reserved.