FileBasedSourceCodeManager.st
author Claus Gittinger <cg@exept.de>
Thu, 05 Mar 2020 11:17:28 +0100
changeset 4561 eace75531554
parent 4541 b08f22706921
permissions -rw-r--r--
#UI_ENHANCEMENT by cg class: SourceCodeManagerUtilities changed: #compareClassWithRepository:askForRevision: typos: genitive of class is class's - not classes.

"
 COPYRIGHT (c) 2011 by Claus Gittinger
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
"{ Package: 'stx:libbasic3' }"

"{ NameSpace: Smalltalk }"

AbstractSourceCodeManager subclass:#FileBasedSourceCodeManager
	instanceVariableNames:''
	classVariableNames:'ModulePathes RepositoryPath Verbose'
	poolDictionaries:''
	category:'System-SourceCodeManagement'
!

!FileBasedSourceCodeManager class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 2011 by Claus Gittinger
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    A simple file based sourceCodeManager, which saves versions in a local directory.
    Versions will be stored as filename.st.vNr (i.e. Foo.st.1, Foo.st.2, etc.)

    This is more an example of the protocol which needs to be implemented,
    than a real manager (although it may be useful for tiny private projects or classroom examples)

    [author:]
        Claus Gittinger
"
! !

!FileBasedSourceCodeManager class methodsFor:'accessing'!

getRepositoryPathForModule:aModuleName
    "internal: used when accessing a source repository.
     Return the path to the top directory for a particular module.
     If no specific path was defined for that module, return the value of
     the global (fallBack) repositoryPath.
     Nil is returned if no repository is available."

    ModulePathes isNil ifTrue:[^ RepositoryPath].
    aModuleName isNil ifTrue:[^ RepositoryPath].
    ^ ModulePathes at:aModuleName ifAbsent:RepositoryPath.

    "Modified: / 20-05-1998 / 16:30:12 / cg"
    "Created: / 21-12-2011 / 23:05:51 / cg"
!

