From 38963f077e3284a770d2093deec9a2dd865b9f42 Mon Sep 17 00:00:00 2001
From: walking dev <104449470+walkingdevel@users.noreply.github.com>
Date: Wed, 29 Jun 2022 10:27:35 +0000
Subject: [PATCH] releases page (#191)
---
.gitignore | 1 +
branch_service.v | 20 +++++------
gitly.v | 18 ++++------
release_entities.v | 2 +-
release_routes.v | 11 +++---
release_service.v | 9 +++--
repo_routes.v | 2 ++
repo_service.v | 53 ++++++++++++++++++----------
repo_template.v | 62 ++++++++-------------------------
settings_entities.v | 1 +
static/css/gitly.scss | 2 +-
static/css/releases.scss | 6 ++++
tag_entities.v | 9 ++---
tag_routes.v | 26 ++++++++++++++
tag_service.v | 53 ++++++++++++++++++++++++++--
templates/releases.html | 8 +++--
utils.v | 7 ++++
validation/validation_service.v | 2 +-
18 files changed, 187 insertions(+), 105 deletions(-)
create mode 100644 static/css/releases.scss
create mode 100644 tag_routes.v
diff --git a/.gitignore b/.gitignore
index 2e76c70..735aa85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@ gitly
static/assets/version
logs/
repos/
+archives/
*.code-workspace
.vscode
diff --git a/branch_service.v b/branch_service.v
index 7697137..3257496 100644
--- a/branch_service.v
+++ b/branch_service.v
@@ -3,6 +3,16 @@ module main
import time
import git
+fn (mut app App) fetch_branches(repo Repo) {
+ branches_output := repo.git('branch -a')
+
+ for branch_output in branches_output.split_into_lines() {
+ branch_name := git.parse_git_branch_output(branch_output)
+
+ app.fetch_branch(repo, branch_name)
+ }
+}
+
fn (mut app App) fetch_branch(repo Repo, branch_name string) {
last_commit_hash := repo.get_last_branch_commit_hash(branch_name)
@@ -26,16 +36,6 @@ fn (mut app App) fetch_branch(repo Repo, branch_name string) {
int(committed_at.unix))
}
-fn (mut app App) fetch_branches(repo Repo) {
- branches_output := repo.git('branch -a')
-
- for branch_output in branches_output.split_into_lines() {
- branch_name := git.parse_git_branch_output(branch_output)
-
- app.fetch_branch(repo, branch_name)
- }
-}
-
fn (mut app App) create_branch_or_update(repository_id int, branch_name string, author string, hash string, date int) {
branch := sql app.db {
select from Branch where repo_id == repository_id && name == branch_name limit 1
diff --git a/gitly.v b/gitly.v
index 747f9d0..6cc719c 100644
--- a/gitly.v
+++ b/gitly.v
@@ -64,7 +64,7 @@ fn new_app() &App {
app.create_tables()
- create_log_directory_if_not_exists('logs')
+ create_directory_if_not_exists('logs')
app.setup_logger()
@@ -85,7 +85,8 @@ fn new_app() &App {
app.load_settings()
- create_repo_storage_directory_if_not_exists(app.settings.repo_storage_path)
+ create_directory_if_not_exists(app.settings.repo_storage_path)
+ create_directory_if_not_exists(app.settings.archive_path)
// Create the first admin user if the db is empty
app.find_user_by_id(1) or {}
@@ -227,14 +228,9 @@ fn (mut app App) create_tables() {
}
}
-fn create_log_directory_if_not_exists(path string) {
- if !os.is_dir(path) {
- os.mkdir(path) or { panic('cannot create logs directory') }
- }
-}
+// maybe it should be implemented with another static server, in dev
+fn (mut app App) send_file(filname string, content string) vweb.Result {
+ app.add_header('Content-Disposition', 'attachment; filename="$filname"')
-fn create_repo_storage_directory_if_not_exists(path string) {
- if !os.exists(path) {
- os.mkdir(path) or { panic('cannot create storage directory') }
- }
+ return app.ok(content)
}
diff --git a/release_entities.v b/release_entities.v
index 6c81aac..ce9de83 100644
--- a/release_entities.v
+++ b/release_entities.v
@@ -11,5 +11,5 @@ mut:
tag_name string [skip]
tag_hash string [skip]
user string [skip]
- date time.Time [skip]
+ date time.Time
}
diff --git a/release_routes.v b/release_routes.v
index e209d42..d1e28a5 100644
--- a/release_routes.v
+++ b/release_routes.v
@@ -3,9 +3,10 @@ module main
import vweb
import time
-['/:user/:repo/releases']
-pub fn (mut app App) releases(user_str string, repo string) vweb.Result {
- if !app.exists_user_repo(user_str, repo) {
+// TODO: add pagination
+['/:username/:repo_name/releases']
+pub fn (mut app App) releases(username string, repo_name string) vweb.Result {
+ if !app.exists_user_repo(username, repo_name) {
return app.not_found()
}
@@ -18,6 +19,8 @@ pub fn (mut app App) releases(user_str string, repo string) vweb.Result {
rels := app.find_repo_releases(app.repo.id)
users := app.find_repo_registered_contributor(app.repo.id)
+ download_archive_prefix := '/$username/$repo_name/tag'
+
for rel in rels {
release.notes = rel.notes
mut user_id := 0
@@ -26,7 +29,7 @@ pub fn (mut app App) releases(user_str string, repo string) vweb.Result {
if tag.id == rel.tag_id {
release.tag_name = tag.name
release.tag_hash = tag.hash
- release.date = time.unix(tag.date)
+ release.date = time.unix(tag.created_at)
user_id = tag.user_id
break
}
diff --git a/release_service.v b/release_service.v
index 9b30818..292ad8c 100644
--- a/release_service.v
+++ b/release_service.v
@@ -1,10 +1,13 @@
module main
-pub fn (mut app App) add_release(tag_id int, repo_id int) {
+import time
+
+pub fn (mut app App) add_release(tag_id int, repo_id int, date time.Time, notes string) {
release := Release{
tag_id: tag_id
repo_id: repo_id
- notes: 'Some notes about this release...'
+ notes: notes
+ date: date
}
sql app.db {
@@ -14,7 +17,7 @@ pub fn (mut app App) add_release(tag_id int, repo_id int) {
pub fn (mut app App) find_repo_releases(repo_id int) []Release {
return sql app.db {
- select from Release where repo_id == repo_id
+ select from Release where repo_id == repo_id order by date desc
}
}
diff --git a/repo_routes.v b/repo_routes.v
index fe6e3c8..70cdd20 100644
--- a/repo_routes.v
+++ b/repo_routes.v
@@ -264,6 +264,8 @@ pub fn (mut app App) tree(username string, repository_name string, branch string
repo_id := app.repo.id
log_prefix := '$username/$repository_name'
+ app.fetch_tags(app.repo)
+
app.current_path = '/$path'
if app.current_path.contains('/favicon.svg') {
return vweb.not_found()
diff --git a/repo_service.v b/repo_service.v
index 26df2e9..b6e8fd3 100644
--- a/repo_service.v
+++ b/repo_service.v
@@ -23,6 +23,18 @@ enum RepoStatus {
clone_done
}
+enum ArchiveFormat {
+ zip
+ tar
+}
+
+fn (f ArchiveFormat) str() string {
+ return match f {
+ .zip { 'zip' }
+ .tar { 'tar' }
+ }
+}
+
fn (mut app App) save_repository(repository Repo) {
id := repository.id
desc := repository.description
@@ -187,12 +199,12 @@ fn (mut app App) user_has_repo(user_id int, repo_name string) bool {
return count >= 0
}
-fn (mut app App) update_repository(mut repository Repo) {
- repository_id := repository.id
+fn (mut app App) update_repository(mut repo Repo) {
+ repo_id := repo.id
- repository.analyse_lang(app)
+ repo.analyse_lang(app)
- data := repository.git('--no-pager log --abbrev-commit --abbrev=7 --pretty="%h$log_field_separator%aE$log_field_separator%cD$log_field_separator%s$log_field_separator%aN"')
+ data := repo.git('--no-pager log --abbrev-commit --abbrev=7 --pretty="%h$log_field_separator%aE$log_field_separator%cD$log_field_separator%s$log_field_separator%aN"')
app.db.exec('BEGIN TRANSACTION')
for line in data.split_into_lines() {
@@ -212,34 +224,34 @@ fn (mut app App) update_repository(mut repository Repo) {
user := app.find_user_by_email(commit_author_email) or { User{} }
if user.id > 0 {
- app.add_contributor(user.id, repository_id)
+ app.add_contributor(user.id, repo_id)
commit_author_id = user.id
}
- app.add_commit_if_not_exist(repository_id, commit_hash, commit_author, commit_author_id,
+ app.add_commit_if_not_exist(repo_id, commit_hash, commit_author, commit_author_id,
commit_message, int(commit_date.unix))
}
}
- app.info(repository.contributors_count.str())
- app.fetch_branches(repository)
+ app.info(repo.contributors_count.str())
+ app.fetch_branches(repo)
+ app.fetch_tags(repo)
- repository.commits_count = app.get_count_repo_commits(repository_id)
- repository.contributors_count = app.get_count_repo_contributors(repository_id)
- repository.branches_count = app.get_count_repo_branches(repository_id)
+ repo.commits_count = app.get_count_repo_commits(repo_id)
+ repo.contributors_count = app.get_count_repo_contributors(repo_id)
+ repo.branches_count = app.get_count_repo_branches(repo_id)
- app.update_repo_commits_count(repository_id, repository.commits_count)
- app.update_repo_contributors_count(repository_id, repository.contributors_count)
+ app.update_repo_commits_count(repo_id, repo.commits_count)
+ app.update_repo_contributors_count(repo_id, repo.contributors_count)
- // TODO: TEMPORARY - UNTIL WE GET PERSISTENT RELEASE INFO
- for tag in app.get_all_repo_tags(repository_id) {
- app.add_release(tag.id, repository_id)
+ for tag in app.get_all_repo_tags(repo_id) {
+ app.add_release(tag.id, repo_id, time.unix(tag.created_at), tag.message)
- repository.releases_count++
+ repo.releases_count++
}
- app.save_repository(repository)
+ app.save_repository(repo)
app.db.exec('END TRANSACTION')
app.info('Repository updated')
}
@@ -603,6 +615,11 @@ fn (r Repo) git_advertise(service string) string {
return git_output
}
+fn (r Repo) archive_tag(tag string, path string, format ArchiveFormat) {
+ // TODO: check tag name before running command
+ r.git('archive $tag --format=$format --output="$path"')
+}
+
fn (r Repo) get_commit_patch(commit_hash string) ?string {
patch := r.git('format-patch --stdout -1 $commit_hash')
diff --git a/repo_template.v b/repo_template.v
index a091c3d..2e61db0 100644
--- a/repo_template.v
+++ b/repo_template.v
@@ -2,72 +2,38 @@ module main
import vweb
-fn (r &Repo) format_commits_count() vweb.RawHtml {
- nr := r.commits_count
-
- if nr == 1 {
- return '1 commit'
+fn get_declension_form(count int, first_form string, second_form string) string {
+ if count == 1 {
+ return '1 $first_form'
}
- return '$nr commits'
+ return '$count $second_form'
}
-fn (r &Repo) format_branches_count() vweb.RawHtml {
- nr := r.branches_count
-
- if nr == 1 {
- return '1 branch'
- }
+fn (r &Repo) format_commits_count() vweb.RawHtml {
+ return get_declension_form(r.commits_count, 'commit', 'commits')
+}
- return '$nr branches'
+fn (r &Repo) format_branches_count() vweb.RawHtml {
+ return get_declension_form(r.branches_count, 'branch', 'branches')
}
fn (r &Repo) format_open_prs_count() vweb.RawHtml {
- nr := r.open_prs_count
-
- if nr == 1 {
- return '1 pull request'
- }
-
- return '$nr pull requests'
+ return get_declension_form(r.open_prs_count, 'pull request', 'pull requests')
}
fn (r &Repo) format_open_issues_count() vweb.RawHtml {
- nr := r.open_issues_count
-
- if nr == 1 {
- return '1 issue'
- }
-
- return '$nr issues'
+ return get_declension_form(r.open_issues_count, 'issue', 'issues')
}
fn (r &Repo) format_contributors_count() vweb.RawHtml {
- nr := r.contributors_count
-
- if nr == 1 {
- return '1 contributor'
- }
-
- return '$nr contributors'
+ return get_declension_form(r.contributors_count, 'contributor', 'contributors')
}
fn (r &Repo) format_topics_count() vweb.RawHtml {
- nr := r.topics_count
-
- if nr == 1 {
- return '1 discussion'
- }
-
- return '$nr discussions'
+ return get_declension_form(r.topics_count, 'discussion', 'discussions')
}
fn (r &Repo) format_releases_count() vweb.RawHtml {
- nr := r.releases_count
-
- if nr == 1 {
- return '1 release'
- }
-
- return '$nr releases'
+ return get_declension_form(r.releases_count, 'release', 'releases')
}
diff --git a/settings_entities.v b/settings_entities.v
index 36dd77c..8cdf109 100644
--- a/settings_entities.v
+++ b/settings_entities.v
@@ -8,5 +8,6 @@ mut:
oauth_client_id string
oauth_client_secret string
repo_storage_path string = './repos'
+ archive_path string = './archives'
hostname string = 'gitly.org'
}
diff --git a/static/css/gitly.scss b/static/css/gitly.scss
index c1d3996..290fea3 100644
--- a/static/css/gitly.scss
+++ b/static/css/gitly.scss
@@ -271,4 +271,4 @@ form {
}
}
-@import 'files', 'langs', 'tree', 'commits', 'hl_table', 'branches', 'admin', 'blob', 'new', 'contributors', 'issues', 'user';
+@import 'files', 'langs', 'tree', 'commits', 'hl_table', 'branches', 'admin', 'blob', 'new', 'contributors', 'issues', 'user', 'releases';
diff --git a/static/css/releases.scss b/static/css/releases.scss
new file mode 100644
index 0000000..e04326c
--- /dev/null
+++ b/static/css/releases.scss
@@ -0,0 +1,6 @@
+.release {
+ border: 1px solid #dfdfdf;
+ border-radius: 3px;
+ margin-top: 10px;
+ padding: 10px;
+}
diff --git a/tag_entities.v b/tag_entities.v
index a262304..d20cc46 100644
--- a/tag_entities.v
+++ b/tag_entities.v
@@ -4,8 +4,9 @@ struct Tag {
id int [primary; sql: serial]
repo_id int [unique: 'tag']
mut:
- name string [unique: 'tag'] // tag name
- hash string // hash of latest commit on tag
- user_id int // id of user that created the tag
- date int // time of latest commit on tag
+ name string [unique: 'tag']
+ hash string
+ message string
+ user_id int
+ created_at int
}
diff --git a/tag_routes.v b/tag_routes.v
new file mode 100644
index 0000000..8592585
--- /dev/null
+++ b/tag_routes.v
@@ -0,0 +1,26 @@
+module main
+
+import vweb
+import os
+
+['/:username/:repo_name/tag/:tag/:format']
+pub fn (mut app App) handle_download_tag_archive(username string, repo_name string, tag string, format string) vweb.Result {
+ // access checking will be implemented in another module
+ user := app.find_user_by_username(username) or { return app.not_found() }
+ repo := app.find_repo_by_name(user.id, repo_name) or { return app.not_found() }
+
+ archive_abs_path := os.abs_path(app.settings.archive_path)
+ snapshot_format := if format == 'zip' { 'zip' } else { 'tar.gz' }
+ snapshot_name := '${username}_${repo_name}_${tag}.$snapshot_format'
+ archive_path := '$archive_abs_path/$snapshot_name'
+
+ if format == 'zip' {
+ repo.archive_tag(tag, archive_path, .zip)
+ } else {
+ repo.archive_tag(tag, archive_path, .tar)
+ }
+
+ archive_content := os.read_file(archive_path) or { return app.not_found() }
+
+ return app.send_file(snapshot_name, archive_content)
+}
diff --git a/tag_service.v b/tag_service.v
index 12bda15..43e8410 100644
--- a/tag_service.v
+++ b/tag_service.v
@@ -2,8 +2,57 @@
// Use of this source code is governed by a GPL license that can be found in the LICENSE file.
module main
-pub fn (mut app App) get_all_repo_tags(repo_id int) []Tag {
+import time
+
+fn (mut app App) fetch_tags(repo Repo) {
+ tags_output := repo.git('tag --format="%(refname:lstrip=2)$log_field_separator%(objectname)$log_field_separator%(subject)$log_field_separator%(authoremail)$log_field_separator%(creatordate:rfc)"')
+
+ for tag_output in tags_output.split_into_lines() {
+ tag_parts := tag_output.split(log_field_separator)
+ tag_name := tag_parts[0]
+ commit_hash := tag_parts[1]
+ commit_message := tag_parts[2]
+ author_email := tag_parts[3]
+ commit_date := time.parse_rfc2822(tag_parts[4]) or {
+ app.info('Error: $err')
+ return
+ }
+
+ user := app.find_user_by_email(author_email) or {
+ User{
+ username: author_email
+ }
+ }
+
+ app.add_tag(repo.id, tag_name, commit_hash, commit_message, user.id, int(commit_date.unix))
+ }
+}
+
+fn (mut app App) add_tag(repo_id int, tag_name string, commit_hash string, commit_message string, user_id int, date int) {
+ tag := sql app.db {
+ select from Tag where repo_id == repo_id && name == tag_name limit 1
+ }
+
+ if tag.id != 0 {
+ return
+ }
+
+ new_tag := Tag{
+ repo_id: repo_id
+ name: tag_name
+ hash: commit_hash
+ message: commit_message
+ user_id: user_id
+ created_at: date
+ }
+
+ sql app.db {
+ insert new_tag into Tag
+ }
+}
+
+fn (mut app App) get_all_repo_tags(repo_id int) []Tag {
return sql app.db {
- select from Tag where repo_id == repo_id order by date desc
+ select from Tag where repo_id == repo_id order by created_at desc
}
}
diff --git a/templates/releases.html b/templates/releases.html
index b42097e..2a28166 100644
--- a/templates/releases.html
+++ b/templates/releases.html
@@ -8,10 +8,14 @@
.release {
Release: @r.tag_name
Hash: @r.tag_hash
- Created by: @r.user
+ @if r.user != ''
+ Created by: @r.user
+ @end
+ Created at: @r.date
Notes: @r.notes
+ Download zip,
+ download tar
}
-
@end
}
@else
diff --git a/utils.v b/utils.v
index cb08aae..7b3cf9a 100644
--- a/utils.v
+++ b/utils.v
@@ -2,6 +2,7 @@ module main
import time
import math
+import os
pub fn (mut app App) running_since() string {
duration := time.now().unix - app.started_at
@@ -28,3 +29,9 @@ pub fn (mut app App) make_path(i int) string {
return s
}
+
+fn create_directory_if_not_exists(path string) {
+ if !os.exists(path) {
+ os.mkdir(path) or { panic('cannot create $path directory') }
+ }
+}
diff --git a/validation/validation_service.v b/validation/validation_service.v
index fde995e..4f4b835 100644
--- a/validation/validation_service.v
+++ b/validation/validation_service.v
@@ -11,7 +11,7 @@ pub fn is_username_valid(value string) bool {
}
pub fn is_repository_name_valid(value string) bool {
- query := r'^[A-Za-z][A-Za-z0-9_\.\-]{1,100}$'
+ query := r'^[A-Za-z][A-Za-z0-9_\.\-]{0,100}$'
mut re := regex.regex_opt(query) or { panic(err) }
--
2.39.5