Source

transifex-docs / docs / plaintext / server / contributing.txt

Full commit
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
.. _contributing:

=========================
Contributing to Transifex
=========================

Thanks for considering donating to the development of Transifex and our common goal of promoting, supporting, and advancing the Transifex framework.

Donating some of your time is the best support you could provide. Spreading a good word about Transifex is also very much appreciated. If you're into coding, you can help us fix bugs and extend Transifex's functionality. Great ways to contribute also include writing documentation and contributing translations of the software.

We're passionate about helping Transifex users make the jump to contributing members
of the community, so there are many ways you can help Transifex's development:

* Report bugs and request features in our `ticket tracker`_.  Please read
  `Reporting bugs`_, below, for the details on how we like our bug reports
  served up.

* Submit patches for new and/or fixed behavior.  Please read `Submitting
  code`_, below, for details on how to submit a patch.

* Join the `transifex-devel`_ mailing list and share your ideas for how
  to improve Transifex.  We're always open to suggestions, although we're
  likely to be skeptical of large-scale suggestions without some code to
  back it up.

* Talk to us on IRC, on the Freenode network in the #transifex channel.

* Provide :ref:`monetary donations <donations>` to keep the lights on!


.. _reporting-bugs:

Reporting bugs
==============

Well-written bug reports are *incredibly* helpful. However, there's a certain
amount of overhead involved in working with any bug tracking system, so your
help in keeping our ticket tracker as useful as possible is appreciated.  In
particular:

* **Do** read the :ref:`FAQ <faq-index>` to see if your issue might be a well-known question.

* **Do** `search the tracker`_ to see if your issue has already been filed.

* **Do** ask on `transifex-devel`_ *first* if you're not sure if what you're
  seeing is a bug.

* **Do** write complete, reproducible, specific bug reports. Include as
  much information as you possibly can, complete with code snippets, test
  cases, etc. This means including a clear, concise description of the
  problem, and a clear set of instructions for replicating the problem.
  A minimal example that illustrates the bug in a nice small test case
  is the best possible bug report.

* **Don't** use the ticket system to ask support questions.  Use the
  `transifex-devel`_ list, or the `#transifex`_ IRC channel for that.

* **Don't** use the ticket system to make large-scale feature requests.
  We like to discuss any big changes to Transifex's core on the `transifex-devel`_
  list before actually working on them.

* **Don't** reopen issues that have been marked "wontfix". This mark means
  that the decision has been made that we can't or won't fix this particular
  issue.  If you're not sure why, please ask on `transifex-devel`_.

* **Don't** use the ticket tracker for lengthy discussions, because they're
  likely to get lost. If a particular ticket is controversial, please move
  discussion to `transifex-devel`_.

* **Don't** post to transifex-devel just to announce that you have filed
  a bug report. All the tickets are mailed to another list
  (`transifex-updates`_), which is tracked by developers and triagers, so we
  see them as they are filed.


.. submitting-patches:
.. submitting-code:

Submitting code
===============

We're always grateful for patches to Transifex's code. Indeed, bug reports with
associated patches will get fixed *far* more quickly than those without patches.

If this is the first time you are contributing code, you'll need to sign the
:ref:`Transifex CLA <cla>`.


.. _coding-style:

Coding style
------------

Here are some notes on the common style we use for stuff landing in Transifex.

* Code follows the guidelines described in the standard Python Coding Style
  of PEP-8_. Docstrings follow PEP-257_.

* Use the following style (indented, auto-concat) when writing long strings::

    def lorem_ipsum():
       description = _(
           "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas "
           "nisl leo, suscipit sed, elementum ac, condimentum eget, augue. "
           "Pellentesque consequat posuere metus. Curabitur scelerisque faucibus "
           "massa. Vestibulum ut nisl ut leo cursus commodo."
       )

* Model filtering should be in the form::

    Module.query.filter_by(name='foo')

  and *not*::

    Module.query.filter(Module.name=='foo')


Committing
----------