knownModules
    "return the modules, we currently know"

    ModulePathes isEmptyOrNil ifTrue:[^ #() ].
    ^ ModulePathes keys

    "Modified: / 26-12-2011 / 00:49:10 / cg"
!

repositoryInfoPerModule
    "return the dictionary, which associates repository paths to module names.
     If no entry is contained in this dictionary for some module,
     the default path will be used."

    ^ ModulePathes ? #()

    "Created: / 22-12-2011 / 00:35:32 / cg"
!

repositoryInfoPerModule:aDictionary
    "set the dictionary, which associates repository paths to module names.
     If no entry is contained in this dictionary for some module,
     the default path will be used."

    ModulePathes := aDictionary

    "Created: / 22-12-2011 / 00:34:52 / cg"
!

repositoryPath
    "return the path of the default repository"

    ^ RepositoryPath

    "Created: / 21-12-2011 / 14:55:12 / cg"
!

repositoryPath:aPath
    "set the path of the default repository"

    RepositoryPath := aPath

    "Created: / 22-12-2011 / 00:30:19 / cg"
! !

!FileBasedSourceCodeManager class methodsFor:'queries'!

defaultRepositoryPath
    ^ './repository'

    "Created: / 21-12-2011 / 23:59:52 / cg"
!

enabled
    ^ true "/ false.

    "Created: / 21-12-2011 / 17:53:34 / cg"
!

isContainerBased
    "true, if the SCM uses some kind of source container (,v files).
     False, if it is like a database or filesystem."

    ^ false

    "Created: / 21-12-2011 / 18:53:55 / cg"
!

isResponsibleForPackage:aString
    ^ true.

    "Created: / 09-07-2011 / 14:32:20 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (comment): / 22-12-2011 / 00:05:54 / cg"
!

managerTypeName
    ^ 'FileRepository'

    "Created: / 16-08-2006 / 11:05:56 / cg"
!

nameOfVersionMethodForExtensions
    ^ #'extensionsVersion_FileRepository'

    "Modified: / 21-12-2011 / 13:02:45 / cg"
!

nameOfVersionMethodInClasses
    ^ #'version_FileRepository'

    "Modified: / 21-12-2011 / 13:02:52 / cg"
!

repositoryNameForPackage:packageId 
    "superclass AbstractSourceCodeManager class says that I am responsible to implement this method"

    ^ self getRepositoryPathForModule:(packageId upTo:$: )

    "Created: / 21-12-2011 / 23:07:02 / cg"
!

settingsApplicationClass
    "link to my settings application (needed for the settings dialog"

    ^ FileBasedSourceCodeManagementSettingsAppl

    "Created: / 19-04-2011 / 12:43:29 / cg"
    "Modified: / 21-12-2011 / 13:02:58 / cg"
!

supportsCheckinLogMessages
    "no, log-messages are not supported (I am too stupid)"

    ^ false

    "Created: / 21-12-2011 / 18:02:42 / cg"
! !

!FileBasedSourceCodeManager class methodsFor:'saving'!

savePreferencesOn:aStream
    aStream nextPutLine:'FileBasedSourceCodeManager notNil ifTrue:['.
    self repositoryInfoPerModule notEmptyOrNil ifTrue:[
        aStream nextPutLine:'    FileBasedSourceCodeManager repositoryInfoPerModule:' , self repositoryInfoPerModule storeString , '.'.
    ].
    (Smalltalk at:#SourceCodeManager) == self ifTrue:[
        aStream nextPutLine:'    Smalltalk at:#SourceCodeManager put:FileBasedSourceCodeManager.'.
    ].
    aStream nextPutLine:'    FileBasedSourceCodeManager repositoryPath:' , self repositoryPath storeString , '.'.
    aStream nextPutLine:'].'.

    "Created: / 09-11-2006 / 15:09:25 / cg"
    "Modified: / 22-12-2011 / 00:46:29 / cg"
! !

!FileBasedSourceCodeManager class methodsFor:'source code administration'!

checkForExistingContainer:fileName inModule:moduleName directory:packageDirName
    "check for a container to exist. Return a boolean result."

    ^ self checkForExistingModule:moduleName directory:packageDirName

    "Created: / 21-12-2011 / 17:56:23 / cg"
    "Modified (format): / 24-02-2017 / 11:32:26 / cg"
!

checkForExistingModule:moduleDir
    "check for a package directory to be present"

    |dir|

    dir := self moduleDirectoryFor:moduleDir.
    ^ dir exists

    "Created: / 21-12-2011 / 18:37:28 / cg"
!

checkForExistingModule:moduleDir directory:packageDir
    "check for a package directory to be present"

    |dir|

    dir := self packageDirectoryForModule:moduleDir package:packageDir.
    ^ dir exists

    "Created: / 21-12-2011 / 18:03:33 / cg"
!

checkinClass:aClass fileName:classFileName directory:packageDir module:moduleDir source:sourceFile logMessage:logMessage force:force
    "Return true if ok, false if not."

    |targetDir newestRevision newRevision newFile packageMode filter outStream logFile|

    targetDir := self packageDirectoryForModule:moduleDir package:packageDir.
    targetDir exists ifFalse:[
        targetDir recursiveMakeDirectory
    ].
    (targetDir filesMatching:(classFileName,'_*')) do:[:eachVersionFile |
        |versionString|

        versionString := eachVersionFile copyFrom:(classFileName size + 2).
        (newestRevision isNil 
        or:[ self isRevision:versionString after:newestRevision ]) ifTrue:[
            newestRevision := versionString
        ].
    ].

    newestRevision isNil ifTrue:[
        newRevision := '1'
    ] ifFalse:[
        newRevision := self revisionAfter:newestRevision
    ].
    newFile := targetDir construct:(classFileName,'_',newRevision printString).
    logFile := targetDir construct:(classFileName,'_',newRevision printString,'.log').

    self updateVersionMethodOf:aClass for:(self revisionStringFor:aClass inModule:moduleDir directory:packageDir container:classFileName revision:newRevision).

    packageMode := self checkMethodPackagesOf:aClass.
    packageMode == #base ifTrue:[
        filter := [:mthd | mthd package == aClass package].
    ].

    [
        outStream := newFile writeStream.
    ] on:OpenError do:[:ex|
        self reportError:('fileout failed').
        ^ false
    ].

    Method flushSourceStreamCache.
    Class fileOutErrorSignal handle:[:ex |
        outStream close.
        newFile remove.
        self reportError:('fileout failed (',ex description,')').
        ^ false
    ] do:[
        self 
            fileOutSourceCodeOf:aClass 
            on:outStream 
            withTimeStamp:false 
            withInitialize:true 
            withDefinition:true
            methodFilter:filter.
    ].
    outStream close.

    newFile exists ifFalse:[
        self reportError:'fileout failed'.
        ^ false.
    ].

    logMessage isEmptyOrNil ifTrue:[
        logFile contents:'no log message'.
    ] ifFalse:[
        logFile contents:logMessage.
    ].
    ^ true

    "Created: / 21-12-2011 / 19:01:07 / cg"
!

