admin: New routes to expose git repositories status

This commit is contained in:
nemunaire 2023-10-23 12:03:40 +02:00
parent 598b34eb4f
commit b08039c997
8 changed files with 265 additions and 0 deletions

45
admin/api/repositories.go Normal file
View File

@ -0,0 +1,45 @@
package api
import (
"net/http"
"strings"
"srs.epita.fr/fic-server/admin/sync"
"github.com/gin-gonic/gin"
)
func declareRepositoriesRoutes(router *gin.RouterGroup) {
if gi, ok := sync.GlobalImporter.(sync.GitImporter); ok {
router.GET("/repositories", func(c *gin.Context) {
mod, err := gi.GetSubmodules()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"repositories": mod})
})
router.GET("/repositories/*repopath", func(c *gin.Context) {
repopath := strings.TrimPrefix(c.Param("repopath"), "/")
mod, err := gi.GetSubmodule(repopath)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, mod)
})
router.POST("/repositories/*repopath", func(c *gin.Context) {
repopath := strings.TrimPrefix(c.Param("repopath"), "/")
mod, err := gi.IsRepositoryUptodate(repopath)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, mod)
})
}
}

View File

@ -18,6 +18,7 @@ func DeclareRoutes(router *gin.RouterGroup) {
declarePasswordRoutes(apiRoutes)
declarePublicRoutes(apiRoutes)
declareQARoutes(apiRoutes)
declareRepositoriesRoutes(apiRoutes)
declareTeamsRoutes(apiRoutes)
declareThemesRoutes(apiRoutes)
declareSettingsRoutes(apiRoutes)

View File

@ -68,6 +68,9 @@ func declareStaticRoutes(router *gin.RouterGroup, cfg *settings.Settings, baseUR
router.GET("/pki/*_", func(c *gin.Context) {
serveIndex(c)
})
router.GET("/repositories", func(c *gin.Context) {
serveIndex(c)
})
router.GET("/settings", func(c *gin.Context) {
serveIndex(c)
})

View File

@ -13,6 +13,10 @@ angular.module("FICApp", ["ngRoute", "ngResource", "ngSanitize"])
controller: "ExerciceController",
templateUrl: "views/exercice.html"
})
.when("/repositories", {
controller: "RepositoriesController",
templateUrl: "views/repositories.html"
})
.when("/sync", {
controller: "SyncController",
templateUrl: "views/sync.html"
@ -719,6 +723,12 @@ angular.module("FICApp")
};
})
.controller("RepositoriesController", function($scope, $http) {
$http.get("api/repositories").then(function(response) {
$scope.repositories = response.data.repositories;
});
})
.controller("SyncController", function($scope, $rootScope, ROSettings, $location, $http, $interval) {
$scope.displayDangerousActions = false;
$scope.configro = ROSettings.get();

View File

@ -0,0 +1,25 @@
<div class="card mt-3 mb-5">
<div class="card-header bg-secondary text-light">
<h3 class="mb-0">
Repositories
</h3>
</div>
<table class="table table-hover table-striped mb-0">
<thead>
<tr>
<th>Chemin</th>
<th>Branche</th>
<th>Commit</th>
<th>Plus récent</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="repository in repositories">
<td>{{ repository.path }}</td>
<td>{{ repository.branch }}</td>
<td>{{ repository.hash }}</td>
<td></td>
</tr>
</tbody>
</table>
</div>

View File

@ -8,6 +8,7 @@ import (
"log"
"os"
"path"
"strings"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
@ -131,3 +132,83 @@ func (i GitImporter) Sync() error {
})
return err
}
func (i GitImporter) GetSubmodules() ([]GitSubmoduleStatus, error) {
oneGitPull.Lock()
defer oneGitPull.Unlock()
r, err := git.PlainOpen(i.li.Base)
if err != nil {
return nil, err
}
w, err := r.Worktree()
if err != nil {
return nil, err
}
modules, err := w.Submodules()
var ret []GitSubmoduleStatus
for _, mod := range modules {
st, err := mod.Status()
if err == nil {
ret = append(ret, GitSubmoduleStatus{
Hash: st.Expected.String(),
Path: st.Path,
Branch: strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(st.Branch.String(), "("), "refs/"), "heads/"), ")"),
})
}
}
return ret, err
}
func (i GitImporter) GetSubmodule(repopath string) (*GitSubmoduleStatus, error) {
oneGitPull.Lock()
defer oneGitPull.Unlock()
r, err := git.PlainOpen(path.Join(i.li.Base, repopath))
if err != nil {
return nil, err
}
st, err := r.Head()
if err != nil {
return nil, err
}
return &GitSubmoduleStatus{
Hash: st.Hash().String(),
Path: repopath,
Branch: st.Name().Short(),
}, nil
}
func (i GitImporter) IsRepositoryUptodate(repopath string) (*GitSubmoduleStatus, error) {
oneGitPull.Lock()
defer oneGitPull.Unlock()
r, err := git.PlainOpen(path.Join(i.li.Base, repopath))
if err != nil {
return nil, err
}
// Perform a git pull --rebase origin/master
err = r.Fetch(&git.FetchOptions{
RemoteName: "origin",
RefSpecs: []config.RefSpec{config.RefSpec("+refs/heads/" + i.Branch + ":refs/remotes/origin/" + i.Branch)},
Auth: i.Auth,
})
st, err := r.Reference(plumbing.ReferenceName("origin/"+i.Branch), true)
if err != nil {
return nil, err
}
return &GitSubmoduleStatus{
Hash: st.Hash().String(),
Path: repopath,
Branch: st.Name().Short(),
}, nil
}