Commit messages should have a short first line and optionally more information
after a blank newline::

    Short (50 chars or less) summary of changes (#ticket)

    More detailed explanatory text can follow after a blank line, if
    necessary. Wrap to about 72 characters or so.

    If there is a ticket associated, include it in the summary line. Prefix
    line with "bugfix:" and "trivial:" for bugfixes and trivial commits.

     - Bullet points are okay, too. Typically a hyphen or asterisk is used
       for the bullet, preceded by a single space, with blank lines between
       items.

     - Use a hanging indent, like above.

Setup Hints for Common Editors
------------------------------

Configuring VIM for Transifex Code
::::::::::::::::::::::::::::::::::

To configure vim for editing Transifex code, make sure that it sets up
the indentation level to 4 columns when editing Python code, that it
expands ASCII TAB (code: 9) characters to spaces, and wraps text at 72
columns.

To enable these options only for ``*.py`` files, you can add the following
options to your ``~/.vimrc`` file::

    if !exists("format_tx_python")
      let format_tx_python = 1

      " Formatting Python code for Transifex.
      autocmd BufNewFile,BufRead *.py set autoindent showmatch
      autocmd BufNewFile,BufRead *.py set formatoptions=tcq2l
      autocmd BufNewFile,BufRead *.py set textwidth=72 shiftwidth=4
      autocmd BufNewFile,BufRead *.py set softtabstop=4 tabstop=8 expandtab
    endif

Configuring Emacs for Transifex Code
::::::::::::::::::::::::::::::::::::

To configure Emacs for editing Transifex code, you can set up a hook
function that runs whenever ``python-mode`` is enabled.  Setting up the
column for wrapping text, and the use of only ASCII SPACE characters for
indentation is easy.  Just add the following Emacs Lisp snippet in your
Emacs startup file::

    ;; Formatting Python code for Transifex.
    (defun local/python-mode-options ()
      "My local configuration tweaks for `python-mode'."
      (setq fill-column 72		;text wrap column
            python-indent 4		;use 4-column indentation for Python
            indent-tabs-mode nil	;use only SPC for indentation
            next-line-add-newlines nil)) ;don't add newlines at end-of-file

    ;; When python-mode loads, call our hook function.
    (eval-after-load "python"
      '(add-hook 'python-mode 'local/python-mode-options))


Changing the model
------------------

As with all software involving a database, changes to Transifex's models should
be handled with care. Production instances of Transifex rely on the stability
of the model when upgrading, and a safe upgrade path should be given to
everyone currently running a Transifex instance.

For this reason we usually discuss model changes on the mailing list,
especially when they're big ones.

New model fields should follow the style of the current fields and include
any help texts attributes to them, if needed.

A commit which changes the models needs to include fixtures that work with the
new model. We already have some sample data for folks to try out in
``txcommon/fixtures/sample_data.json``.

Typical model change scenario
:::::::::::::::::::::::::::::

When model modifications are introduced, the following steps should be followed
to have the fixture updated:

1. Flush, create tables and load data::

     ./manage.py flush
     ./manage.py syncdb
     ./manage.py loaddata txcommon/fixtures/sample_data.json

2. Make changes to the models.
3. Probably a migration will be required.

   Starting with Release 0.7, Transifex uses South for managing the schema
   migrations of models. To get the migration script related to your model
   change run::

     ./manage.py startmigration <app_name> <migration_name> --auto

   Note that a new python script with the <migration_name> will be created under
   the <app_name>/migrations/ dir. As it's an automatic detection, please, have
   a look at it to verify that it is accurately reflects your changes.
   After reviewing it, you can test it running::

     ./manage.py migrate <app_name> --db-dry-run

   In complex changes the South application might not be able to proceed
   automatically (for example when adding a non-null column). In this case you can
   delete the column and re-add it, add it as null and change it, etc. South
   also supports to manual migrations and more information can be found
   at http://south.aeracode.org/wiki/Documentation.

   Once your migration passes through the above command without errors, you can
   proceed with the migration in the database by omitting the option --db-dry-run::

      ./manage.py migrate <app_name>

4. With the new DB schema in place, dump the data again::

     ./manage.py dumpdata --indent=2 projects releases codebases \
     tarball vcs sites > txcommon/fixtures/sample_data.json

5. Try the new DB fixture::

     ./manage.py flush --noinput
     ./manage.py syncdb --noinput
     ./manage.py loaddata txcommon/fixtures/sample_data.json
     ./manage.py loaddata txcommon/fixtures/sample_users.json

   Open up your browser to check that everything looks OK and that your changes
   have been included as you expected.

6. Update the documentation if needed. Commit all changes together.


Updating translation files (PO files)
-------------------------------------

To extract new messages from the source code and update the PO files of
Transifex it is necessary to run the following command and then commit the
changes::

    ./manage.py txmakemessages --all


.. _add-new-handler:

How to add support for a new file format
----------------------------------------

This is a small tutorial to show you how to write a handler for a new file format
in Transifex. This covers most of the basics to get you started with writing
new file handlers. However, for more details, you can always go through the source
code of the file handlers already available in Transifex. They can be found in
the directory 

* transifex/resources/formats/

of Transifex's source code.

Sample file
:::::::::::

We'll write a sample handler to parse and compile the file shown below.

.. code-block:: txt

    KEY1=Hello, world
    KEY2=This contains special chars \n \t " '


Sample Handler code
::::::::::::::::::::

You can use the sample code below as a base template to start writing a handler
for a new file format. We'll write a new file
    
*transifex/resources/formats/xyz.py

with the content below. The code below is accompanied with detailed comments to
help you understand better.

.. code-block:: python

    # -*- coding: utf-8 -*-
    import re, codecs

    from transifex.resources.models import SourceEntity
    from transifex.resources.formats.utils.decorators import *
    from transifex.resources.formats.utils.hash_tag import hash_tag
    from transifex.resources.formats.core import Handler, ParseError, CompileError
    from transifex.resources.formats.compilation import Compiler,\
                SimpleCompilerFactory


    class SampleParseError(ParseError):
        pass


    class SampleCompileError(CompileError):
        pass


    class SampleCompiler(Compiler):
        def _compile(self, content):
            """Internal compile function.

            Subclasses must override this method, if they need to change
            the compile behavior.

            Args:
                content: The content (template) of the resource.
            """
            
            """In our case, we won't need to customize this method.
            The default implementation does the job of replacing
            md5 patterns in the template with the corresponding
            translations."""
            super(SampleCompiler, self)._compile(content)

        def _post_compile(self, content=None):
            """Do any work after the compilation process."""
            super(SampleCompiler, self)._post_compile(content)



    class SampleHandler(SimpleCompilerFactory, Handler):
        """Some info and properties for the handler."""
        name = 'Sample handler'
        format = "Sample handler .xyz (*.xyz)"

        #method_name uniquely identifies this handler among other handlers
        method_name = 'XYZ'
        format_encoding = 'UTF-8'

        """Bind the error handlers"""
        HandlerCompilerError = SampleCompileError
        HandlerParseError = SampleParseError

        """Bind the compiler class to the Handler"""
        CompilerClass = SampleCompiler

        def _parse(self, is_source, lang_rules):
            """The actual functions that parses the content.

            Formats need to override this to provide the desired behavior.

            Two stringsets are available to subclasses:
            - self.stringset to save the translated strings
            - self.suggestions to save suggested translations

            Args:
                is_source: Flag to determine if this is a source file or not.
                lang_rules: rules for the language

            Returns:
                An object which, when used as an argument in
                `self._create_template()`, the template for the resource
                is generated.

            """
            template = u""
            context = ""

            for line in self.content.splitlines():
                #split line to get key, value or source, translation pair
                #In this case, the above file format is key-value based
                key, value = line.split('=', 1)
                
                """
                During importing source files, we have to generate a template
                in which values/translations are replaced with an md5 pattern.
                This template is used during compiling the file for download.
                We generate a unique md5 pattern for (key/source string, context)
                pair.
                """
                if is_source:
                    """
                    During importing source file,
                    generate an md5 pattern and replace the value only if
                    value has some useful data (and not just whitespaces).
                    Else, save the line as it is in the template.
                    """
                    if value.strip():
                        template += key + '=' + '%s_tr' % hash_tag(key, '')
                    else:
                        template += line
                elif not value.strip():
                    """Similarly, during importing a translation file, if
                    value doesn't contain any useful data like above,
                    then move to the next line."""
                    continue

                """This adds the (key, value, context) to self.stringset
                to be saved to the database."""
                self._add_translation_string(key, value, context=context)

            return template


        def _escape(self, s):
            """Escaping during compilation"""
            """Let's say we'll escape \n and \t"""
            return (s.replace('\n', r'\n')
                     .replace('\t', r'\t')
            )

    
        def _unescape(self, s):
            """Unescaping durng importing"""
            """Let's say we'll unescape \\n and \\t"""
            return (s.replace(r'\t', '\t')
                     .replace(r'\n', '\n')
            )


Integrate SampleHandler with Transifex
::::::::::::::::::::::::::::::::::::::

1. In transifex/settings/70-translation.conf, add the following to 
I18N_METHODS dictionary:

.. code-block:: python

    I18N_METHODS = {
        ...
        ...
        ...
        'XYZ': {
            """This will appear in the i18n types selection menu"""
            'description': 'XYZ file',
            'mimetype': 'text/plain',
            'file-extensions': '.xyz'
        },
        
    }

2. Again in transifex/settings/70-translation.conf, we have to add the
following to I18N_HANDLER_CLASS_NAMES dictionary:

.. code-block:: python

    I18N_HANDLER_CLASS_NAMES = {
        ...
        ...
        ...
        'XYZ': 'transifex.resources.formats.xyz.SampleHandler',
    }

And we are done! Now, we restart the server (Django test server or whatever). Now,
we'll be able to see "XYZ file" in the list of supported i18n types during
resource creation. When we create our resource with the above sample file and
"XYZ file" chosen as i18n type, the following template will be stored in the
database for use during compilation.

.. code-block:: txt

    KEY1=50311154357828e0ad520eace59ef0e5_tr
    KEY2=122ea8bfd5e1babcc99eca728985bc6b_tr

Let's say, we have translated a string "Hello, world" in Portuguese as "Olá mundo,".
When the Portuguese translation file is downloaded, the file will look like:

.. code-block:: txt

    KEY1=Olá mundo,
    KEY2=

During the compilation process, the md5 patterns were replaced by their
corresponding translations, 'Olá mundo,' for KEY1 and '' for KEY2.


Various Development Tips
========================

Useful URLS
-----------

Transifex:

- http://help.transifex.net/technical/contributing.html
- http://help.transifex.net/
- http://code.indifex.com/
- http://code.indifex.com/transifex
- http://code.indifex.com/transifex-docs
- http://code.indifex.com/transifex-client

Python & Django:

- http://docs.djangoproject.com/
- http://www.djangobook.com/
- http://diveintopython.org/


~/.bashrc
---------

::

    # User specific aliases and functions
    bind '"\e[A"':history-search-backward # first letters of command + up/down
    bind '"\e[B"':history-search-forward

    alias mg="python manage.py"
    alias ll='ls -l --color=tty'

    export WORKON_HOME=~mits/devel/env/
    source /usr/bin/virtualenvwrapper.sh

    HISTSIZE=20000
    HISTFILESIZE=999999
    shopt -s histappend


~/.hgrc
-------

::

    [paths]
    txod = ssh://hg@bitbucket.org/indifex/transifex
    mytxod = ssh://hg@bitbucket.org/indifex/transifex-myusername-devel

    [ui]
    username = Dimitris Glezos <glezos@indifex.com>

    [smtp]
    host = mailgate.otenet.gr

    [extensions]
    children =
    churn =
    color =
    extdiff =
    fetch =
    graphlog =
    mq =
    parentrevspec =
    patchbomb =
    progress =
    purge =
    rebase =
    record =
    transplant =


Useful packages
---------------

- bash-completion
- xchat-gnome
- ipython, python-nose
- skype, pidgin
- mercurial, tortoisehg
- meld
- ipython

- python-virtualenv # for virtual environments. More info:
  http://trac.transifex.org/wiki/Development/InstallationOnCentOS_VirtualEnv

Minor ones:

- eclipse + pydev
- regexxer        # mass search & replace, recursive in dirs
- shutter         # screenshots
- freemind        # mind mapping for notes, brainstorming
- istanbul        # screencasts
- mgopen fonts    # Tx logo etc


~/.ssh/config
-------------

::

    Host bb
        HostName bitbucket.org
        User hg
        Compression yes


Workflow etc
------------

::

    # Create ssh key with ssh-keygen
    # Create account on Bitbucket, add SSH key
    # Fork transifex public repo on BB to transifex-myname-devel
    hg clone txod
    hg clone mytxod
    # work, commit.
    # Commit messages style: http://help.transifex.net/technical/contributing.html#commit-messages
    # To share devel/testing nodes:
    hg tip
    hg out -r tip --force mytxod
    hg push -r t gip --force mytxod
    # other person can:
    hg pull -r <node|tip> <repo>


Testing
-------

You first have to have all the necessary packages installed. Details
can be found at :ref:`install-manually-ref`.

Then, give the command::

    cd transifex/transifex

to enter the directory with the transifex source code.

You can test any app and addon with::

    ./manage.py test <app_name>

If you want to execute a specific test case for the app, you can use::

    ./manage.py test <app_name>.<TestCase>

For example, to test the model for the resources app (the test case is
in ``transifex/resources/tests/models.py``), you should use the
command::

    ./manage.py test resources.ResourcesModelTests

For details, see https://docs.djangoproject.com/en/1.2/topics/testing/.

A standard way to work on a branch is::

    hg commit, commit, commit
    hg pull -u
    hg rebase -b <one of my commits> -d tip

You can collapse all commits in one by using ``hg rebase --collapse``. To avoid the multiple merges, please look here: http://miniblog.glezos.com/post/356364079/single-merge-rebase.


Other useful tips
-----------------

- Add ``exc_info=True`` when logging from inside an exception handler
- Find string: ``find . -type f -iname '*py' | xargs grep <str>``
- Temporarily store changes to pull: ``hg shelve`` or:
  ``hg commit && hg pull -u && hg rebase``.
- Add the following lines to settings.py to create the test database
  in memory only::

    if 'test' in sys.argv:
      DATABASES = {
        'default': {
            'NAME': os.path.join(PROJECT_PATH, 'transifex.db.sqlite'),
            'ENGINE': 'django.db.backends.sqlite3',
        },
    }

- Add the following line to settings.py to skip running migrations
  for tests::

    SOUTH_TESTS_MIGRATE = False

- Insert the line::

    import ipdb; ipdb.set_trace();

  to interrupt the execution of the program at the specified
  position. See http://docs.python.org/library/pdb.html for what you
  can do then.


Cross Site Request Forgery protection
-------------------------------------

Transifex is protected against CSRF_ attacks by ensuring that GET requests are
side-effect free. This means that every action inside Transifex that has an
effect on the database, session, etc. are not being done over a GET request.
With Django's `CSRF Middleware`_ enabled, attackers are prevented from meddling
with a Transifex user's settings.

Examples of CSRF-vulnerable elements include::
 * a link which calls ``/logout`` over GET to log the user out
 * a 'pt_BR' link to change the page's language to Brazilian Portuguese
   and save this setting to the user's cookie
 * a simple AJAX request to lock a file.

Examples of safe actions include::
 * searching through the website for a string
 * pagination

If you are adding some code in Transifex which has a side-effect, always require POST.


Development Workflow Using a Patch Queue
----------------------------------------

Patches to the ``mainline`` Transifex branch can go through various iterations before they are ready to be pushed to the branch.  One way of tracking these patches without having to create multiple heads in your local clone is to use a ``patch queue`` and ``rebasing``.  Patch queues are supported by the the MQ extension of Mercurial, and rebasing with the Rebase extension can slide patches forward in history, reducing the number of merge changesets in the final history of the ``mainline`` branch.

Advantages of Using a Patch Queue
:::::::::::::::::::::::::::::::::

* **Multiple inter-dependent changes can be prepared together, without disrupting the mainline.**

  It is quite often the case that a single mega-patch is not very easy to review.  Either because it touches far too many files, or because it isn't easy to review many co-dependent changes in a single diff.  A patch queue can hold an arbitrarily large number of inter-dependent changes, stacked on top of each other, or tagged as part of logical sets of changes.  All the patches can be developed in the same patch queue, and they do not have to be committed to the ``mainline`` Transifex branch in a partially done, half-working state.

* **Patches are only pushed to ''mainline'' when they are ready.**

  The patch queue can be versioned as a separate ``patch repository``, and the patches it contains may be refined, extended, refactored or otherwise hacked at will until they have the right `smell <http://c2.com/cgi/wiki?CodeSmell>`_, until they pass all the tests, or until they look `good <http://c2.com/cgi/wiki?GoodCode>`_.  Then all the patches can be pushed to the ''mainline'' in one go.  This may be a slightly better way of working on larger changes that cannot be committed in a half-done state, because they would disrupt the work of others working on the ''mainline'', and would probably look awful if they were committed as a huge "nuclear bomb" style patch.

* **Patches can be easily reviewed before hitting the mainline.**

  It is trivial to extract patches from the patch queue and make them available for review.  In-progress work that lives in a patch queue does not have to be committed to the ``mainline`` branch for review, but it may be emailed, copied to a pastebin, and so on.  When a reviewer replies, then it is also easy to update a patch while it is still in the patch queue, adding more changes, removing some of the existing changes, or even replacing the entire patch with a new, improved version.  Then the review process may start again with the "refreshed" state of the patch.

  All this can be done without necessarily committing new changes to the mainline.  The history of the patches themselves can be pushed online though, including comments about who, when, why updated the patches and other information pertinent to the review process.  (A patch queue can be a Mercurial repository itself, with commits that update the patches comprising the queue.)


Disadvantages of Using a Patch Queue
::::::::::::::::::::::::::::::::::::

* **Parts of the history are gone when rebasing.**

  When history is rewritten by rebasing a changeset, parts of the old change history are gone forever.  After a patch has been rebased on top of a new 'tip' of the mainline branch, it is not very easy to go back and see how things looked before the rebase.  If it turns out that the new state of your local clone is utterly broken after a rebase, the usefulness of bug hunting extensions like ``bisect`` is slightly reduced.

  If you are working with several people in the same set of patches, it may be useful some times to ''keep'' the extra merges in a "feature clone" of the mainline, until they are ready to be committed to the mainline repository.

* **Patch history moves to the patch queue repository.**

  A patch queue can be initialized as a repository of its own, using the ``qinit -c`` option.  This way each patch is ''versioned'' and may be committed as a normal text file inside the ``.hg/patches`` repository of the clone you are working with.  But this also means that then you have a nested Mercurial repository in ``.hg/patches``, with a fileset of its own, and a separate history of its own.  Looking at the history of the Transifex source will only show the set of patches you have applied, i.e.::

    keramida@foo:/hg/transifex/tx-bsd$ hg qapplied -s
    patch-docs-txcreatedirs: docs: suggest 'txcreatedirs' before 'syncdb' when installing
    patch-git-repo-error: Transplant change 7dc79c18070b to the git vcs type too.
    patch-docs-platform: Add platform specific notes for Tx developers
    patch-txcreatedirs-dircheck: txcreatedirs: check if a directory exists before trying to create it

  But this does now show how many iterations each patch has gone through, who changed it, why, and so on.  Some times, this sort of history is a valuable tool about the way a feature was developed, but this history is "hidden" in the commits of the ``.hg/patches`` queue repository.

* **Tracking diffs for diffs may be a bit confusing.**

  A patch queue can be initialized as a repository of its own, using the ``qinit -c`` option.  This way each patch is ''versioned'' and may be committed as a normal text file inside the ``.hg/patches`` repository of the clone you are working with.  But this also means that then you have a nested Mercurial repository in ``.hg/patches``, with a fileset of its own, a separate history of its own, and that browsing changesets in the patch repository shows ``diffs of diffs``.  This may turn out to be a bit confusing; at least until you get used to tracking Transifex patches as separate versionable entities.

  A sample "diff of a diff" looks like this::

    keramida@foo:/hg/transifex/tx-bsd$ hg -R .hg/patches export 0
    diff -r 000000000000 -r e8c17ca9c40b patch-docs-platform
    --- /dev/null   Thu Jan 01 00:00:00 1970 +0000
    +++ b/patch-docs-platform       Tue Mar 24 09:53:39 2009 +0200
    @@ -0,0 +1,18 @@
    +Add platform specific notes for Tx developers
    +
    +Transifex installation notes for FreeBSD are part of this change.  Some
    +bits are missing from FreeBSD to make Transifex work `out of the box',
    +so write about these too.
    +
    +diff --git a/docs/development/index.txt b/docs/development/index.txt
    +--- a/docs/development/index.txt
    ++++ b/docs/development/index.txt
    +@@ -7,4 +7,6 @@
    + .. toctree::
    +    :maxdepth: 2
    +
    +-   contributing
    +\ No newline at end of file
    ++   contributing
    ++
    ++   platform

  Note how the last 12 lines of this diff contain a ''nested'' patch.  This is the actual patch that applies on top of the Transifex source.  The commit we are looking at is a ''patch queue'' commit: the change that added a new Transifex patch to the queue repository.  There are tools like ``interdiff`` that can help with this sort of thing, but it may take a bit of getting used to; especially if you are going to look at the patch queue changesets very often.

Patch Queue Workflow
::::::::::::::::::::

The workflow when using a patch queue to develop local Transifex changes, until these changes are accepted for ``mainline`` or just because you want to hack your own, slightly modified version of Transifex, includes the following steps:

* Clone the ``mainline`` of Transifex
* Create and initialize a patch queue for the new clone
* Hack a bit on the Transifex code
* Save your changes in a patch
* Hack a bit more
* Save your changes again, either in the same patch or in a new patch on top of the previous one
* Commit the patch state to the nested queue repository
* Optionally, push the queue repository somewhere online, so others can review the patches you created
* Pull new changes from the Transifex mainline
* Rebase the patch queue on top of the new mainline ''tip'' changeset
* Commit the rebased state of the patches
* Optionally, push them again online, for another round of reviews
* Repeat, until the patches of the queue look satisfactory
* Send your patches to the Transifex developers (or push them directly to ``mainline`` if you already have push access)

Each one of these workflow steps is described in more detail in the following sections.

How to Clone the Mainline Branch of Transifex
:::::::::::::::::::::::::::::::::::::::::::::

The ''mainline'' branch of Transifex is open for read access to everyone.  A Mercurial repository is available at http://code.indifex.com/transifex, so cloning this repository to a local ''mainline'' copy is as easy as typing::

    % mkdir ~/work/transifex
    % cd ~/work/transifex
    % hg clone http://code.indifex.com/transifex

This should finish in a few seconds, downloading the full history of the ''mainline'' branch.  Typical output looks like this::

    % hg clone http://code.indifex.com/transifex
    requesting all changes
    adding changesets
    adding manifests
    adding file changes
    added 432 changesets with 1869 changes to 580 files
    updating working directory
    491 files updated, 0 files merged, 0 files removed, 0 files unresolved

The default behavior of ``hg clone`` is to create a local directory with the same name as the last component of the URI, so you should now have a local clone of the ''mainline'' branch in a directory called ``mainline``::

    % ls -ld mainline*
    drwxrwxr-x  19 keramida  users  1024 Mar 24 05:20 mainline

How to Pull Future Commits to the Local Mainline Clone
::::::::::::::::::::::::::::::::::::::::::::::::::::::

When ``clone`` completes successfully, it also creates a new ``mainline/.hg/hgrc`` file with a single ``default`` repository path, pointing to the source of the cloned repository::

    % cat mainline/.hg/hgrc
    [paths]
    default = http://code.indifex.com/transifex

This way you can keep pulling future changes by simply entering the local ``mainline`` clone, and running ``hg pull``::

    % cd ~/work/transifex/mainline
    % hg pull
    pulling from http://code.indifex.com/transifex
    searching for changes
    adding changesets
    adding manifests
    adding file changes
    added 2 changesets with 2 changes to 2 files
    (run 'hg update' to get a working copy)


.. _donations:

Donating money
==============

Donating some of your time is the best support you could provide. Having said
that, we also gladly accept monetary donations.

You can donate some money to the Transifex team via PayPal in a number of
ways, including credit cards. If you're not sure how much to donate, we
recommend €20 or €50, but will gladly accept donations of any size.

.. raw:: html

    <div style="text-align: center;">
    <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
    <input type="hidden" name="cmd" value="_donations">
    <input type="hidden" name="business" value="finance@indifex.com">
    <input type="hidden" name="lc" value="GB">
    <input type="hidden" name="item_name" value="One-time donation to the Transifex Project">
    <input type="hidden" name="cn" value="Comments welcome!">
    <input type="hidden" name="no_shipping" value="1">
    <input type="hidden" name="return" value="http://transifex.org/wiki/Donate/Thanks">
    <input type="hidden" name="cancel_return" value="http://transifex.org/wiki/Donate/">
    <input type="hidden" name="currency_code" value="EUR">
    <label for="amount" style="font-size: 14px;">€</label> <input name="amount" value="20.00" size="5" style="font-size: 14px; margin-right: 4px;" type="text">
    <input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="">
    <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
    </form>
    </div>


If you're in love with Transifex, you can also make a recurring donation which
will take place on a monthly basis.

.. raw:: html

    <div style="text-align: center;">
    <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
    <input type="hidden" name="cmd" value="_xclick-subscriptions">
    <input type="hidden" name="business" value="finance@indifex.com">
    <input type="hidden" name="lc" value="GB">
    <input type="hidden" name="item_name" value="Monthly donation to the Transifex Project">
    <input type="hidden" name="cn" value="Comments welcome!">
    <input type="hidden" name="no_shipping" value="1">
    <input type="hidden" name="return" value="http://transifex.org/wiki/Donate/Thanks">
    <input type="hidden" name="cancel_return" value="http://transifex.org/wiki/Donate/">
    <input type="hidden" name="currency_code" value="EUR">
    <input type="hidden" name="bn" value="PP-DonationsBF">
     <input type="hidden" name="p3" value="1" />
     <input type="hidden" name="t3" value="M" />
     <input type="hidden" name="src" value="1" />
     <input type="hidden" name="sra" value="1" />
      <select name="a3" style="font-size: 14px;">
       <option value="5.00">€5</option>
       <option value="10.00" selected="selected">€10</option>
       <option value="15.00">€15</option>
       <option value="20.00">€20</option>
       <option value="25.00">€25</option>
       <option value="30.00">€30</option>
       <option value="50.00">€50</option>
       <option value="100.00">€100</option>
       <option value="200.00">€200</option>
      </select>
    <input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="">
    <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
    </form>
    </div>

(To cancel a monthly donation, log in to your PayPal account and go to the
Profile tab. In the Financial Information column, click Recurring Payments.
Find "Transifex" and click View Payment, and then Cancel.)

How will my donation be used?
-----------------------------

Received donations are much appreciated and are used to support the community's
operations. Here are a number of ways we use donations:

- Organizing development sprints, where Transifex developers can meet
  face-to-face and online and code together.
- Helping cover the travel expenses of key developers to attend conferences
  important to the development of Transifex, such as ones where potential new
  contributors lurk.
- Helping cover hosting and domain registration costs, legal stuff, donations
  to other open source projects we use extensively, and other similar costs.

Donations are handled by Indifex, the primary sponsor and caretaker of
Transifex. We're completely open on how we use donations, and if you have any
questions about them, please don't hesitate to email us directly.



Frequently Asked Questions
==========================

I submitted a bug fix several weeks ago. Why are you ignoring my patch?
-----------------------------------------------------------------------

Don't worry: We're not ignoring you!

It's important to understand there is a difference between "a ticket is being
ignored" and "a ticket has not been attended to yet." Our ticket system
contains hundreds of open tickets, with various degrees of impact on end-user
functionality, and Transifex's developers have to review and prioritize.

On top of that: the people who work on Transifex are all volunteers. As a result,
the amount of time that we have to work on the framework is limited and will
vary from week to week depending on our spare time. If we're busy, we may not
be able to spend as much time on Transifex as we might want.

Besides, if your feature request stands no chance of inclusion in Transifex, we
won't ignore it -- we'll just close the ticket. So if your ticket is still
open, it doesn't mean we're ignoring you; it just means we haven't had time to
look at it yet.



.. _PEP-8:   http://www.python.org/dev/peps/pep-0008/
.. _PEP-257: http://www.python.org/dev/peps/pep-0257/
.. _transifex-updates: http://groups.google.com/group/transifex-updates
.. _search the tracker: http://transifex.org/search?&ticket=on
.. _`#transifex`: irc://irc.freenode.net/transifex
.. _ticket tracker: http://transifex.org/newticket
.. _transifex-devel: http://groups.google.com/group/transifex-devel
.. _CSRF: http://en.wikipedia.org/wiki/Cross-site_request_forgery
.. _`CSRF Middleware`: http://docs.djangoproject.com/en/dev/ref/contrib/csrf/