Lepiter Iceberg Integration Model

This pages provides details about how Lepiter is integrated with Iceberg.

Basic Model

We start from an Iceberg repository.

icebergRepository := IceRepository registry 
  detect: [ :each | each name = 'test-repo-1' ].

The iceberg repository gets wrapped in a dedicated repository that is Lepiter-aware.

gtIcebergRepository := GtGitRepository 
	fromIcebergRepository: icebergRepository.

The way the repository is aware of Lepiter is using a dedicated working copy, which is an instance of GtLepiterWorkingCopy Object subclass: #GtLepiterWorkingCopy instanceVariableNames: 'databases icebergRepository lepiterChangesStragegy' classVariableNames: '' package: 'GToolkit4Git-Lepiter' .

lepiterWorkingCopy := gtIcebergRepository lepiterWorkingCopy.

This working copy wraps a IceRepository Object subclass: #IceRepository instanceVariableNames: 'name workingCopy index commitsInPackageCache' classVariableNames: 'Registry RepositoryClass' package: 'Iceberg-Core' and its IceWorkingCopy Object subclass: #IceWorkingCopy instanceVariableNames: 'repository packages referenceCommit shouldIgnoreNotifications project properties' classVariableNames: '' package: 'Iceberg-Core' and adds extra functionality to work with Lepiter. For example, it knows how to return the list of Lepiter databases from the repository currently registered with the default instance of LeDatabasesRegistry LeBasicDatabasesRegistry subclass: #LeDatabasesRegistry uses: TGtUniqueInstance instanceVariableNames: '' classVariableNames: '' package: 'Lepiter-Core-Registry'

lepiterWorkingCopy databases 

Creating diffs

We can directly as the working copy to do a diff with the reference commit. This diff contains both changes to Lepiter files from disk, and code changes done inside the image.

lepiterWorkingCopy diffToReferenceCommit.

We can also create the diff step by step. First we can define the source and the target commitish.

sourceCommitish := lepiterWorkingCopy.
targetCommitish := lepiterWorkingCopy referenceCommit 

The first step is to detect what types of changes are in the repo. For Lepiter we can have a GtLepiterLocalDatabaseChange GtLepiterLocalChange subclass: #GtLepiterLocalDatabaseChange instanceVariableNames: 'database propertiesFileChange pageChanges attachmentChanges linksChange' classVariableNames: '' package: 'GToolkit4Git-Lepiter' that indicates that a change was done in that database.

changes := sourceCommitish changesTo: targetCommitish.

This computes both code changes and Lepiter changes.

 sourceCommitish lepiterChangesTo: targetCommitish.
 sourceCommitish codeChangesTo: targetCommitish.

These changes are passed in IceDiff>>#buildForChanges: buildForChanges: aCollection | leftTree rightTree | leftTree := IceNode value: IceRootDefinition new. rightTree := IceNode value: IceRootDefinition new. aCollection do: [ :change | change accept: (IceChangeImporter new version: source; diff: self; parentNode: leftTree; yourself). change accept: (IceChangeImporter new version: target; diff: self; parentNode: rightTree; yourself) ] "displayingProgress: [ :change | change displayingProgressString ]". mergedTree := self mergedTreeOf: leftTree with: rightTree. tree := mergedTree select: [ :operation | operation hasChanges ]. to a IceChangeImporter Object subclass: #IceChangeImporter instanceVariableNames: 'parentNode diff version' classVariableNames: '' package: 'Iceberg-Changes' visitor.

diff := IceDiff from: sourceCommitish to: targetCommitish.

Steps for performing the diff

diff := IceDiff new
	sourceVersion: sourceCommitish;
	targetVersion: targetCommitish;
leftTree := IceNode value: IceRootDefinition new.
changes do: [ :aChange | 
	aChange accept: (IceChangeImporter new
		version: sourceCommitish;
		diff: diff;
		parentNode: leftTree;
		yourself) ].
rightTree := IceNode value: IceRootDefinition new.
changes do: [ :change | 
	change accept: (IceChangeImporter new
		version: targetCommitish;
		diff: diff;
		parentNode: rightTree;
		yourself) ].
mergedTree := diff mergedTreeOf: leftTree with: rightTree.
tree := mergedTree select: [ :operation | operation hasChanges ].
diff instVarNamed: #tree put: tree.
diff instVarNamed: #mergedTree put: mergedTree.


icebergRepository index
icebergRepository index updateDiskWorkingCopy: diff
icebergRepository index updateIndex: diff
icebergRepository index addToGitIndex.
newCommit := icebergRepository
	commitIndexWithMessage: 'Example commit' 
	andParents: (workingCopy workingCopyState 
		referenceCommits reject: [ :each | each isNoCommit ]).

Swiching branches

targetBranch := icebergRepository branchNamed: 'master'.

targetBranch checkout: ((GtGitIceCheckoutAlreadyLoadedContent new
	lepiterWorkingCopy: lepiterWorkingCopy)
		committish: targetBranch;


Merging follows the same steps as in Iceberg.

otherBranch := icebergRepository branchNamed: 'main'.
mergeAction := GtGitIceMerge new
	repository: gtIcebergRepository;
	mergeCommit: otherBranch commit;
mergeAction calculateChanges
mergeTree := mergeAction instVarNamed:  #mergeTree
gtIcebergRepository lepiterWorkingCopy 
	loadLepiterChangesInWorkingCopyFrom: mergeTree

At this step the merge tree contains changes for all files between the two branches, including code and lepiter files. At this moment this tree is not Lepiter aware. So changes to Lepiter pages appear just as changes to files.

Step-by-step merge

leftCommit := icebergRepository headCommit.
mergeCommit := otherBranch commit.
commonAncestor := leftCommit commonAncestorWith: mergeCommit.

Next we create diffs with the common ancestor.

sourceDiff := mergeCommit gtDiffTo: commonAncestor.

When creating this diff we compute the set of changes between two commits. These are going to be a list of IceChange Object subclass: #IceChange instanceVariableNames: '' classVariableNames: '' package: 'Iceberg-Changes'

mergeCommit changesTo: commonAncestor
targetDiff := leftCommit diffTo: commonAncestor.