View File

@ -64,3 +64,10 @@ func getForgeBaseLink(remote string) (u *url.URL, err error) {
u.Host = res[1]
return
}
type GitSubmoduleStatus struct {
Hash string `json:"hash"`
Text string `json:"text,omitempty"`
Path string `json:"path"`
Branch string `json:"branch"`
}

View File

@ -245,3 +245,96 @@ func (i GitImporter) GetExerciceLink(e *fic.Exercice) (u *url.URL, err error) {
return
}
func (i GitImporter) GetSubmodules() ([]GitSubmoduleStatus, error) {
oneGitPull.Lock()
defer oneGitPull.Unlock()
cmdsubmodule := exec.Command("git", "-C", i.li.Base, "submodule", "status")
stdout, err := cmdsubmodule.CombinedOutput()
if err != nil {
log.Printf("Git repository submodule failed: %s\n%s", err, stdout)
return nil, fmt.Errorf("%w:\n%s", err, stdout)
}
var ret []GitSubmoduleStatus
for _, line := range strings.Split(string(stdout), "\n") {
flds := strings.Fields(line)
if len(flds) == 3 {
ret = append(ret, GitSubmoduleStatus{
Hash: flds[0],
Path: flds[1],
Branch: strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(flds[2], "("), "refs/"), "remotes/"), "heads/"), "origin/"), ")"),
})
}
}
return ret, err
}
func (i GitImporter) GetSubmodule(repopath string) (*GitSubmoduleStatus, error) {
oneGitPull.Lock()
defer oneGitPull.Unlock()
if repopath == "" {
cmdsubmodule := exec.Command("git", "-C", i.li.Base, "show", "-q", "--oneline")
stdout, err := cmdsubmodule.CombinedOutput()
if err != nil {
log.Printf("Git repository show failed: %s\n%s", err, stdout)
return nil, fmt.Errorf("%w:\n%s", err, stdout)
}
flds := strings.SplitN(string(stdout), " ", 2)
return &GitSubmoduleStatus{
Hash: flds[0],
Text: strings.TrimSpace(flds[1]),
Path: "",
Branch: i.Branch,
}, nil
} else {
cmdsubmodule := exec.Command("git", "-C", i.li.Base, "submodule", "status", repopath)
stdout, err := cmdsubmodule.CombinedOutput()
if err != nil {
log.Printf("Git repository submodule failed: %s\n%s", err, stdout)
return nil, fmt.Errorf("%w:\n%s", err, stdout)
}
flds := strings.Fields(strings.TrimSpace(string(stdout)))
if len(flds) == 3 {
return &GitSubmoduleStatus{
Hash: flds[0],
Path: flds[1],
Branch: strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(flds[2], "("), "refs/"), "remotes/"), "heads/"), "origin/"), ")"),
}, nil
}
}
return nil, fmt.Errorf("Unable to parse")
}
func (i GitImporter) IsRepositoryUptodate(repopath string) (*GitSubmoduleStatus, error) {
oneGitPull.Lock()
defer oneGitPull.Unlock()
cmdsubmodule := exec.Command("git", "-C", path.Join(i.li.Base, repopath), "fetch", "origin", i.Branch)
stdout, err := cmdsubmodule.CombinedOutput()
if err != nil {
log.Printf("Git repository submodule fetch failed: %s\n%s", err, stdout)
return nil, fmt.Errorf("%w:\n%s", err, stdout)
}
cmdsubmodule = exec.Command("git", "-C", path.Join(i.li.Base, repopath), "show", "-q", "--oneline", "origin/"+i.Branch)
stdout, err = cmdsubmodule.CombinedOutput()
if err != nil {
log.Printf("Git repository submodule status failed: %s\n%s", err, stdout)
return nil, fmt.Errorf("%w:\n%s", err, stdout)
}
flds := strings.SplitN(string(stdout), " ", 2)
return &GitSubmoduleStatus{
Hash: flds[0],
Text: strings.TrimSpace(flds[1]),
Path: repopath,
Branch: i.Branch,
}, nil
}