git/GitRepository.st
author Jan Vrany <jan.vrany@labware.com>
Thu, 28 Jul 2022 06:56:07 +0100
changeset 943 442fe77c421f
parent 481 0cfef855baa2
permissions -rw-r--r--
Merge

"{ Package: 'stx:libscm/git' }"

GitLibraryObject subclass:#GitRepository
	instanceVariableNames:'path workdir remotes index'
	classVariableNames:''
	poolDictionaries:'GitObjectType GitStatusCodes'
	category:'SCM-Git-Core'
!


!GitRepository class methodsFor:'instance creation'!

clone: url to: stringOrDirectory
    | dir ref fetchStats checkoutStats options |

    (url asString startsWith: 'file://') ifTrue:[
        "Arghhh...local transport not yet supported by libgit2, use command line..."
        GitCommand clone: url to: stringOrDirectory pathName.
        ^self open: stringOrDirectory pathName
    ].


    dir := stringOrDirectory asFilename.
    dir exists ifTrue:[
        GitError raiseErrorString:'Destination directory already exists'.
        ^self
    ].
    dir directory isDirectory ifFalse:[
        GitError raiseErrorString:'Parent directory for destination does not exists'.
        ^self
    ].

    ref := ByteArray new: ExternalBytes sizeofPointer.
    fetchStats := GitIndexerStatsStructure new.
    checkoutStats := GitIndexerStatsStructure new.
    options := GitCheckoutOptions new.
    options strategyCreateMissing.
    GitError raiseIfError: 
        (GitPrimitives prim_git_clone: ref origin_url: url asString workdir_path: dir pathName fetch_stats: fetchStats checkout_stats: checkoutStats checkout_opts: options).
    ^self new 
        setHandleFromRef:ref;
        setPath: dir pathName;
        yourself

    "Created: / 01-10-2012 / 00:09:00 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

discover: path
    "Find a git repository in given directory or super-directories.
     If not repository is found, return nil.

     Currently, it searches for presence of .git
     directory"

    | d |

    path isNil ifTrue:[ ^ nil ].
    d := path asFilename.
    [ d notNil ] whileTrue:[
        (d / '.git') exists ifTrue:[ ^ d ].
        d := d isRootDirectory 
            ifTrue:[nil]
            ifFalse:[d := d directory].
    ].
    ^nil

    "Created: / 06-10-2012 / 19:35:49 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

open: aStringOrFilename
    "Opens an existing git repository on given path"

    | ref |

    ref := ByteArray new: ExternalBytes sizeofPointer.
    GitError raiseIfError: (GitPrimitives prim_git_repository_open: ref path: aStringOrFilename asString).
    ^self new 
        setHandleFromRef:ref;
        setPath: aStringOrFilename;
        yourself

    "Created: / 07-09-2012 / 23:45:19 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GitRepository class methodsFor:'accessing'!

structSize
    "Returns size of undelaying structure in bytes"

    ^0
! !

!GitRepository methodsFor:'accessing'!

head
    | ref err |
    ref := ByteArray new: ExternalBytes sizeofPointer.
    err := GitPrimitives prim_git_repository_head: ref repo: handle.
    GitError raiseIfError: err.

    ^GitReference new setHandleFromRef: ref

    "Created: / 25-09-2012 / 10:48:58 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

index
    "Return repository working index"

    index isNil ifTrue:[
        | ref err |

        ref := ByteArray new: ExternalBytes sizeofPointer.
        err := GitPrimitives prim_git_repository_index: ref repo: handle.
               GitError raiseIfError: err.
        index := GitIndex new
                setHandleFromRef: ref;
                setRepository: self;
                yourself
    ].
    ^index

    "Created: / 02-10-2012 / 15:33:15 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

index: aGitIndex
    "Sets repository working index to given index"

    self assert: (aGitIndex isKindOf: GitIndex).
    index := nil.
    GitPrimitives prim_git_repository_set_index: handle index: aGitIndex getHandle.
    aGitIndex setRepository: self.
    index := aGitIndex

    "Created: / 02-10-2012 / 15:33:49 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

path
    ^ path
!

remotes

    remotes isNil ifTrue:[
        | list  |

        list := GitStringArray new.
        GitError raiseIfError:(GitPrimitives prim_git_remote_list: list repo: handle).
        remotes := Dictionary new.
        list do:[:name|
            remotes at: name put: (self remoteNamed: name)
        ].
    ].
    ^remotes.

    "Created: / 30-09-2012 / 20:15:03 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

