M api/graph/resolver.go => api/graph/resolver.go +9 -0
@@ 15,3 15,12 @@ var allowedCloneSchemes = map[string]struct{}{
"http": struct{}{},
"git": struct{}{},
}
+
+type CloneStatus string
+
+const (
+ CloneNone CloneStatus = "NONE"
+ CloneInProgress CloneStatus = "IN_PROGRESS"
+ CloneComplete CloneStatus = "COMPLETE"
+ CloneError CloneStatus = "ERROR"
+)
M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +7 -4
@@ 128,12 128,15 @@ func (r *mutationResolver) CreateRepository(ctx context.Context, name string, vi
panic(fmt.Errorf("Unknown visibility %s", visibility)) // Invariant
}
- cloneInProgress := cloneURL != nil
+ cloneStatus := CloneNone
+ if cloneURL != nil {
+ cloneStatus = CloneInProgress
+ }
row := tx.QueryRowContext(ctx, `
INSERT INTO repository (
created, updated, name, description, path, visibility, owner_id,
- clone_in_progress
+ clone_status
) VALUES (
NOW() at time zone 'utc',
NOW() at time zone 'utc',
@@ 141,7 144,7 @@ func (r *mutationResolver) CreateRepository(ctx context.Context, name string, vi
) RETURNING
id, created, updated, name, description, visibility,
path, owner_id;
- `, name, description, repoPath, dvis, user.UserID, cloneInProgress)
+ `, name, description, repoPath, dvis, user.UserID, cloneStatus)
if err := row.Scan(&repo.ID, &repo.Created, &repo.Updated, &repo.Name,
&repo.Description, &repo.RawVisibility,
&repo.Path, &repo.OwnerID); err != nil {
@@ 188,7 191,7 @@ func (r *mutationResolver) CreateRepository(ctx context.Context, name string, vi
}
}
- if cloneInProgress {
+ if cloneURL != nil {
u, err := url.Parse(*cloneURL)
if err != nil {
return valid.Errorf(ctx, "cloneUrl", "Invalid clone URL: %s", err)
M api/repos/middleware.go => api/repos/middleware.go +25 -20
@@ 33,6 33,15 @@ func Middleware(queue *work.Queue) func(next http.Handler) http.Handler {
}
}
+type CloneStatus string
+
+const (
+ CloneNone CloneStatus = "NONE"
+ CloneInProgress CloneStatus = "IN_PROGRESS"
+ CloneComplete CloneStatus = "COMPLETE"
+ CloneError CloneStatus = "ERROR"
+)
+
// Schedules a clone.
func Clone(ctx context.Context, repoID int, repo *git.Repository, cloneURL string) {
queue, ok := ctx.Value(ctxKey).(*work.Queue)
@@ 40,33 49,29 @@ func Clone(ctx context.Context, repoID int, repo *git.Repository, cloneURL strin
panic("No repos worker for this context")
}
task := work.NewTask(func(ctx context.Context) error {
- defer func() {
- recovered := recover()
- err := database.WithTx(ctx, nil, func(tx *sql.Tx) error {
- _, err := tx.Exec(
- `UPDATE repository SET clone_in_progress = false WHERE id = $1;`,
- repoID,
- )
- if err != nil {
- return err
- }
- return nil
- })
- if err != nil {
- panic(err)
- }
- if recovered != nil {
- panic(recovered)
- }
- }()
cloneCtx, cancel := context.WithTimeout(ctx, 10*time.Minute)
defer cancel()
err := repo.Clone(cloneCtx, &git.CloneOptions{
URL: cloneURL,
RecurseSubmodules: git.NoRecurseSubmodules,
})
+ cloneStatus := CloneComplete
+ var cloneError sql.NullString
if err != nil {
- // TODO: Set repo to error state. Email error to user.
+ cloneStatus = CloneError
+ cloneError.String = err.Error()
+ cloneError.Valid = true
+ }
+ if err := database.WithTx(ctx, nil, func(tx *sql.Tx) error {
+ _, err := tx.Exec(`
+ UPDATE repository
+ SET clone_status = $2, clone_error = $3
+ WHERE id = $1;`, repoID, cloneStatus, cloneError)
+ if err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
panic(err)
}
return nil
A gitsrht/alembic/versions/0a3d114e8a18_add_clone_status_to_repository.py => gitsrht/alembic/versions/0a3d114e8a18_add_clone_status_to_repository.py +49 -0
@@ 0,0 1,49 @@
+"""Add clone_status to repository
+
+Revision ID: 0a3d114e8a18
+Revises: 163a7732e6a0
+Create Date: 2022-02-15 08:25:12.564021
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '0a3d114e8a18'
+down_revision = '163a7732e6a0'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.execute("""
+ CREATE TYPE clone_status AS ENUM (
+ 'NONE',
+ 'IN_PROGRESS',
+ 'COMPLETE',
+ 'ERROR'
+ );
+
+ ALTER TABLE repository
+ ADD COLUMN clone_status clone_status NOT NULL DEFAULT 'NONE';
+ ALTER TABLE repository
+ ALTER COLUMN clone_status DROP DEFAULT;
+ ALTER TABLE repository
+ ADD COLUMN clone_error varchar
+ CHECK ((clone_status = 'ERROR') != (clone_error IS NULL));
+ ALTER TABLE repository
+ DROP COLUMN clone_in_progress;
+ """)
+
+
+def downgrade():
+ op.execute("""
+ ALTER TABLE repository
+ DROP COLUMN clone_error;
+ ALTER TABLE repository
+ DROP COLUMN clone_status;
+ DROP TYPE clone_status;
+ ALTER TABLE repository
+ ADD COLUMN clone_in_progress boolean NOT NULL DEFAULT false;
+ ALTER TABLE repository
+ ALTER COLUMN clone_in_progress DROP DEFAULT;
+ """)
M gitsrht/templates/empty-repo.html => gitsrht/templates/empty-repo.html +7 -1
@@ 1,6 1,8 @@
{% extends "repo.html" %}
{% block repohead %}
+{% if repo.clone_status == 'IN_PROGRESS' %}
<meta http-equiv="refresh" content="5">
+{% endif %}
{% endblock %}
{% block content %}
@@ 23,10 25,14 @@
</dl>
</div>
<div class="col-md-8">
- {% if repo.clone_in_progress %}
+ {% if repo.clone_status == 'IN_PROGRESS' %}
<div class="alert alert-primary">
A clone operation is currently in progress.
</div>
+ {% elif repo.clone_status == 'ERROR' %}
+ <div class="alert alert-danger">
+ An error occured while cloning: {{repo.clone_error}}
+ </div>
{% endif %}
<p>
Nothing here yet!
M gitsrht/types/__init__.py => gitsrht/types/__init__.py +4 -1
@@ 3,6 3,7 @@ import sqlalchemy as sa
from sqlalchemy import event
import sqlalchemy_utils as sau
from sqlalchemy.ext.declarative import declared_attr
+from sqlalchemy.dialects import postgresql
from srht.database import Base
from srht.oauth import ExternalUserMixin, ExternalOAuthTokenMixin
from gitsrht.git import Repository as GitRepository
@@ 45,7 46,9 @@ class Repository(Base):
nullable=False,
default=RepoVisibility.public)
readme = sa.Column(sa.Unicode)
- clone_in_progress = sa.Column(sa.Boolean, nullable=False)
+ clone_status = sa.Column(postgresql.ENUM(
+ 'NONE', 'IN_PROGRESS', 'COMPLETE', 'ERROR'), nullable=False)
+ clone_error = sa.Column(sa.Unicode)
@declared_attr
def owner_id(cls):