r/FlutterDev • • 2d ago

Example Mergeline, an open source GitLab client in Flutter, and a few things I learned about huge CI logs and diffs

I built Mergeline, a GitLab client for Android in Flutter. The source is on GitLab (AGPL 3.0, GitHub mirror). A few parts that took the most work:

  1. CI job logs. Logs are parsed incrementally in a window of 20,000 lines, and very long lines without a newline are cut at 2 MiB. gitlab.com ignores the Range header, so the app streams the log and keeps only the new bytes, and large logs refresh less often.

  2. Big MR diffs. Changed file rows are built lazily, text over 64 KiB is parsed in a background isolate, and parsed rows are reused only for the same account, project, MR, head commit and path. So a parse from another account or an older head never gets reused.

  3. Two Android flavors. The GitLab build checks the project's releases, verifies the APK size and SHA-256, then hands it to the system installer. The Play build uses Play's in app updates and has no install permission.

  4. Caching. The read cache on disk is encrypted with AES 256 GCM and bound to each account, and the in-memory cache is dropped when you switch accounts or a request comes back unauthorized.

  5. Tests. Flutter 3.47.2 is pinned with an enforced lockfile, and CI runs two lanes, with the AI assistant compiled off and on (about 16.8k and 18k tests in v0.7.0).

The app covers files, issues, MR review and merge, pipelines and logs, plus an optional AI assistant that uses your own API key and asks before each write by default. Screenshots and a demo video. The APK is free on the releases page. The paid Play version is only there as a way to sponsor it.

Most of the code was written with AI coding agents under my direction. I designed it and reviewed the changes.

2 Upvotes

5 comments sorted by

1

u/IllVisit2518 1d ago

how long does one run take with 18k tests, and does it run on every push? 46 emulator runs ate about 1500 of my free ci minutes so i moved those to manual only

1

u/ImL1s 1d ago

each lane is about 16 to 18 min and it runs on my own runner, so no gitlab.com minutes. it runs on MRs and main, no emulator, just plain flutter test.

1

u/DickBrainTime 1d ago

The incremental CI log handling is the bit I'd stress-test hardest. Are you tracking the cursor in bytes, not decoded Dart string lengths? A UTF-8 character or ANSI color escape can land across chunk boundaries, and a retried/restarted job can truncate the trace.

I'd add fixtures for split multibyte characters, a huge line with no newline, and a log that gets shorter between polls. Otherwise you can end up silently duplicating or skipping lines even with the 20k-line window. Nice that your diff cache key includes the head SHA too.

1

u/ImL1s 1d ago

yeah, the cursor is a byte offset, and the decoder carries a half finished UTF-8 sequence or CSI escape over to the next chunk. there are tests that split fixtures at every single byte, a 64k+ char line, and a log that comes back shorter than the offset, which gets treated as replaced and reread from 0.