5
0
Fork 0
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:
f 2019-07-16 16:47:44 -03:00
parent f7ca99a7a4
commit cff18bf861
No known key found for this signature in database
GPG key ID: 2AE5A13E321F953D
13 changed files with 289 additions and 9 deletions

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -63,6 +63,18 @@ class SitePolicy
build?
end
def pull?
build?
end
def fetch?
pull?
end
def merge?
pull?
end
private
def current_role

View file

@ -0,0 +1 @@
%time{ datetime: time, title: time }= time_ago_in_words time

View 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'

View file

@ -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)

View file

@ -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:

View file

@ -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:

View file

@ -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

View file

@ -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.

View 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