Contents

C and C++

What gitta reads in C and C++ code, how it finds headers, and how it treats the preprocessor.

What gitta follows

gitta reads .c and .h files as C, and .cc, .cpp, .cxx, .c++, .hpp, .hh, .hxx, .h++, .ipp, .inl and .tpp files as C++. It keeps track of these names:

Names are written as in C++ code: leveldb::DB::Put, fmt::vformat. A C struct is struct client, and its field is client::querybuf. Inline namespaces are left out, because callers leave them out too.

Overloaded functions are separate items, keyed by their parameter types. A call is matched by name, so a change to one overload reaches every call of that name.

A static function or variable, a macro defined in a source file and anything in an anonymous namespace belong to that one file. A function with the same name in another file is a different function. Static inline functions and macros in headers belong to every file that includes them.

Uses

gitta counts calls, type names, macros (also inside other macros), template arguments, and fields read with . or ->. For a field, it uses the declared type of the variable in front of it: a parameter, a local variable, a global, this, a data member of the class, or a chain of members such as data->set.timeout. A global declared in a header, such as extern struct redisServer server;, is followed too. C++ names are looked up in the enclosing namespaces and class, in using namespace, in using a::b and through namespace aliases.

Headers

A header usually has many users. When one agent changes a declaration, a macro or a struct field in a header, every agent whose new code uses it is warned, in any file. The agent that changed the header does not have to know where it is included.

gitta looks for #include "x.h" next to the including file first. Then it looks in the include directories, then in include/ and src/ of the repository. If none has the file, it uses a file anywhere in the repository whose path ends in x.h. It looks for #include <x.h> only in the include directories and include/. A header that is not in the repository is a system or library header, and gitta leaves it alone.

Include directories

List your include directories in a committed .gitta-include file, one per line, the way you give them to the compiler with -I:

 .gitta-include
include
deps/hiredis
# .h files under src/ are C++
.h c++ src/
# a macro that opens a namespace
namespace PROTOBUF_NAMESPACE_OPEN google

You can also add directories for one checkout with the include list in .gitta/config.json.

Is a .h file C or C++?

gitta decides in this order:

  1. A .h c++ or .h c line in .gitta-include, for all headers or for one directory. The most specific line wins.
  2. A header that a C++ source file of the repository includes is C++.
  3. A header whose text uses C++, such as classes, namespaces or templates, is C++.
  4. Any other header is C.

When the chosen language reads a header badly, gitta tries the other one and keeps the better result.

The preprocessor

gitta does not run the preprocessor. It reads the code of every branch of #if, #ifdef, #elif and #else, so a function that exists only on one platform is still followed. When two branches write different headers for the same function, gitta reads the first branch. Code under #if 0 is skipped.

Line continuations, X-macros, export macros such as class LEVELDB_EXPORT DB and macros that open a namespace, such as FMT_BEGIN_NAMESPACE, are read. A macro that gitta does not know, for example one used as a statement without a semicolon, leaves a small region that does not parse. The rest of the file is read as usual, and the uses inside that region still count. gitta reports a file as not parsed only when more than half of it does not parse.

z/OS XL C/C++

With gitta Enterprise, gitta reads the parts of the z/OS XL C/C++ dialect that change what a name means. It keeps every byte offset, so line numbers stay right.

For code pages of C sources on z/OS, see Set the code page for each folder.

What counts as a change of meaning

These changes are high severity. Code that uses the changed name must be checked:

gitta also follows a change through helper functions. A function that returns what a changed helper returns has changed meaning too.

Merging

When two agents edit the same file, Git merges it line by line first. When the lines collide, gitta merges functions, classes and namespaces as units, so two agents who change different functions next to each other both keep their work. This structural merge needs a file that parses cleanly. Other files merge line by line, and a collision there is a conflict.

Security checks

gitta checks the lines that an agent adds to C and C++ files. It reports these risks:

RiskSeverity
A copy into a buffer with no size limit: strcpy, strcat, sprintf, gets, scanf with %s, or memcpy and memmove with a length nothing bounds. A length is bounded when it is a sizeof, a constant or the smaller of two values, or when the few lines before compare it, set it from a sizeof or allocate the buffer with it.Medium
A variable used as the format of printf, fprintf, sprintf, snprintf, syslog and similar callsMedium
A command that is not a literal given to system, popen or an exec callHigh
Certificate checks turned off: SSL_VERIFY_NONE in OpenSSL or wolfSSL, MBEDTLS_SSL_VERIFY_NONE, curl’s CURLOPT_SSL_VERIFYPEER or CURLOPT_SSL_VERIFYHOST set to 0High
MD5 or SHA-1 on a passwordMedium
Secrets in the code, or written to a log or the consoleMedium

Between agents, a function named like a check, for example verify_peer, ossl_verify_peer or check_permission, counts as a security control. When one agent weakens it, every agent whose new code calls it is warned, and the merge reports it as a weakened check. See Warnings.

Tests

gitta recognises C and C++ test files by name, such as db_test.cc, cache_unittest.cpp and test_sds.c, and by content: GoogleTest, Catch2, doctest, Boost.Test, CppUnit, Unity and CMocka test cases. A merge reports a weakened check when an agent

Measured on real code

We read every C and C++ file of these projects with the release build in October 2026. When part of a file does not parse, gitta reads the rest of the file as usual. The table shows how much was left unread.

ProjectFilesFiles with an unread partBytes left unreadFiles not parsedRead speed
SQLite 3.50.4 amalgamation420.01%06.8 MB/s
curl107115 (1%)0.02%05.5 MB/s
Redis, with its dependencies85439 (5%)0.14%3 (0.35%)5.4 MB/s
fmt7419 (26%)0.42%02.2 MB/s
LevelDB1330003.0 MB/s
Linux kernel/, mm/ and include/linux/3718111 (3%)0.08%05.2 MB/s

Read speed is on one core of an Apple M1 Pro. A hook scans only its own agent's worktree and reuses the last scan of the others.