workingCopy
    ^self workingCopyOn: self path

    "Created: / 19-09-2012 / 15:32:19 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GitRepository methodsFor:'actions'!

commit: message tree: tree parents: parents update: refOrNil commiter: commiterOrNil author: authorOrNil
    | oid err committer author parentsA |

    committer := commiterOrNil notNil 
                    ifTrue:[committer copyNow]
                    ifFalse:[GitCommitterQuery query copyNow].
    author := commiterOrNil notNil
                    ifTrue:[author]
                    ifFalse:[committer copy].
    parentsA := ByteArray new: (ExternalBytes sizeofPointer * parents size).
    parents withIndexDo:[:each :idx|
        parentsA pointerAt: (1 + ((idx - 1) * ExternalBytes sizeofPointer)) put: each getHandle.
    ].
    oid := GitOid new.

    err := GitPrimitives prim_git_commit_create: oid 
                                           repo: handle 
                                     update_ref: (refOrNil notNil ifTrue:[refOrNil name] ifFalse:[nil]) 
                                         author: author getHandle
                                      committer: committer getHandle
                               message_encoding: 'utf-8'
                                        message: message utf8Encoded
                                           tree: tree getHandle
                                   parent_count: parents size
                                        parents: parentsA.
    GitError raiseIfError: err.

    ^self lookup: oid type: OBJ_COMMIT.

    "Created: / 25-09-2012 / 14:51:55 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

detach
    "Detach the HEAD.
     If the HEAD is already detached and points to a Commit, 0 is returned.

     If the HEAD is already detached and points to a Tag, the HEAD is
     updated into making it point to the peeled Commit, and 0 is returned.

     If the HEAD is already detached and points to a non commitish, the HEAD is 
     unaletered, and -1 is returned.

     Otherwise, the HEAD will be detached and point to the peeled Commit.
    "

    GitError raiseIfError: (GitPrimitives prim_git_repository_detach_head: handle).

    "Created: / 02-10-2012 / 17:07:24 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

push: aGitRemote
    "pushes all changes to given remote repository"

    GitCommand push
        workingDirectory: self path;
        remote: aGitRemote name;
        execute

    "Created: / 30-09-2012 / 23:46:01 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GitRepository methodsFor:'copying'!

cloneTo: aStringOrFilename"^<GitRepository>"

    "Clones the receiver to given directory. And equivalent to

        git clone <path> <clonePath>

    "

    ^GitRepository 
        clone: ('file://', self path ) 
           to: (aStringOrFilename)

    "Created: / 30-09-2012 / 19:07:03 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

shallowCopy

    ^self class open: path pathName

    "Created: / 10-09-2012 / 19:04:41 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GitRepository methodsFor:'initialization'!

setPath: aStringOrFilename
    path := aStringOrFilename asFilename

    "Created: / 10-09-2012 / 19:05:27 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GitRepository methodsFor:'initialization & release'!

free
    handle notNil ifTrue:[
        GitPrimitives prim_git_object_free: handle. 
        handle := nil.
    ].

    "Created: / 17-09-2012 / 21:16:44 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GitRepository methodsFor:'lookup'!

lookup: oid
    "Lookup an object with given OID"
    ^self lookup: oid type: OBJ_ANY

    "Created: / 10-09-2012 / 10:53:13 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

lookup: oid type: typeId
    "Lookup an object with given OID"

    | ref err type obj |

    oid class == GitOid ifFalse:[
        self error: 'Passed oid is not a GitOid'.
        ^nil.
    ].
    ref := ByteArray new: ExternalBytes sizeofPointer.
    err := GitPrimitives prim_git_object_lookup: ref repo: handle id: oid type: typeId.
    GitError raiseIfError: err.

    typeId == OBJ_ANY ifTrue:[
        obj := ExternalAddress new setAddressFromBytes:ref.
        type := GitPrimitives prim_git_object_type: obj.
    ] ifFalse:[
        type := typeId.
    ].
    obj := GitObject newForType: type.
    ^obj
        setHandleFromRef: ref;
        setOid: oid;
        setRepository: self;
        yourself

    "Created: / 10-09-2012 / 11:01:25 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GitRepository methodsFor:'private'!

