mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-22 02:36:22 +00:00
poder actualizar el sitio a partir del skel
This commit is contained in:
parent
f7ca99a7a4
commit
cff18bf861
13 changed files with 289 additions and 9 deletions
|
@ -114,6 +114,26 @@ class SitesController < ApplicationController
|
|||
redirect_to site_posts_path @site
|
||||
end
|
||||
|
||||
def fetch
|
||||
@site = find_site
|
||||
authorize @site
|
||||
|
||||
@commits = @site.repository.commits
|
||||
end
|
||||
|
||||
def merge
|
||||
@site = find_site
|
||||
authorize @site
|
||||
|
||||
if @site.repository.merge(current_usuarie)
|
||||
flash[:success] = I18n.t('sites.fetch.merge.success')
|
||||
else
|
||||
flash[:error] = I18n.t('sites.fetch.merge.error')
|
||||
end
|
||||
|
||||
redirect_to sites_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def site_params
|
||||
|
|
|
@ -28,6 +28,18 @@ class Site < ApplicationRecord
|
|||
|
||||
attr_accessor :jekyll, :collections
|
||||
|
||||
# El repositorio git para este sitio
|
||||
def repository
|
||||
@repository ||= Site::Repository.new path
|
||||
end
|
||||
|
||||
# Trae los cambios del skel y verifica que haya cambios
|
||||
def needs_pull?
|
||||
!repository.commits.empty?
|
||||
end
|
||||
|
||||
# TODO: Mover esta consulta a la base de datos para no traer un montón
|
||||
# de cosas a la memoria
|
||||
def invitade?(usuarie)
|
||||
invitades.pluck(:id).include? usuarie.id
|
||||
end
|
||||
|
|
78
app/models/site/repository.rb
Normal file
78
app/models/site/repository.rb
Normal file
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Site
|
||||
# Acciones para el repositorio Git de un sitio. Por ahora hacemos un
|
||||
# uso muy básico de Git, con lo que asumimos varias cosas, por ejemplo
|
||||
# que un sitio tiene un solo origen, que siempre se trabaja con la
|
||||
# rama master, etc.
|
||||
class Repository
|
||||
attr_reader :rugged, :changes
|
||||
|
||||
def initialize(path)
|
||||
@rugged = Rugged::Repository.new(path)
|
||||
@changes = 0
|
||||
end
|
||||
|
||||
def remote
|
||||
@remote ||= rugged.remotes.first
|
||||
end
|
||||
|
||||
# Trae los cambios del repositorio de origen sin aplicarlos y
|
||||
def fetch
|
||||
if remote.check_connection :fetch
|
||||
@changes = rugged.fetch(remote)[:received_objects]
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
# Incorpora los cambios en el repositorio actual
|
||||
#
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def merge(author)
|
||||
master = rugged.branches['master'].target
|
||||
origin = rugged.branches['origin/master'].target
|
||||
merge = rugged.merge_commits(master, origin)
|
||||
|
||||
# No hacemos nada si hay conflictos
|
||||
#
|
||||
# TODO: Enviar un correo a administración para poder revisar
|
||||
# manualmente. Idealmente no deberíamos tener conflictos pero
|
||||
# quién sabe.
|
||||
return if merge.conflicts?
|
||||
|
||||
author = { name: author.name, email: author.email }
|
||||
commit = Rugged::Commit
|
||||
.create(rugged,
|
||||
parents: [master, origin],
|
||||
tree: merge.write_tree(rugged),
|
||||
message: I18n.t('sites.fetch.merge.message'),
|
||||
author: author,
|
||||
committer: author,
|
||||
update_ref: 'HEAD')
|
||||
|
||||
# Forzamos el checkout para mover el HEAD al último commit y
|
||||
# escribir los cambios
|
||||
rugged.checkout 'HEAD', strategy: :force
|
||||
commit
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
# Compara los commits entre el repositorio remoto y el actual para
|
||||
# que luego los podamos mostrar.
|
||||
def commits
|
||||
walker = Rugged::Walker.new rugged
|
||||
|
||||
# Obtenemos todos los commits que existen en origin/master que no
|
||||
# están en la rama master local
|
||||
#
|
||||
# XXX: monitorear esto por performance
|
||||
walker.push 'refs/remotes/origin/master'
|
||||
walker.hide 'refs/heads/master'
|
||||
|
||||
walker.each.to_a
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,6 +11,10 @@ class Usuarie < ApplicationRecord
|
|||
has_many :roles
|
||||
has_many :sites, through: :roles
|
||||
|
||||
def name
|
||||
email.split('@', 2).first
|
||||
end
|
||||
|
||||
def rol_for_site(site)
|
||||
site.roles.merge(roles).first
|
||||
end
|
||||
|
|
|
@ -63,6 +63,18 @@ class SitePolicy
|
|||
build?
|
||||
end
|
||||
|
||||
def pull?
|
||||
build?
|
||||
end
|
||||
|
||||
def fetch?
|
||||
pull?
|
||||
end
|
||||
|
||||
def merge?
|
||||
pull?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_role
|
||||
|
|
1
app/views/layouts/_time.haml
Normal file
1
app/views/layouts/_time.haml
Normal file
|
@ -0,0 +1 @@
|
|||
%time{ datetime: time, title: time }= time_ago_in_words time
|
34
app/views/sites/fetch.haml
Normal file
34
app/views/sites/fetch.haml
Normal file
|
@ -0,0 +1,34 @@
|
|||
.row
|
||||
.col
|
||||
= render 'layouts/breadcrumb',
|
||||
crumbs: [link_to(t('sites.index'), sites_path), t('.title')]
|
||||
.row.justify-content-center
|
||||
.col-md-8#pull
|
||||
%h1= t('.title')
|
||||
%p.lead= sanitize_markdown t('.help.fetch'), tags: %w[em strong a]
|
||||
|
||||
%h2= t('.toc')
|
||||
%ul.toc
|
||||
- @commits.each do |commit|
|
||||
%li= link_to commit.summary, "##{commit.oid}"
|
||||
|
||||
- @commits.each do |commit|
|
||||
.row.justify-content-center
|
||||
.col-md-8{ id: commit.oid }
|
||||
%h1= commit.summary
|
||||
%p.lead= render 'layouts/time', time: commit.time
|
||||
|
||||
-#
|
||||
No hay forma de obtener el cuerpo del commit separado del
|
||||
resumen, cortamos por el primer salto de línea doble y obtenemos
|
||||
todo lo demás
|
||||
= sanitize_markdown commit.message.split("\n\n", 2).last,
|
||||
tags: %w[p a h1 h2 h3 h4 h5 h6 ol ul li strong em]
|
||||
|
||||
%hr
|
||||
|
||||
- unless @commits.empty?
|
||||
.row.justify-content-center
|
||||
.col-md-8
|
||||
= link_to t('.merge.request'), site_pull_path(@site),
|
||||
method: 'post', class: 'btn btn-lg btn-success'
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
.row
|
||||
.col
|
||||
= render 'layouts/breadcrumb', crumbs: [ t('sites.index') ]
|
||||
= render 'layouts/breadcrumb', crumbs: [t('sites.index')]
|
||||
.row
|
||||
.col
|
||||
%h1
|
||||
|
@ -20,14 +19,15 @@
|
|||
%h2
|
||||
- if policy(site).show?
|
||||
= link_to site.name, site_path(site)
|
||||
- else
|
||||
- else
|
||||
= site.name
|
||||
- if site.invitade? current_usuarie
|
||||
%span.badge.badge-warning{data: { toggle: 'tooltip' },
|
||||
title: t('help.sites.invitade')}
|
||||
%span.badge.badge-warning{ data: { toggle: 'tooltip' },
|
||||
title: t('help.sites.invitade') }
|
||||
= t('.invitade')
|
||||
%br
|
||||
.btn-group{role: 'group', 'aria-label': t('sites.actions')}
|
||||
.btn-group{ role: 'group',
|
||||
'aria-label': t('sites.actions') }
|
||||
- if current_usuarie.rol_for_site(site).temporal
|
||||
= button_to t('sites.invitations.accept'),
|
||||
site_usuaries_accept_invitation_path(site),
|
||||
|
@ -68,7 +68,8 @@
|
|||
type: 'secondary',
|
||||
link: nil
|
||||
- else
|
||||
= form_tag site_enqueue_path(site), method: :post, class: 'form-inline' do
|
||||
= form_tag site_enqueue_path(site),
|
||||
method: :post, class: 'form-inline' do
|
||||
= button_tag type: 'submit',
|
||||
class: 'btn btn-success',
|
||||
title: t('help.sites.enqueue'),
|
||||
|
@ -85,3 +86,9 @@
|
|||
text: t('sites.build_log'),
|
||||
type: 'warning',
|
||||
link: site_build_log_path(site)
|
||||
- if policy(site).pull? && site.needs_pull?
|
||||
= render 'layouts/btn_with_tooltip',
|
||||
tooltip: t('help.sites.pull'),
|
||||
text: t('.pull'),
|
||||
type: 'info',
|
||||
link: site_pull_path(site)
|
||||
|
|
|
@ -158,6 +158,16 @@ en:
|
|||
edit:
|
||||
title: 'Edit %{site}'
|
||||
submit: 'Save changes'
|
||||
fetch:
|
||||
title: 'Upgrade the site'
|
||||
help:
|
||||
fetch: 'Any changes made to the site are saved into a _git_ repository. Git saves the differences between previous and current versions of files so we can explore them as the history of the project. Also, we can bring and send changes between repositories. In this case, every site managed with Sutty share a common root that we call [skeleton](https://0xacab.org/sutty/skel.sutty.nl). When we upgrade this skeleton, you can explore the changes here and accept them to make your site better.'
|
||||
toc: 'Table of contents'
|
||||
merge:
|
||||
request: 'Upgrade my site with these changes'
|
||||
success: 'Site upgrade has been completed. Your next build will run this upgrade :)'
|
||||
error: "There was an error when we were trying to upgrade your site. This could be due to conflicts that couldn't be solved automatically. We've sent a report of the issue to Sutty's admins so they already know about it. Sorry! :("
|
||||
message: 'Skeleton upgrade'
|
||||
footer:
|
||||
powered_by: 'is developed by'
|
||||
templates:
|
||||
|
|
|
@ -163,6 +163,16 @@ es:
|
|||
edit:
|
||||
title: 'Editar %{site}'
|
||||
submit: 'Guardar cambios'
|
||||
fetch:
|
||||
title: 'Actualizar el sitio'
|
||||
help:
|
||||
fetch: 'Todos los cambios en el sitio se guardan en un repositorio _git_. En git, se guarda la diferencia entre una versión anterior y la actual de todos los archivos y podemos explorar la historia de un proyecto. Además, podemos traer y enviar cambios con otros repositorios. En este caso, todos los sitios gestionados desde Sutty tienen una raíz común, que llamamos [esqueleto](https://0xacab.org/sutty/skel.sutty.nl). Cuando hacemos cambios en el esqueleto para mejorar los sitios, podés explorar los cambios aquí y aceptarlos.'
|
||||
toc: 'Tabla de contenidos'
|
||||
merge:
|
||||
request: 'Incorporar los cambios en mi sitio'
|
||||
success: 'Ya se incorporaron los cambios en el sitio, se aplicarán en la próxima compilación que hagas :)'
|
||||
error: 'Hubo un error al incorporar los cambios en el sitio. Esto puede deberse a conflictos entre cambios que no se pueden resolver automáticamente. Hemos enviado un reporte del problema a les administradores de Sutty para que estén al tanto de la situación. ¡Lo sentimos! :('
|
||||
message: 'Actualización del esqueleto'
|
||||
footer:
|
||||
powered_by: 'es desarrollada por'
|
||||
i18n:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop:disable Metrics/BlockLength
|
||||
Rails.application.routes.draw do
|
||||
devise_for :usuaries
|
||||
|
||||
|
@ -14,6 +15,10 @@ Rails.application.routes.draw do
|
|||
resources :sites, constraints: { site_id: %r{[^/]+}, id: %r{[^/]+} } do
|
||||
get 'public/:type/:basename', to: 'sites#send_public_file'
|
||||
|
||||
# Gestionar actualizaciones del sitio
|
||||
get 'pull', to: 'sites#fetch'
|
||||
post 'pull', to: 'sites#merge'
|
||||
|
||||
# Gestionar usuaries
|
||||
get 'usuaries/invite', to: 'usuaries#invite'
|
||||
post 'usuaries/invite', to: 'usuaries#send_invitations'
|
||||
|
@ -41,3 +46,4 @@ Rails.application.routes.draw do
|
|||
post 'reorder_posts', to: 'sites#reorder_posts'
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/BlockLength
|
||||
|
|
|
@ -96,9 +96,9 @@ El sitio esqueleto es un repositorio Git que se clona al directorio del
|
|||
sitio. Esto permite luego pullear actualizaciones desde el esqueleto a
|
||||
los sitios, esperamos que sin conflictos!
|
||||
|
||||
## Plantillas
|
||||
## Diseño
|
||||
|
||||
Las plantillas son plantillas Jekyll adaptadas a Sutty. Vamos a empezar
|
||||
Los diseños son plantillas Jekyll adaptadas a Sutty. Vamos a empezar
|
||||
adaptando las que estén disponibles en <https://jekyllthemes.org/> y
|
||||
otras fuentes, agregando features de Sutty y simplificando donde haga
|
||||
falta (algunas plantillas tienen requisitos extraños).
|
||||
|
@ -113,3 +113,52 @@ eliminarlo, eliminar el directorio.
|
|||
|
||||
Lo correcto sería preguntar a todes les usuaries si están de acuerdo en
|
||||
borrar el sitio. Si una no está de acuerdo, el borrado se cancela.
|
||||
|
||||
### Licencias
|
||||
|
||||
Las licencias disponibles se pueden gestionar desde la base de datos.
|
||||
Los atributos son:
|
||||
|
||||
* Titulo
|
||||
* URL
|
||||
* Descripción, por qué la recomendamos, etc.
|
||||
|
||||
El problema que tenemos es que las queremos tener traducidas, entonces
|
||||
hay varias opciones:
|
||||
|
||||
* Incorporar columna idioma en la base de datos y cada vez que se
|
||||
muestren las licencias filtrar por idioma actual (o idioma por
|
||||
defecto).
|
||||
|
||||
Esto nos permitiría ofrecer licencias por jurisdicción también, aunque
|
||||
empezaríamos con las internacionales...
|
||||
|
||||
* Incorporar la gema de traducción y poner las traducciones en la base
|
||||
de datos. Esto permite tener una sola licencia con sus distintas
|
||||
traducciones en un solo registro.
|
||||
|
||||
Pensábamos que necesitábamos cambiar a PostgreSQL, pero la gema
|
||||
[Mobility](https://github.com/shioyama/mobility) permite usar
|
||||
distintas estrategias.
|
||||
|
||||
* Incorporar las traducciones a los locales de sutty. Esta es la opción
|
||||
menos flexible, porque implica agregar licencias a la base de datos y
|
||||
al mismo tiempo actualizar los archivos y re-deployear Sutty... mejor
|
||||
no.
|
||||
|
||||
Pero es importante que estén asociadas entre sí por idioma.
|
||||
|
||||
Permitir que les usuaries elijan licencia+privacidad+codigo de
|
||||
convivencia e informarles que van a ser los primeros artículos dentro de
|
||||
su sitio y que los pueden modificar después. Que esta es nuestra
|
||||
propuesta tecnopolítica para que los espacios digitales y analógicos
|
||||
sean espacios amables siguiente una lógica de cuidados colectivos.
|
||||
|
||||
## Actualizar skel
|
||||
|
||||
Cuando actualizamos el skel, sutty pide a todos los sitios
|
||||
consentimiento para aplicar las actualizaciones. Antes de aplicar las
|
||||
actualizaciones muestra el historial para que les usuaries vean cuales
|
||||
son los cambios que se van a aplicar. Este historial viene del
|
||||
repositorio git con lo que tenemos que tomarnos la costumbre de escribir
|
||||
commits completos con explicación.
|
||||
|
|
37
test/models/site/repository_test.rb
Normal file
37
test/models/site/repository_test.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@rol = create :rol
|
||||
@site = @rol.site
|
||||
@usuarie = @rol.usuarie
|
||||
|
||||
# Volver al principio para poder traer cambios
|
||||
Dir.chdir(@site.path) do
|
||||
`git reset --hard e0627e34c6ef6ae2592d7f289b82def20ba56685`
|
||||
end
|
||||
end
|
||||
|
||||
teardown do
|
||||
@site.destroy
|
||||
end
|
||||
|
||||
test 'se pueden traer cambios' do
|
||||
assert @site.repository.fetch.is_a?(Integer)
|
||||
end
|
||||
|
||||
test 'se pueden mergear los cambios' do
|
||||
assert !@site.repository.commits.empty?
|
||||
assert @site.repository.merge(@usuarie)
|
||||
assert @site.repository.commits.empty?
|
||||
|
||||
assert_equal @usuarie.name,
|
||||
@site.repository.rugged
|
||||
.branches['master'].target.committer[:name]
|
||||
|
||||
Dir.chdir(@site.path) do
|
||||
assert_equal 'nothing to commit, working tree clean',
|
||||
`LC_ALL=C git status`.strip.split("\n").last
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue