Manuale dello sviluppatore¶
Sostieni lo sviluppo di Ghini¶
L’installazione di Ghini sempre include scaricare i sorgenti, collegati al repository su github. Questo è così perché nei nostri occhi, ogni utente è sempre potenzialmente anche uno sviluppatore.
Se volete contribuire a Ghini, può farlo in diversi modi:
Utilizzare il software, notare cosa piace di meno e potrebbe essere corretto, ``segnalarlo come problema <http://github.com/ghini/ghini.desktop/issues/new>`_ separatamente. Uno sviluppatore reagirà prima di quanto si possa immaginare.
Se avete un’idea di ciò che manca nel software ma non abbastanza e formalizzare in questioni separate, si potrebbe prendere in considerazione l’assunzione di un professionista. Questo è il modo migliore per assicurarsi che qualcosa accade rapidamente su Ghini. Assicurarsi che lo sviluppatore apra problemi su github e vi pubblichi il contributi.
Traduci! Qualsiasi aiuto con traduzione sarà gradito, così si prega di tradurre! è possibile farlo senza installare nulla sul tuo computer, usando solo il servizio di traduzione on-line offerti da http://hosted.weblate.org/
“Fork the repository”, scegliere un problema, risolverlo, aprire una richiesta “pull request”. Vedere il flusso di lavoro per risolvere bug qui sotto.
Se non hai ancora installato Ghini e desideri dare un’occhiata alla storia del suo codice, è possibile aprire la nostra pagina progetto github e vedere tutto ciò che s’è fatto intorno a Ghini fin dalla sua nascita nel 2004 come Bauble.
Fonte del software, versioni, rami¶
Se si desidera che una particolare versione di Ghini, abbiamo rilasciare e gestire versioni come rami. Si dovrebbe effettuare il git checkout
del ramo corrispondente alla versione di vostra scelta.
linea di produzione¶
Nomi dei rami per le versioni stabili (produzione) di Ghini sono nella forma ghini-x.y
(ad esempio: ghini-1.0); nomi dei rami dove sono pubblicate le versioni di prova di Ghini sono nella forma ghini-x.y-dev
(ad esempio: ghini-1.0-dev).
Flusso di sviluppo¶
Il nostro flusso di lavoro è di impegnarsi continuamente per la branca di test, spesso di spingerli a github, di lasciare travis-ci e coveralls.io di controllare la qualità dei rami test spinti, infine, di volta in volta, per unire il ramo testing la versione corrispondente.
Quando si lavora a problemi più grandi, che sembrano richiedere più di un paio di giorni, io potrei aprire una filiale connessa alla questione. Non lo faccio molto spesso.
problemi più importanti¶
Quando affronta un problema più grande singolo, creare un tag di ramo all’estremità di una linea di sviluppo principale (ad es.: ghini-1.0-dev
) e seguire il flusso di lavoro descritto al
https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging
In Breve
git up
git checkout -b issue-xxxx
git push origin issue-xxxx
Lavorare sul nuovo ramo temporaneo. Quando è pronto, andare a github, unire il ramo con la linea di sviluppo principale da cui diramano, risolvere i conflitti, ove necessario, eliminare il ramo temporaneo.
Quando si è pronti per la pubblicazione, è possibile unire la linea di sviluppo nella linea di produzione corrispondente.
Aggiornando l’insieme di stringhe traducibili¶
Di tanto in tanto, durante il processo di aggiornamento del software, si verrà essere aggiungendo o modificando le stringhe nelle fonti python, nella documentazione, nelle fonti glade. La maggior parte delle nostre corde sono traducibile e sono offerti a weblate per le persone a contribuire, sotto forma di diversi file. po
.
Un file po
è specifico di una lingua ed è composto da coppie di testi, in lingua originale e la corrispondente traduzione. Quando un traduttore aggiunge una traduzione su weblate, questa raggiunge il nostro repositorio github. Quanto un programmatore aggiunge una stringa nel software, questa raggiunge weblate per venir tradotta.
Weblate ospita il progetto Ghini. Il progetto è suddiviso in componenti, ciascuna corrispondente ad un branch di un nostro repositorio github. Ciascuna componente accetta traduzioni in diverse lingue.
componente |
repositorio |
branch |
---|---|---|
Desktop 1.0 | ghini.desktop | ghini-1.0-dev |
Desktop 1.1 | ghini.desktop | ghini-1.1-dev |
Documentation 1.0 | ghini.desktop-docs.i18n | ghini-1.0-dev |
Documentation 1.1 | ghini.desktop-docs.i18n | ghini-1.1-dev |
Web 1.2 | ghini.web | master |
Per aggiornare i file po
relativi al software, esegui il seguente script dalla linea di comando, preferibilmente dalla base del checkout di ghini.desktop:
./scripts/i18n.sh
Per aggiornare i file po
relativi alla documentazione, esegui il seguente script dalla linea di comando, preferibilmente dalla base del checkout di ghini.desktop-docs.i18n:
./doc/runme.sh
Quando si esegue uno degli script di cui sopra, è probabile che si abbia bisogno di fare il commit di tutti i file po del progetto. Si potrebbero voler esaminare le modifiche prima di inserirle nel respository. Questo è più importante quando si esegue una correzione marginale di una stringa, come la rimozione di un errore di battitura.
La nostra documentazione su readthedocs ha una versione originale in lingua inglese, e diverse traduzioni. Seguiamo semplicemente la descrizione della localizzazione. Qui non c’è nulla che abbiamo inventato noi.
ReadTheDocs controlla l’impostazione “Language” del progetto ed invoca sphinx-intl
per produrre la documentazione formattata nella lingua obiettivo. Con la configurazione predefinita — che non abbiamo alterato — sphinx-intl
prevede un file po
per ogni documento di origine, che abbia lo stesso nome del documento di origine, e che risieda nella directory locale/$(LANG)/LC_MESSAGES/
.
D’altra parte, Weblate (e noi stessi) preferiamo un file po
per ogni lingua, e tenerli tutti nella stessa cartella /po
, come facciamo per il progetto software: /po/$(LANG).po
.
Per non ripetere informazione e per lasciare lavorare entrambi i sistemi nel loro modo naturale, abbiamo due insiemi di link simbolici (git li rispetta).
Per riassumere: quando viene aggiornato un file nella documentazione, lo script``runme.sh`` si occupa di:
copiare i file
rst
dal software alla documentazione;creare un nuovo file
pot
per ciascun elemento di documentazione;unire tutti i file
pot
in un unicodoc.pot
;aggiornare tutti i
doc.po
(uno per lingua) sulla base del nuovodoc.pot
;creare tutti i collegamenti simbolici:
quelli utilizzati da
sphinx-intl
in/local/$(LANG)/LC_MESSAGES/
quelli utilizzati da noi e weblate in
/po/$(LANG).po
Sicuramente potremmo scrivere tutto ciò in un Makefile, o ancora meglio includerlo nel ‘/ doc/Makefile ‘. Chissà, forse lo faremo, un giorno.
Aggiunta mancante unit test¶
Se siete interessati a contribuire allo sviluppo di Ghini, un buon modo per farlo sarebbe aiutandoci a trovare e scrittura di unit test mancante.
Una funzione ben testata è uno cui comportamento non è possibile modificare senza rompere almeno un test di unità.
Siamo tutti d’accordo che in teoria teoria e pratica corrispondono perfettamente e che uno prima scrive i test, quindi implementa la funzione. In pratica, tuttavia, pratica non corrisponde a teoria e noi abbiamo scritto test dopo scrivendo e pubblicando anche le funzioni.
In questa sezione viene descritto il processo di aggiunta di unit test per bauble.plugins.plants.family.remove_callback
.
Elementi di cui eseguire il test¶
Prima di tutto, aprire l’indice del rapporto di copertura e scegliere un file con bassa copertura.
In questo esempio, eseguire in ottobre 2015, siamo sbarcati su bauble.plugins.plants.family
, al 33%.
https://coveralls.io/builds/3741152/source?filename=bauble%2Fplugins%2Fplants%2Ffamily.py
Le prime due funzioni che hanno bisogno di prove, di edit_callback
e add_genera_callback
, includono la creazione e l’attivazione di un oggetto basandosi su una finestra di dialogo personalizzata. Ci dovrebbe davvero prima scrivere unit test per tale classe, poi torna qui.
La funzione successiva, remove_callback
, attiva anche un paio di finestre di dialogo e il messaggio, ma in forma di richiamo di una funzione che richiede l’input dell’utente tramite caselle di sì-no-ok. Queste funzioni possiamo facilmente sostituire con una funzione beffardo il comportamento.
Come provare¶
Così, dopo aver deciso cosa descrivere nello unit test, guardiamo il codice e vediamo che ha bisogno di discriminare un paio di casi:
- correttezza dei parametri
l’elenco delle famiglie non ha elementi.
l’elenco delle famiglie ha più di un elemento.
l’elenco delle famiglie ha esattamente un elemento.
- cascade
la famiglia non ha nessun generi
la famiglia ha uno o più generi
- confirm
l’utente conferma eliminazione
l’utente non confermare l’eliminazione
- deleting
tutto va bene quando si elimina la famiglia
C’è qualche errore durante l’eliminazione della famiglia
Decido che mi concentrerò solo sugli aspetti cascata e confermare. Due domande binarie: 4 casi.
dove mettere i test¶
Individuare lo script di test e scegliere la classe dove mettere i test di unità extra.
https://coveralls.io/builds/3741152/source?filename=bauble%2Fplugins%2Fplants%2Ftest.py#L273
che fare con i test disattivati
La classe
FamilyTests
contiene un test saltato, attuarlo sarà essere un po’ di lavoro perché abbiamo bisogno di riscrivere il FamilyEditorPresenter, separarla dalla FamilyEditorView e riconsiderare ciò che a che fare con la classe FamilyEditor, che credo dovrebbe essere rimosso e sostituito con una singola funzione.
le prove di scrittura¶
Dopo l’ultimo test nella classe FamilyTests, aggiungere i quattro casi che voglio descrivere, e mi assicuro che falliscono, e dato che io sono pigro, scrivo il codice più compatto so per la generazione di un errore:
def test_remove_callback_no_genera_no_confirm(self):
1/0
def test_remove_callback_no_genera_confirm(self):
1/0
def test_remove_callback_with_genera_no_confirm(self):
1/0
def test_remove_callback_with_genera_confirm(self):
1/0
Una prova, passo dopo passo¶
Cominciamo con il primo test case.
Durante la scrittura di test, seguono in genere il modello:
T₀ (condizione iniziale),
- action,
T₁ (test il risultato dell’azione data le condizioni iniziali)
la ragione di un nome — test unitari
C’è una ragione perché unit test sono chiamati unit test. Si prega di test mai due azioni in un test.
Quindi cerchiamo di descrivere T₀ per il primo test, un database che tiene una famiglia senza generi:
def test_remove_callback_no_genera_no_confirm(self):
f5 = Family(family=u'Arecaceae')
self.session.add(f5)
self.session.flush()
Non vogliamo che la funzione in fase di test per richiamare la funzione interattiva utils.yes_no_dialog
, remove_callback
vogliamo di richiamare una funzione di sostituzione non interattiva. Raggiungiamo questo obiettivo semplicemente facendo punto di utils.yes_no_dialog
in un’espressione lambda
che, come l’originale funzione interattiva, accetta un parametro e restituisce un valore booleano. In questo caso: False
:
def test_remove_callback_no_genera_no_confirm(self):
# T_0
f5 = Family(family=u'Arecaceae')
self.session.add(f5)
self.session.flush()
# action
utils.yes_no_dialog = lambda x: False
from bauble.plugins.plants.family import remove_callback
remove_callback(f5)
Successivamente abbiamo il risultato del test.
Beh, non vogliamo solo testare o meno l’oggetto è stato eliminato Arecaceae, ci dovremmo prova anche il valore restituito da ‘remove_callback ‘, e se yes_no_dialog
e message_details_dialog
sono stati richiamati o non.
Un’espressione lambda
non è sufficiente per questo. Facciamo qualcosa di apparentemente più complesso, che renderà la vita molto più facile.
Innanzitutto definiamo una funzione piuttosto generica:
def mockfunc(msg=None, name=None, caller=None, result=None):
caller.invoked.append((name, msg))
return result
e possiamo importare partial
dal modulo standard functools
, applicarlo parzialmente alla mockfunc
, lasciando solo msg
non vincolato, ed applicare ed utilizzare questa applicazione parziale, che è una funzione che accetta un parametro e restituisce un valore, per sostituire le due funzioni in utils
. La funzione di test ora assomiglia a questo:
def test_remove_callback_no_genera_no_confirm(self):
# T_0
f5 = Family(family=u'Arecaceae')
self.session.add(f5)
self.session.flush()
self.invoked = []
# action
utils.yes_no_dialog = partial(
mockfunc, name='yes_no_dialog', caller=self, result=False)
utils.message_details_dialog = partial(
mockfunc, name='message_details_dialog', caller=self)
from bauble.plugins.plants.family import remove_callback
result = remove_callback([f5])
self.session.flush()
La sezione test controlla che message_details_dialog
non è stato richiamato, che yes_no_dialog
è stato richiamato, con il parametro di messaggio corretto, che Arecaceae è ancora lì:
# effect
self.assertFalse('message_details_dialog' in
[f for (f, m) in self.invoked])
self.assertTrue(('yes_no_dialog', u'Are you sure you want to '
'remove the family <i>Arecaceae</i>?')
in self.invoked)
self.assertEquals(result, None)
q = self.session.query(Family).filter_by(family=u"Arecaceae")
matching = q.all()
self.assertEquals(matching, [f5])
E così via.¶
‘ci sono due tipi di persone, coloro che completano quello che iniziano e così via’
Prossimo test è quasi la stessa, con la differenza che il utils.yes_no_dialog
deve restituire True
(questo raggiungiamo specificando result=True
nell’applicazione parziale della mockfunc
generica).
Con questa azione, il valore restituito da remove_callback
dovrebbe essere True
, e non ci dovrebbe essere nessuna famiglia Arecaceae nel database più:
def test_remove_callback_no_genera_confirm(self):
# T_0
f5 = Family(family=u'Arecaceae')
self.session.add(f5)
self.session.flush()
self.invoked = []
# action
utils.yes_no_dialog = partial(
mockfunc, name='yes_no_dialog', caller=self, result=True)
utils.message_details_dialog = partial(
mockfunc, name='message_details_dialog', caller=self)
from bauble.plugins.plants.family import remove_callback
result = remove_callback([f5])
self.session.flush()
# effect
self.assertFalse('message_details_dialog' in
[f for (f, m) in self.invoked])
self.assertTrue(('yes_no_dialog', u'Are you sure you want to '
'remove the family <i>Arecaceae</i>?')
in self.invoked)
self.assertEquals(result, True)
q = self.session.query(Family).filter_by(family=u"Arecaceae")
matching = q.all()
self.assertEquals(matching, [])
Date un’occhiata al commit 734f5bb9feffc2f4bd22578fcee1802c8682ca83 per le altre due funzioni di prova.
Prova registrazione¶
I nostri oggetti di bauble.test.BaubleTestCase
utilizzano handler
della classe bauble.test.MockLoggingHandler
. Ogni volta che viene avviato un singolo unit test, il metodo setUp
creerà un nuovo handler
e lo associa al logger radice. Il metodo tearDown
si prende cura di rimuoverlo.
È possibile controllare per la presenza di messaggi di registrazione specifici in self.handler.messages
. messages
è un dizionario, inizialmente vuoto, con due livelli di indicizzazione. Prima il nome del logger che rilascia la registrazione, quindi il nome del livello del record di registrazione. Le chiavi vengono create quando necessario. I valori contengono elenchi di messaggi formattati secondo qualsiasi formattatore si associa al gestore, per difetto logging.Formatter("%(message)s")
.
È possibile svuotare in modo esplicito i messaggi raccolti richiamando self.handler.clear()
.
Coordinamento¶
Di volta in volta si desidera attivare la classe di test che sta lavorando a:
nosetests bauble/plugins/plants/test.py:FamilyTests
E alla fine del processo si desidera aggiornare le statistiche:
./scripts/update-coverage.sh
Struttura dell’interfaccia utente¶
L’interfaccia utente è costruita secondo la modello — vista — Presenter modello architettonico. Per gran parte dell’interfaccia, modello è un oggetto di database di SQLAlchemy, ma ci sono anche gli elementi dell’interfaccia dove non esiste un corrispondente modello di database. In generale:
Il vista è descritto come parte di un file glade. Ciò dovrebbe includere il segnale-callback e associazioni ListStore-TreeView. Basta utilizzare la classe base
GenericEditorView
definito inbauble.editor
. Quando si crea l’istanza di questa classe generica, passarlo il glade nome del file e il nome del widget di radice, quindi consegnare questa istanza per il presentatore costruttore.Nel file glade, nella sezione
action-widget
chiusura tua descrizione oggetto GtkDialog, assicurarsi che ogni elemento diaction-widget
ha un valore validoresponse
. Utilizza valori validi GtkResponseType, ad esempio:- GTK_RESPONSE_OK, -5
- GTK_RESPONSE_CANCEL, -6
- GTK_RESPONSE_YES, -8
- GTK_RESPONSE_NO, -9
Non c’è un modo facile per scrivere prove unitarie per una sottoclasse vista, cui si prega non derivare viste, davvero non ce n’è alcun bisogno.
Nel file glade, ogni widget input deve definire quale gestore viene attivato il segnale. La classe generica di Presenter offre callback generici che coprono il maggior parte dei casi.
GtkEntry (voce di testo a riga singola) gestirà il segnale
changed
, conon_text_entry_changed
oon_unique_text_entry_changed
.GtkTextView: associarla a un GtkTextBuffer. Per gestire il segnale
changed
sulla GtkTextBuffer, dobbiamo definire un gestore che richiama il genericoon_textbuffer_changed
, l’unico ruolo per questa funzione è quello di passare il nostro gestore generico il nome dell’attributo modello che riceve il cambiamento. Si tratta di un workaroud per un bug irrisolto in GTK.GtkComboBox con testi tradotti non può essere facilmente gestito dal file glade, così non ci proviamo. Utilizzare il metodo
init_translatable_combo
della classeGenericEditorView
generica, ma si prega di richiamarlo dal presentatore.
Il modello è solo un oggetto con attributi noti. In questa interazione, la modello è solo un contenitore di dati passiva, che non si fa nulla di più che lasciare che la presentatore modificarlo.
La sottoclasse Presenter definisce e implementa:
‘widget_to_field_map ‘, un dizionario associando i nomi widget a nome degli attributi di modello,
‘view_accept_buttons ‘, l’elenco dei nomi di widget che, se attivato dall’utente, significa che la vista deve essere chiusa,
tutti gli accessori necessari i callback,
Facoltativamente, essa svolge la modello ruolo, troppo.
Il presentatore aggiorna continuamente il modello in base alle modifiche nella vista. Se la modello corrisponde a un oggetto di database, la presentatore impegna tutti modello aggiornamenti al database quando il vista è chiuso correttamente, o il rollback se la vista viene annullata. (questo comportamento è influenzato dal parametro
do_commit
)Se il modello è un’altra cosa, il presentatore farà qualcos’altro.
Nota
Un presentatore ben educato utilizza la API della vista per interrogare i valori inseriti dall’utente o per impostare lo stato dei widget. Per favore non imparare dalla pratica dei nostri presentatori anomali, alcuni dei quali direttamente gestire campi di
view.widgets
. Così facendo, questi presentatori ci impedisce di scrivere unit test.
La classe base per il presentatore, GenericEditorPresenter
definito in bauble.editor
, implementa molte utili callback generici. C’è una classe di MockView
, che è possibile utilizzare durante la scrittura di test per il tuo presentatori.
Esempi¶
Contact
e ContactPresenter
sono implementati seguendo le linee di cui sopra. La visualizzazione è definita nel file contact.glade
.
Un buon esempio di modello di visualizzazione/relatore (nessun modello) è dato dalla gestione connessione.
Usiamo lo stesso schema architettonico per l’interazione non di database, impostando il presentatore anche come modello. Facciamo questo, ad esempio, per la finestra di dialogo Esporta JSON. Il seguente comando vi darà un elenco di istanze di GenericEditorView
:
grep -nHr -e GenericEditorView\( bauble
Tavolo allungabile Ghini con plugin¶
Quasi tutto di Ghini è estensibile tramite plugin. Plugin può creare tabelle, definire ricerche personalizzate, aggiungere voci di menu, creare comandi personalizzati e altro ancora.
Per creare un nuovo plugin è necessario estendere la classe bauble.pluginmgr.Plugin
.
Il plugin Tag
è un buon esempio minimo, anche se il TagItemGUI
rientra il pattern architetturale Model-View-Presenter.
Struttura del plugin¶
Ghini è un framework per la gestione delle collezioni e viene distribuito insieme a una serie di plugin rendendo Ghini un manager collezione botanica. Ma Ghini rimane un quadro e si potrebbe in teoria rimuovere tutti i plugin distribuiamo e Scrivi la tua, o scrivere il proprio plugin che estendono o completare il comportamento attuale di Ghini.
Una volta che avete selezionato e aperto una connessione al database, si terra nella finestra ricerca. La finestra di ricerca è un’interazione tra due oggetti: SearchPresenter (SP) e SearchView (SV).
SV è quello che vedete, SP detiene lo status di programma e gestisce le richieste che si esprimono attraverso SV. Gestire queste richieste influenzano il contenuto di SV e lo stato del programma in SP.
I risultati di ricerca illustrati nella parte più larga della SV sono righe, oggetti che sono istanze di classi registrate in un plugin.
Ciascuna di queste classi deve implementare un preciso insieme di funzioni per poter correttamente operare all’interno di Ghini. Ghini riserva spazio alle classi dei plugin.
SP sa di tutte le classi registrate (contenute nei plugin), sono memorizzate in un dizionario, che associa ciascuna classe alla relativa implementazione di plugin. SV ha uno slot (un gtk.Box
) dove è possibile aggiungere elementi. In qualsiasi momento, al massimo un solo un elemento nello slot è visibile.
Un plugin definisce una o più classi di plugin. Una classe di plugin svolge il ruolo di un presentatore parziale (pP - plugin presenter) come implementare i callback necessari per la visualizzazione parziale associata raccordo nello slot (pV - plugin vista), e il modello MVP è completato dal presentatore padre (SP), ancora una volta funge da modello. Vediamo di riassumere e completare:
SP funge da modello,
la visualizzazione parziale di pV è definita in un file glade.
i callback implementati da pP sono nominati e riferiti nel file glade.
un menu di scelta rapida per la riga di SP,
una proprietà dipendente.
Quando si registra una classe di plugin, la SP:
aggiunge il pV nello slot e lo rende invisibile.
aggiunge un’istanza di pP nelle classi plugin registrati.
informa il pP che il modello è SP.
collega tutte le richiamate dal pV a pP.
Quando un elemento in pV genera un’azione in pP, pP può inoltrare l’azione a SP e può richiedere che SP aggiorni il modello e di conseguenza la vista.
Quando l’utente seleziona una riga in SP, SP nasconde tutto nello slot pluggable e mostra solo il pV corrispondente al tipo della riga selezionata e chiede al pP di aggiornare il pV con tutto ciò che è relativo alla riga selezionata.
Oltre a impostare la visibilità del pV vari, niente deve essere disabilitato né rimosso: un pV invisibile non può innescare eventi!
flusso di lavoro per risolvere bug¶
flusso di lavoro in condizioni normali¶
durante l’utilizzo del software, è possibile notare un problema, o si ottiene un’idea di qualcosa che potrebbe essere meglio, pensate a questo proposito abbastanza buono per avere un’idea molto chiara di quello che realmente è, che notato. si apre un problema e descrive il problema. qualcuno potrebbe reagire con sentori.
si apre il sito di problemi e scegliere uno che si vuole affrontare.
assegnare il problema a se stessi, in questo modo che si è informare il mondo che avete intenzione di lavorare a questo. qualcuno potrebbe reagire con sentori.
Facoltativamente Forkare il repository nel tuo account e preferibilmente creare un ramo, chiaramente associato al problema.
scrivere unit test e loro si impegnano a tua filiale (per favore non spingere in mancanza di unit test per github, eseguire prima la
nosetests
localmente).scrivere più unit test (idealmente, le prove formano la descrizione completa della funzionalità aggiunta o correzione).
Assicurarsi che la funzionalità che si desidera aggiungere o correggere è davvero completamente descritto dagli unit test che hai scritto.
Assicurarsi che gli unit test sono atomici, cioè verificare variazioni sui cambiamenti lungo una singola variabile. non dare input complesso di unit test o test che non si adattano su uno schermo (25 righe di codice).
scrivere il codice che effettua i test riescono.
aggiornare i file i18n (Esegui
./scripts/i18n.sh
).Quando possibile, tradurre le nuove stringhe che metti nei file di codice o radura.
quando cambi qualche stringa, assicurati che le sue vecchie traduzioni vengano ancora utilizzate.
confermare le modifiche.
spingere a github.
aprire una richiesta di pull.
pubblicazione in produzione¶
Aprire il pull richiesta pagina utilizzando come base una linea di produzione
ghini-x.y
, rispetto alghini-x.y-dev
.Assicurarsi che un commit
bump
è incluso nelle differenze.dovrebbe essere possibile unire automaticamente i rami.
creare la nuova richiesta di pull, lo chiamano come “pubblica la linea di produzione”.
Forse devi aspettare per travis-ci eseguire i controlli.
unire le modifiche.
raccontare al mondo: su facebook, il gruppo google, linkedin,...
Fase di¶
Scrivi una recensione su questo flusso di lavoro. considerare questo come una linea guida, a voi stessi e ai vostri colleghi. si prega di contribuire a rendere migliore e la pratica di corrispondenza.