******************
Contributing guide
******************

Thanks for your interest in contributing to cpplint.

Any kinds of contributions are welcome: Bug reports, Documentation, Patches. However, here are some contributions you probably shouldn't make:

* Drastic reorganization
   * Making the code conform to Google's Python style guidelines
* Features that could be regarded as a security vulnerability

If you need some ideas, you may check out some of the tasks in our `issue tracker <https://github.com/cpplint/cpplint/issues>`_.

Development
===========

For many tasks, it is okay to just develop using a single installed python version. But if you need to test/debug the project in multiple python versions, you need to install those versions:

1. (Optional) Install multiple python versions

   1. (Optional) Install `pyenv <https://github.com/pyenv/pyenv-installer>`_ to manage python versions
   2. (Optional) Using pyenv, install the python versions used in testing::

        pyenv install 3.<version>
        # ...
        pyenv local 3.<version> ...

It may be okay to run and test python against locally installed libraries, but if you need to have a consistent build, it is recommended to manage your environment using virtualenv: `virtualenv <https://virtualenv.pypa.io/en/latest/>`_, `virtualenvwrapper <https://pypi.org/project/virtualenvwrapper/>`_::

    mkvirtualenv cpplint [-p /usr/bin/python3]
    pip install .[dev]

Alternatively, you can locally install patches like this::

    pip install -e .[dev]
    # for usage without virtualenv, add --user

Please install pre-commit locally to run the linters before committing::

    pipx install pre-commit
    pre-commit install

Pull requests
-------------

When you're finished with a pull request, please:

* add a relevant test case to cpplint_unittest.py
* add a summary of what your changes do to CHANGELOG.rst
* specify the problem solved in the pull request
* make sure that your code passes the tests and lints
* don't force-push to the branch. These make the commit history messy, and we squash all commits when merging anyways.

.. _testing:

Testing
-------

You can test your changes under your local python environment by running the tests and lints below:

.. code-block:: bash

    # install test requirements
    pip install .[test]
    # run a single test
    pytest --no-cov cpplint_unittest.py -k testName
    # run a single CLI integration test
    pytest --no-cov cpplint_clitest.py -k testSillySample
    # run all tests. you don't have to run the above tests separately
    pytest
    # lint the code
    pylint cpplint.py
    pre-commit run --all-files

Alternatively, you can run `tox` to automatically run all tests and lints. Use `-e ` followed by the python runner and version (which you must have installed) to automatically generate the testing environment and run the above tests and lints in it. For example, `tox -e py39` does the steps in Python 3.9, `tox -e py313` does the steps in Python 3.13, and `tox -e pypy3` does the steps using the latest version of the pypy interpreter.

Releasing
=========

The release process first prepares the documentation, then publishes to testpypi to verify, then releases to real pypi. Testpypi acts like real pypi, so broken releases cannot be deleted. For a typical bugfixing release, no special issue on testpypi is expected (but it's still good practice). The commands are documented below, and assume you've went through the testing steps above.

.. code-block:: bash

    # prepare files for release
    $EDITOR cpplint.py # increment the version
    $EDITOR CHANGELOG.rst # log changes
    git add cpplint.py CHANGELOG.rst
    git commit -m "Releasing x.y.z"
    # Build
    pip install --upgrade build wheel twine
    rm -rf dist
    python -m build --sdist --wheel
    # Test release, requires account on testpypi
    twine upload --repository testpypi dist/*
    # ... Check website and downloads from https://test.pypi.org/project/cpplint/
    # Actual release
    twine upload dist/*
    git tag x.y.z
    git push --tags

After releasing, it is be good practice to comment on completed GitHub issues to notify authors.

Catching up with Upstream
=========================

For maintainers, it is a regular duty to look at what cpplint changes were merged upstream and include them in this fork (though these updates happen rarely).

Checkout here and upstream google:

.. code-block:: bash

    git clone git@github.com:cpplint/cpplint.git
    cd cpplint
    git remote add google https://github.com/google/styleguide

To incorporate google's changes:

.. code-block:: bash

    git fetch google gh-pages

    ## Merge workflow (clean, no new commits)
    git checkout develop -b updates
    git merge google/gh-pages # this will have a lot of conflicts
    # ... solve conflicts
    git merge -- continue

    ## Rebase workflow (dirty, creates new commits)
    git checkout -b updates FETCH_HEAD
    git rebase develop # this will have a lot of conflicts, most of which can be solved with the next command (run repeatedly)
    # solve conflicts with files deleted in our fork (this is idempotent and safe to be called. when cpplint.py has conflicts, it will do nothing)
    git status | grep 'new file:' | awk '{print $3}' | xargs -r git rm --cached ; git status | grep 'deleted by us' | awk '{print $4}' | xargs -r git rm
    git status --untracked-files=no | grep 'nothing to commit' && git rebase --skip

    git push -u origin updates
    # check github action
    git push origin --delete updates

    git rebase updates develop
    git branch -D updates
    git push

Setup fetching of pull requests in .git/config:

.. code-block:: bash

    [remote "origin"]
    	url = git@github.com:cpplint/cpplint.git
    	fetch = +refs/heads/*:refs/remotes/origin/*
    # following line should be new, fetches PRs from cpplint
    	fetch = +refs/pull/*/head:refs/remotes/origin/pr/*
    [remote "google"]
    	url = https://github.com/google/styleguide
    	fetch = +refs/heads/*:refs/remotes/google/*
    # following line should be new, fetches PRs from google/styleguides
    	fetch = +refs/pull/*/head:refs/remotes/google/pr/*


To compare this for with upstream (after git fetch):

.. code-block:: bash

    git diff google/gh-pages:cpplint/cpplint.py develop:cpplint.py
    git diff google/gh-pages:cpplint/cpplint_unittest.py develop:cpplint_unittest.py