createModule:moduleDir
    "create a module directory"

    |dir|

    dir := self moduleDirectoryFor:moduleDir.
    dir recursiveMakeDirectory.
    ^ dir exists.

    "Created: / 21-12-2011 / 18:38:22 / cg"
!

createModule:moduleDir directory:packageDir
    "create a package directory"

    |dir|

    dir := self packageDirectoryForModule:moduleDir package:packageDir.
    dir recursiveMakeDirectory.
    ^ dir exists.

    "Created: / 21-12-2011 / 18:44:20 / cg"
!

initialRevisionStringFor:aClass inModule:moduleDir directory:packageDir container:fileName
    "return a string usable as initial revision string"

    ^ self 
        revisionStringFor:aClass 
        inModule:moduleDir 
        directory:packageDir 
        container:fileName 
        revision:'1'

    "Created: / 21-12-2011 / 18:14:03 / cg"
!

moduleDirectoryFor:moduleDir
    "a modules directory as filename"

    |root|

    root := self getRepositoryPathForModule:moduleDir.
    root isNil ifTrue:[
        RepositoryPath := root := './versions'.
    ].

    ^ (root asFilename construct:moduleDir)

    "Created: / 21-12-2011 / 18:38:38 / cg"
!

packageDirectoryForModule:moduleDir package:package
    "a packages directory as filename"

    |dir|

    dir := self moduleDirectoryFor:moduleDir.
    ^ dir construct:package

    "Created: / 21-12-2011 / 18:43:27 / cg"
!

revisionInfoFromString:aString 
    "{ Pragma: +optSpace }"

    "return a VersionInfo object filled with revision info.
     This extracts the relevant info from aString."

    ^ self revisionInfoFromStandardVersionString:aString

    "
     self revisionInfoFromString:'Path: stx/libbasic/Array.st, Version: 123, User: cg, Time: 2011-12-21T21:03:08.826' 
    "

    "Created: / 21-12-2011 / 14:50:12 / cg"
!

revisionLogOf:clsOrNil 
        fromRevision:rev1OrNil toRevision:rev2OrNil numberOfRevisions:limitOrNil 
        fileName:classFileName directory:packageDir module:moduleDir 

    |info log targetDir count newestRevision|

    targetDir := self packageDirectoryForModule:moduleDir package:packageDir.
    targetDir exists ifFalse:[^ nil ].

    info := IdentityDictionary new.
    log := OrderedCollection new.
    count := 0.

    (targetDir filesMatching:(classFileName,'_*')) do:[:eachVersionFile |
        |versionString cs versionChange info|

        versionString := eachVersionFile copyFrom:(classFileName size + 2).
        count := count + 1.
        (newestRevision isNil 
        or:[ self isRevision:versionString after:newestRevision ]) ifTrue:[
            newestRevision := versionString
        ].

        (rev1OrNil isNil 
            or:[ rev1OrNil = 0
            or:[ versionString = rev1OrNil
            or:[ self isRevision:versionString after:rev1OrNil ]]])
        ifTrue:[
            (rev2OrNil isNil 
                or:[ rev2OrNil = 0
                or:[ versionString = rev2OrNil
                or:[ self isRevision:rev2OrNil after:versionString ]]])
            ifTrue:[
                (limitOrNil isNil
                or:[ log size < limitOrNil ])
                ifTrue:[
                    cs := ChangeSet fromFile:(targetDir construct:eachVersionFile).
                    versionChange := cs detect:[:chg | chg isMethodChange
                                                       and:[chg selector = self nameOfVersionMethodInClasses]]
                                        ifNone:nil.
                    versionChange notNil ifTrue:[
                        info := self revisionInfoFromString:versionChange source.
                    ] ifFalse:[
                        info := self versionInfoClass new.
                    ].

                    info revision:versionString.
                    log add:info.
                ]
            ].
        ].
    ].
    log sort:[:a :b | self isRevision:b revision after:a revision].

    info at:#revisions put:log.
    info at:#numberOfRevisions put:count.
    info at:#newestRevision put:newestRevision.

    ^ info

    "Created: / 21-12-2011 / 20:39:31 / cg"
!

streamForClass:aClass fileName:classFileName revision:revision directory:packageDir module:moduleDir cache:doCache
    |targetDir oldFile|

    targetDir := self packageDirectoryForModule:moduleDir package:packageDir.
    oldFile := (targetDir construct:classFileName,'_',revision).
    ^ oldFile readStream

    "Created: / 21-12-2011 / 20:49:01 / cg"
! !

!FileBasedSourceCodeManager class methodsFor:'documentation'!

version
    ^ '$Header$'
!

version_CVS
    ^ '$Header$'
! !