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:

  1. copiare i file rst dal software alla documentazione;

  2. creare un nuovo file pot per ciascun elemento di documentazione;

  3. unire tutti i file pot in un unico doc.pot;

  4. aggiornare tutti i doc.po (uno per lingua) sulla base del nuovo doc.pot;

  5. creare tutti i collegamenti simbolici:

    1. quelli utilizzati da sphinx-intl in /local/$(LANG)/LC_MESSAGES/

    2. 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 modellovistaPresenter 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 in bauble.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 di action-widget ha un valore valido response. 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, con on_text_entry_changed o on_unique_text_entry_changed.

    • GtkTextView: associarla a un GtkTextBuffer. Per gestire il segnale changed sulla GtkTextBuffer, dobbiamo definire un gestore che richiama il generico on_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 classe GenericEditorView 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 al ghini-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.