##// END OF EJS Templates
docs: fix contributing.rst reference to Session.remove()...
Mads Kiilerich -
r8192:2b8892f9 default
parent child Browse files
Show More
@@ -1,311 +1,311 b''
1 .. _contributing:
1 .. _contributing:
2
2
3 =========================
3 =========================
4 Contributing to Kallithea
4 Contributing to Kallithea
5 =========================
5 =========================
6
6
7 Kallithea is developed and maintained by its users. Please join us and scratch
7 Kallithea is developed and maintained by its users. Please join us and scratch
8 your own itch.
8 your own itch.
9
9
10
10
11 Infrastructure
11 Infrastructure
12 --------------
12 --------------
13
13
14 The main repository is hosted on Our Own Kallithea (aka OOK) at
14 The main repository is hosted on Our Own Kallithea (aka OOK) at
15 https://kallithea-scm.org/repos/kallithea/, our self-hosted instance
15 https://kallithea-scm.org/repos/kallithea/, our self-hosted instance
16 of Kallithea.
16 of Kallithea.
17
17
18 For now, we use Bitbucket_ for `pull requests`_ and `issue tracking`_. The
18 For now, we use Bitbucket_ for `pull requests`_ and `issue tracking`_. The
19 issue tracker is for tracking bugs, not for support, discussion, or ideas --
19 issue tracker is for tracking bugs, not for support, discussion, or ideas --
20 please use the `mailing list`_ or :ref:`IRC <readme>` to reach the community.
20 please use the `mailing list`_ or :ref:`IRC <readme>` to reach the community.
21
21
22 We use Weblate_ to translate the user interface messages into languages other
22 We use Weblate_ to translate the user interface messages into languages other
23 than English. Join our project on `Hosted Weblate`_ to help us.
23 than English. Join our project on `Hosted Weblate`_ to help us.
24 To register, you can use your Bitbucket or GitHub account. See :ref:`translations`
24 To register, you can use your Bitbucket or GitHub account. See :ref:`translations`
25 for more details.
25 for more details.
26
26
27
27
28 Getting started
28 Getting started
29 ---------------
29 ---------------
30
30
31 To get started with Kallithea development::
31 To get started with Kallithea development::
32
32
33 hg clone https://kallithea-scm.org/repos/kallithea
33 hg clone https://kallithea-scm.org/repos/kallithea
34 cd kallithea
34 cd kallithea
35 python3 -m venv ../kallithea-venv
35 python3 -m venv ../kallithea-venv
36 source ../kallithea-venv/bin/activate
36 source ../kallithea-venv/bin/activate
37 pip install --upgrade pip setuptools
37 pip install --upgrade pip setuptools
38 pip install --upgrade -e . -r dev_requirements.txt python-ldap python-pam
38 pip install --upgrade -e . -r dev_requirements.txt python-ldap python-pam
39 kallithea-cli config-create my.ini
39 kallithea-cli config-create my.ini
40 kallithea-cli db-create -c my.ini --user=user --email=user@example.com --password=password --repos=/tmp
40 kallithea-cli db-create -c my.ini --user=user --email=user@example.com --password=password --repos=/tmp
41 kallithea-cli front-end-build
41 kallithea-cli front-end-build
42 gearbox serve -c my.ini --reload &
42 gearbox serve -c my.ini --reload &
43 firefox http://127.0.0.1:5000/
43 firefox http://127.0.0.1:5000/
44
44
45 If you plan to use Bitbucket_ for sending contributions, you can also fork
45 If you plan to use Bitbucket_ for sending contributions, you can also fork
46 Kallithea on Bitbucket_ first (https://bitbucket.org/conservancy/kallithea) and
46 Kallithea on Bitbucket_ first (https://bitbucket.org/conservancy/kallithea) and
47 then replace the clone step above by a clone of your fork. In this case, please
47 then replace the clone step above by a clone of your fork. In this case, please
48 see :ref:`contributing-guidelines` below for configuring your fork correctly.
48 see :ref:`contributing-guidelines` below for configuring your fork correctly.
49
49
50
50
51 Contribution flow
51 Contribution flow
52 -----------------
52 -----------------
53
53
54 Starting from an existing Kallithea clone, make sure it is up to date with the
54 Starting from an existing Kallithea clone, make sure it is up to date with the
55 latest upstream changes::
55 latest upstream changes::
56
56
57 hg pull
57 hg pull
58 hg update
58 hg update
59
59
60 Review the :ref:`contributing-guidelines` and :ref:`coding-guidelines`.
60 Review the :ref:`contributing-guidelines` and :ref:`coding-guidelines`.
61
61
62 If you are new to Mercurial, refer to Mercurial `Quick Start`_ and `Beginners
62 If you are new to Mercurial, refer to Mercurial `Quick Start`_ and `Beginners
63 Guide`_ on the Mercurial wiki.
63 Guide`_ on the Mercurial wiki.
64
64
65 Now, make some changes and test them (see :ref:`contributing-tests`). Don't
65 Now, make some changes and test them (see :ref:`contributing-tests`). Don't
66 forget to add new tests to cover new functionality or bug fixes.
66 forget to add new tests to cover new functionality or bug fixes.
67
67
68 For documentation changes, run ``make html`` from the ``docs`` directory to
68 For documentation changes, run ``make html`` from the ``docs`` directory to
69 generate the HTML result, then review them in your browser.
69 generate the HTML result, then review them in your browser.
70
70
71 Before submitting any changes, run the cleanup script::
71 Before submitting any changes, run the cleanup script::
72
72
73 ./scripts/run-all-cleanup
73 ./scripts/run-all-cleanup
74
74
75 When you are completely ready, you can send your changes to the community for
75 When you are completely ready, you can send your changes to the community for
76 review and inclusion. Most commonly used methods are sending patches to the
76 review and inclusion. Most commonly used methods are sending patches to the
77 mailing list (via ``hg email``) or by creating a pull request on Bitbucket_.
77 mailing list (via ``hg email``) or by creating a pull request on Bitbucket_.
78
78
79 .. _contributing-tests:
79 .. _contributing-tests:
80
80
81
81
82 Running tests
82 Running tests
83 -------------
83 -------------
84
84
85 After finishing your changes make sure all tests pass cleanly. Run the testsuite
85 After finishing your changes make sure all tests pass cleanly. Run the testsuite
86 by invoking ``py.test`` from the project root::
86 by invoking ``py.test`` from the project root::
87
87
88 py.test
88 py.test
89
89
90 Note that on unix systems, the temporary directory (``/tmp`` or where
90 Note that on unix systems, the temporary directory (``/tmp`` or where
91 ``$TMPDIR`` points) must allow executable files; Git hooks must be executable,
91 ``$TMPDIR`` points) must allow executable files; Git hooks must be executable,
92 and the test suite creates repositories in the temporary directory. Linux
92 and the test suite creates repositories in the temporary directory. Linux
93 systems with /tmp mounted noexec will thus fail.
93 systems with /tmp mounted noexec will thus fail.
94
94
95 You can also use ``tox`` to run the tests with all supported Python versions.
95 You can also use ``tox`` to run the tests with all supported Python versions.
96
96
97 When running tests, Kallithea generates a `test.ini` based on template values
97 When running tests, Kallithea generates a `test.ini` based on template values
98 in `kallithea/tests/conftest.py` and populates the SQLite database specified
98 in `kallithea/tests/conftest.py` and populates the SQLite database specified
99 there.
99 there.
100
100
101 It is possible to avoid recreating the full test database on each invocation of
101 It is possible to avoid recreating the full test database on each invocation of
102 the tests, thus eliminating the initial delay. To achieve this, run the tests as::
102 the tests, thus eliminating the initial delay. To achieve this, run the tests as::
103
103
104 gearbox serve -c /tmp/kallithea-test-XXX/test.ini --pid-file=test.pid --daemon
104 gearbox serve -c /tmp/kallithea-test-XXX/test.ini --pid-file=test.pid --daemon
105 KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 py.test
105 KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 py.test
106 kill -9 $(cat test.pid)
106 kill -9 $(cat test.pid)
107
107
108 In these commands, the following variables are used::
108 In these commands, the following variables are used::
109
109
110 KALLITHEA_WHOOSH_TEST_DISABLE=1 - skip whoosh index building and tests
110 KALLITHEA_WHOOSH_TEST_DISABLE=1 - skip whoosh index building and tests
111 KALLITHEA_NO_TMP_PATH=1 - disable new temp path for tests, used mostly for testing_vcs_operations
111 KALLITHEA_NO_TMP_PATH=1 - disable new temp path for tests, used mostly for testing_vcs_operations
112
112
113 You can run individual tests by specifying their path as argument to py.test.
113 You can run individual tests by specifying their path as argument to py.test.
114 py.test also has many more options, see `py.test -h`. Some useful options
114 py.test also has many more options, see `py.test -h`. Some useful options
115 are::
115 are::
116
116
117 -k EXPRESSION only run tests which match the given substring
117 -k EXPRESSION only run tests which match the given substring
118 expression. An expression is a python evaluable
118 expression. An expression is a python evaluable
119 expression where all names are substring-matched
119 expression where all names are substring-matched
120 against test names and their parent classes. Example:
120 against test names and their parent classes. Example:
121 -x, --exitfirst exit instantly on first error or failed test.
121 -x, --exitfirst exit instantly on first error or failed test.
122 --lf rerun only the tests that failed at the last run (or
122 --lf rerun only the tests that failed at the last run (or
123 all if none failed)
123 all if none failed)
124 --ff run all tests but run the last failures first. This
124 --ff run all tests but run the last failures first. This
125 may re-order tests and thus lead to repeated fixture
125 may re-order tests and thus lead to repeated fixture
126 setup/teardown
126 setup/teardown
127 --pdb start the interactive Python debugger on errors.
127 --pdb start the interactive Python debugger on errors.
128 -s, --capture=no don't capture stdout (any stdout output will be
128 -s, --capture=no don't capture stdout (any stdout output will be
129 printed immediately)
129 printed immediately)
130
130
131 Performance tests
131 Performance tests
132 ^^^^^^^^^^^^^^^^^
132 ^^^^^^^^^^^^^^^^^
133
133
134 A number of performance tests are present in the test suite, but they are
134 A number of performance tests are present in the test suite, but they are
135 not run in a standard test run. These tests are useful to
135 not run in a standard test run. These tests are useful to
136 evaluate the impact of certain code changes with respect to performance.
136 evaluate the impact of certain code changes with respect to performance.
137
137
138 To run these tests::
138 To run these tests::
139
139
140 env TEST_PERFORMANCE=1 py.test kallithea/tests/performance
140 env TEST_PERFORMANCE=1 py.test kallithea/tests/performance
141
141
142 To analyze performance, you could install pytest-profiling_, which enables the
142 To analyze performance, you could install pytest-profiling_, which enables the
143 --profile and --profile-svg options to py.test.
143 --profile and --profile-svg options to py.test.
144
144
145 .. _pytest-profiling: https://github.com/manahl/pytest-plugins/tree/master/pytest-profiling
145 .. _pytest-profiling: https://github.com/manahl/pytest-plugins/tree/master/pytest-profiling
146
146
147 .. _contributing-guidelines:
147 .. _contributing-guidelines:
148
148
149
149
150 Contribution guidelines
150 Contribution guidelines
151 -----------------------
151 -----------------------
152
152
153 Kallithea is GPLv3 and we assume all contributions are made by the
153 Kallithea is GPLv3 and we assume all contributions are made by the
154 committer/contributor and under GPLv3 unless explicitly stated. We do care a
154 committer/contributor and under GPLv3 unless explicitly stated. We do care a
155 lot about preservation of copyright and license information for existing code
155 lot about preservation of copyright and license information for existing code
156 that is brought into the project.
156 that is brought into the project.
157
157
158 Contributions will be accepted in most formats -- such as pull requests on
158 Contributions will be accepted in most formats -- such as pull requests on
159 Bitbucket, something hosted on your own Kallithea instance, or patches sent by
159 Bitbucket, something hosted on your own Kallithea instance, or patches sent by
160 email to the `kallithea-general`_ mailing list.
160 email to the `kallithea-general`_ mailing list.
161
161
162 When contributing via Bitbucket, please make your fork of
162 When contributing via Bitbucket, please make your fork of
163 https://bitbucket.org/conservancy/kallithea/ `non-publishing`_ -- it is one of
163 https://bitbucket.org/conservancy/kallithea/ `non-publishing`_ -- it is one of
164 the settings on "Repository details" page. This ensures your commits are in
164 the settings on "Repository details" page. This ensures your commits are in
165 "draft" phase and makes it easier for you to address feedback and for project
165 "draft" phase and makes it easier for you to address feedback and for project
166 maintainers to integrate your changes.
166 maintainers to integrate your changes.
167
167
168 .. _non-publishing: https://www.mercurial-scm.org/wiki/Phases#Publishing_Repository
168 .. _non-publishing: https://www.mercurial-scm.org/wiki/Phases#Publishing_Repository
169
169
170 Make sure to test your changes both manually and with the automatic tests
170 Make sure to test your changes both manually and with the automatic tests
171 before posting.
171 before posting.
172
172
173 We care about quality and review and keeping a clean repository history. We
173 We care about quality and review and keeping a clean repository history. We
174 might give feedback that requests polishing contributions until they are
174 might give feedback that requests polishing contributions until they are
175 "perfect". We might also rebase and collapse and make minor adjustments to your
175 "perfect". We might also rebase and collapse and make minor adjustments to your
176 changes when we apply them.
176 changes when we apply them.
177
177
178 We try to make sure we have consensus on the direction the project is taking.
178 We try to make sure we have consensus on the direction the project is taking.
179 Everything non-sensitive should be discussed in public -- preferably on the
179 Everything non-sensitive should be discussed in public -- preferably on the
180 mailing list. We aim at having all non-trivial changes reviewed by at least
180 mailing list. We aim at having all non-trivial changes reviewed by at least
181 one other core developer before pushing. Obvious non-controversial changes will
181 one other core developer before pushing. Obvious non-controversial changes will
182 be handled more casually.
182 be handled more casually.
183
183
184 There is a main development branch ("default") which is generally stable so that
184 There is a main development branch ("default") which is generally stable so that
185 it can be (and is) used in production. There is also a "stable" branch that is
185 it can be (and is) used in production. There is also a "stable" branch that is
186 almost exclusively reserved for bug fixes or trivial changes. Experimental
186 almost exclusively reserved for bug fixes or trivial changes. Experimental
187 changes should live elsewhere (for example in a pull request) until they are
187 changes should live elsewhere (for example in a pull request) until they are
188 ready.
188 ready.
189
189
190 .. _coding-guidelines:
190 .. _coding-guidelines:
191
191
192
192
193 Coding guidelines
193 Coding guidelines
194 -----------------
194 -----------------
195
195
196 We don't have a formal coding/formatting standard. We are currently using a mix
196 We don't have a formal coding/formatting standard. We are currently using a mix
197 of Mercurial's (https://www.mercurial-scm.org/wiki/CodingStyle), pep8, and
197 of Mercurial's (https://www.mercurial-scm.org/wiki/CodingStyle), pep8, and
198 consistency with existing code. Run ``scripts/run-all-cleanup`` before
198 consistency with existing code. Run ``scripts/run-all-cleanup`` before
199 committing to ensure some basic code formatting consistency.
199 committing to ensure some basic code formatting consistency.
200
200
201 We support Python 3.6 and later.
201 We support Python 3.6 and later.
202
202
203 We try to support the most common modern web browsers. IE9 is still supported
203 We try to support the most common modern web browsers. IE9 is still supported
204 to the extent it is feasible, IE8 is not.
204 to the extent it is feasible, IE8 is not.
205
205
206 We primarily support Linux and OS X on the server side but Windows should also work.
206 We primarily support Linux and OS X on the server side but Windows should also work.
207
207
208 HTML templates should use 2 spaces for indentation ... but be pragmatic. We
208 HTML templates should use 2 spaces for indentation ... but be pragmatic. We
209 should use templates cleverly and avoid duplication. We should use reasonable
209 should use templates cleverly and avoid duplication. We should use reasonable
210 semantic markup with element classes and IDs that can be used for styling and testing.
210 semantic markup with element classes and IDs that can be used for styling and testing.
211 We should only use inline styles in places where it really is semantic (such as
211 We should only use inline styles in places where it really is semantic (such as
212 ``display: none``).
212 ``display: none``).
213
213
214 JavaScript must use ``;`` between/after statements. Indentation 4 spaces. Inline
214 JavaScript must use ``;`` between/after statements. Indentation 4 spaces. Inline
215 multiline functions should be indented two levels -- one for the ``()`` and one for
215 multiline functions should be indented two levels -- one for the ``()`` and one for
216 ``{}``.
216 ``{}``.
217 Variables holding jQuery objects should be named with a leading ``$``.
217 Variables holding jQuery objects should be named with a leading ``$``.
218
218
219 Commit messages should have a leading short line summarizing the changes. For
219 Commit messages should have a leading short line summarizing the changes. For
220 bug fixes, put ``(Issue #123)`` at the end of this line.
220 bug fixes, put ``(Issue #123)`` at the end of this line.
221
221
222 Use American English grammar and spelling overall. Use `English title case`_ for
222 Use American English grammar and spelling overall. Use `English title case`_ for
223 page titles, button labels, headers, and 'labels' for fields in forms.
223 page titles, button labels, headers, and 'labels' for fields in forms.
224
224
225 .. _English title case: https://en.wikipedia.org/wiki/Capitalization#Title_case
225 .. _English title case: https://en.wikipedia.org/wiki/Capitalization#Title_case
226
226
227 Template helpers (that is, everything in ``kallithea.lib.helpers``)
227 Template helpers (that is, everything in ``kallithea.lib.helpers``)
228 should only be referenced from templates. If you need to call a
228 should only be referenced from templates. If you need to call a
229 helper from the Python code, consider moving the function somewhere
229 helper from the Python code, consider moving the function somewhere
230 else (e.g. to the model).
230 else (e.g. to the model).
231
231
232 Notes on the SQLAlchemy session
232 Notes on the SQLAlchemy session
233 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
233 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
234
234
235 Each HTTP request runs inside an independent SQLAlchemy session (as well
235 Each HTTP request runs inside an independent SQLAlchemy session (as well
236 as in an independent database transaction). ``Session`` is the session manager
236 as in an independent database transaction). ``Session`` is the session manager
237 and factory. ``Session()`` will create a new session on-demand or return the
237 and factory. ``Session()`` will create a new session on-demand or return the
238 current session for the active thread. Many database operations are methods on
238 current session for the active thread. Many database operations are methods on
239 such session instances - only ``Session.remove()`` should be called directly on
239 such session instances. The session will generally be removed by
240 the manager.
240 TurboGears automatically.
241
241
242 Database model objects
242 Database model objects
243 (almost) always belong to a particular SQLAlchemy session, which means
243 (almost) always belong to a particular SQLAlchemy session, which means
244 that SQLAlchemy will ensure that they're kept in sync with the database
244 that SQLAlchemy will ensure that they're kept in sync with the database
245 (but also means that they cannot be shared across requests).
245 (but also means that they cannot be shared across requests).
246
246
247 Objects can be added to the session using ``Session().add``, but this is
247 Objects can be added to the session using ``Session().add``, but this is
248 rarely needed:
248 rarely needed:
249
249
250 * When creating a database object by calling the constructor directly,
250 * When creating a database object by calling the constructor directly,
251 it must explicitly be added to the session.
251 it must explicitly be added to the session.
252
252
253 * When creating an object using a factory function (like
253 * When creating an object using a factory function (like
254 ``create_repo``), the returned object has already (by convention)
254 ``create_repo``), the returned object has already (by convention)
255 been added to the session, and should not be added again.
255 been added to the session, and should not be added again.
256
256
257 * When getting an object from the session (via ``Session().query`` or
257 * When getting an object from the session (via ``Session().query`` or
258 any of the utility functions that look up objects in the database),
258 any of the utility functions that look up objects in the database),
259 it's already part of the session, and should not be added again.
259 it's already part of the session, and should not be added again.
260 SQLAlchemy monitors attribute modifications automatically for all
260 SQLAlchemy monitors attribute modifications automatically for all
261 objects it knows about and syncs them to the database.
261 objects it knows about and syncs them to the database.
262
262
263 SQLAlchemy also flushes changes to the database automatically; manually
263 SQLAlchemy also flushes changes to the database automatically; manually
264 calling ``Session().flush`` is usually only necessary when the Python
264 calling ``Session().flush`` is usually only necessary when the Python
265 code needs the database to assign an "auto-increment" primary key ID to
265 code needs the database to assign an "auto-increment" primary key ID to
266 a freshly created model object (before flushing, the ID attribute will
266 a freshly created model object (before flushing, the ID attribute will
267 be ``None``).
267 be ``None``).
268
268
269 TurboGears2 DebugBar
269 TurboGears2 DebugBar
270 ^^^^^^^^^^^^^^^^^^^^
270 ^^^^^^^^^^^^^^^^^^^^
271
271
272 It is possible to enable the TurboGears2-provided DebugBar_, a toolbar overlayed
272 It is possible to enable the TurboGears2-provided DebugBar_, a toolbar overlayed
273 over the Kallithea web interface, allowing you to see:
273 over the Kallithea web interface, allowing you to see:
274
274
275 * timing information of the current request, including profiling information
275 * timing information of the current request, including profiling information
276 * request data, including GET data, POST data, cookies, headers and environment
276 * request data, including GET data, POST data, cookies, headers and environment
277 variables
277 variables
278 * a list of executed database queries, including timing and result values
278 * a list of executed database queries, including timing and result values
279
279
280 DebugBar is only activated when ``debug = true`` is set in the configuration
280 DebugBar is only activated when ``debug = true`` is set in the configuration
281 file. This is important, because the DebugBar toolbar will be visible for all
281 file. This is important, because the DebugBar toolbar will be visible for all
282 users, and allow them to see information they should not be allowed to see. Like
282 users, and allow them to see information they should not be allowed to see. Like
283 is anyway the case for ``debug = true``, do not use this in production!
283 is anyway the case for ``debug = true``, do not use this in production!
284
284
285 To enable DebugBar, install ``tgext.debugbar`` and ``kajiki`` (typically via
285 To enable DebugBar, install ``tgext.debugbar`` and ``kajiki`` (typically via
286 ``pip``) and restart Kallithea (in debug mode).
286 ``pip``) and restart Kallithea (in debug mode).
287
287
288
288
289 "Roadmap"
289 "Roadmap"
290 ---------
290 ---------
291
291
292 We do not have a road map but are waiting for your contributions. Refer to the
292 We do not have a road map but are waiting for your contributions. Refer to the
293 wiki_ for some ideas of places we might want to go -- contributions in these
293 wiki_ for some ideas of places we might want to go -- contributions in these
294 areas are very welcome.
294 areas are very welcome.
295
295
296
296
297 Thank you for your contribution!
297 Thank you for your contribution!
298 --------------------------------
298 --------------------------------
299
299
300
300
301 .. _Weblate: http://weblate.org/
301 .. _Weblate: http://weblate.org/
302 .. _issue tracking: https://bitbucket.org/conservancy/kallithea/issues?status=new&status=open
302 .. _issue tracking: https://bitbucket.org/conservancy/kallithea/issues?status=new&status=open
303 .. _pull requests: https://bitbucket.org/conservancy/kallithea/pull-requests
303 .. _pull requests: https://bitbucket.org/conservancy/kallithea/pull-requests
304 .. _bitbucket: http://bitbucket.org/
304 .. _bitbucket: http://bitbucket.org/
305 .. _mailing list: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general
305 .. _mailing list: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general
306 .. _kallithea-general: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general
306 .. _kallithea-general: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general
307 .. _Hosted Weblate: https://hosted.weblate.org/projects/kallithea/kallithea/
307 .. _Hosted Weblate: https://hosted.weblate.org/projects/kallithea/kallithea/
308 .. _wiki: https://bitbucket.org/conservancy/kallithea/wiki/Home
308 .. _wiki: https://bitbucket.org/conservancy/kallithea/wiki/Home
309 .. _DebugBar: https://github.com/TurboGears/tgext.debugbar
309 .. _DebugBar: https://github.com/TurboGears/tgext.debugbar
310 .. _Quick Start: https://www.mercurial-scm.org/wiki/QuickStart
310 .. _Quick Start: https://www.mercurial-scm.org/wiki/QuickStart
311 .. _Beginners Guide: https://www.mercurial-scm.org/wiki/BeginnersGuides
311 .. _Beginners Guide: https://www.mercurial-scm.org/wiki/BeginnersGuides
General Comments 0
You need to be logged in to leave comments. Login now