checkout: object
    | err stats options |
    object isGitTreeish ifFalse:[
        GitError raiseErrorString:'Invalid argument: ', object printString.
        ^self.
    ].

    stats := GitIndexerStatsStructure new.
    options := GitCheckoutOptions new.
    options strategyCreateMissing.
    err := GitPrimitives prim_git_checkout_tree: handle treeish: object getHandle opts: options stats: stats. 
    GitError raiseIfError: err.

    "Created: / 19-09-2012 / 09:52:32 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GitRepository methodsFor:'private-accessing'!

getHandleClass
    ^GitRepositoryHandle

    "Created: / 17-09-2012 / 21:20:59 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

getIndex
    ^index

    "Created: / 02-10-2012 / 15:40:44 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

remoteNamed: name
    | ref err |

    ref := ByteArray new: ExternalBytes sizeofPointer.
    err := GitPrimitives prim_git_remote_load: ref repo: handle  name: name.
    GitError raiseIfError: err.
    ^GitRemote new
        setHandleFromRef: ref;
        setRepository: self;
        setName: name

    "Created: / 30-09-2012 / 20:16:11 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

statusOf: aFilename
    | ref err |

    (aFilename pathName startsWith: self workdir pathName ) ifFalse:[
        GitError raiseErrorString: 'Given path is within working copy'.
        ^0.
    ].

    ref := ByteArray new: ExternalBytes sizeofInt.
    err := GitPrimitives prim_git_status_file: ref repo: handle path: (aFilename pathName copyFrom: (workdir pathName size + 2)).
    GitError raiseIfError: err.

    ^ref longAt:1

    "Created: / 24-09-2012 / 22:27:58 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

workdir
    "Get the path of the working directory for this repository or nil, if
     repository is bare"

    self isBare ifTrue: [ ^ nil ].
    workdir isNil ifTrue:[
        workdir := GitPrimitives prim_git_repository_workdir: handle.
        workdir := workdir asFilename.
    ].
    ^workdir

    "Created: / 10-09-2012 / 19:07:25 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

workdir: aStringOrFilename update: aBoolean
    "Set the path of the working directory for this repository.
     If update is true, then create/update gitlink in workdir and set 
     config 'core.worktree' (if workdir is not the parent of the 
     .git directory)"

    GitError raiseIfError: 
        (GitPrimitives prim_git_repository_set_workdir: handle 
                                               workdir: aStringOrFilename asString
                                        update_gitlink: (aBoolean ifTrue:[1] ifFalse:[0])).
    workdir := aStringOrFilename asFilename.

    "Created: / 10-09-2012 / 19:19:40 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

workingCopyOn: aStringOrFilename
    self isBare ifTrue:[
        GitError raiseErrorString: 'Bare repositories have no working copies'.
        ^nil.
    ].

    aStringOrFilename ~= path ifTrue:[
        GitError raiseErrorString: 'Off-repository working copies are not sypported (and likely never will be)'.
        ^nil
    ].

    ^GitWorkingCopy new setRepository: self

    "Created: / 19-09-2012 / 09:48:55 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GitRepository methodsFor:'queries'!

hasOffRepoWorkdir
    ^self workdir ~= self path

    "Created: / 03-10-2012 / 16:08:14 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GitRepository methodsFor:'testing'!

headIsDetached
    ^(GitPrimitives prim_git_repository_head_detached: handle) == 1

    "Created: / 25-09-2012 / 11:06:30 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

headIsOrphan
    ^(GitPrimitives prim_git_repository_head_orphan: handle) == 1

    "Created: / 25-09-2012 / 11:06:48 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

isBare
    "Check if a repository is bare"

    ^(GitPrimitives prim_git_repository_is_bare: handle) == 1

    "Created: / 10-09-2012 / 19:11:57 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

isEmpty
    "An empty repository has just been initialized and contains
     no commits."

    ^(GitPrimitives prim_git_repository_is_empty: self) == 1

    "Created: / 10-09-2012 / 19:12:24 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!GitRepository class methodsFor:'documentation'!

version_GIT
    "Never, ever change this method. Ask JV or CG why"
    ^thisContext method mclass theNonMetaclass instVarNamed: #revision
!

version_SVN
    ^ '$Id$'
! !