# Perfetto Developer Tools

The Perfetto team have created several scripts/tools which navigating and
working with the Perfetto codebase. This page is mainly targeted towards
frequent contributors to Perfetto (e.g. team members or external contributors
sending a lot of PRs).

These tools have a bit of a learning curve to them but can significantly
accelerate the developer experience.

## Continuous integration

The Perfetto CI on GitHub Actions covers building and testing
on most platforms and toolchains within ~30 mins. Anecdotally most build
failures and bugs are detected at the Perfetto CI level.

You can also
[test a pending Perfetto CL against Chrome's TryBots](testing.md#chromium).

## "Stacked diffs" with GitHub

From our days on Android, Perfetto has long worked on a "stacked-diff" model and
its one that we like a lot inside the team. However, GitHub is very much _not_
optimized for stack diffs.

We've explored a bunch of tools in the ecosystem (git-town, Graphite, git-spice,
git-stack) and for various reasons, none of them fit all our requirements. These
are:

1. Use branches as a fundamental unit of a "PR" _not_ commits

- This is what we're used to from our Android days and we all like it.

2. Does not replace normal git commands but makes working with them easier

- We're not looking for a system which wants to take over all parts of git, just
  something which makes things a bit easier

3. _important_ Handles updating stacks by merging **not** rebasing

- The most critical one which knocks out a bunch of tools.
- The main problem here is that GitHub breaks badly for reviewers if you
  rebase + force-push.
- While it's much nicer for authors, the cost to reviewers is too high.

To this end, we have developed a bunch of Python scripts which live in our tools
folder and implement the above. You can add them to your git aliases. Modify
your `.gitconfig` to include the following:

```
[alias]
    # Create a new branch based on a parent and set its parent config
    # Usage: git new-branch <new_branch_name> [--parent <parent_branch>]
    new-branch   = "!f() { ./tools/git_new_branch.py \"$@\"; }; f"

    # Set the parent branch for the target branch (default: current)
    # Usage: git set-parent <parent_branch> [--target <branch>]
    set-parent   = "!f() { ./tools/git_set_parent.py \"$@\"; }; f"

    # Renames local branch and updates children's parent config
    # Usage: git rename-branch --new <new_name> [--target <old_name>]
    rename-branch = "!f() { ./tools/git_rename_branch.py \"$@\"; }; f"

    # Checkout the configured parent of the target branch (default: current)
    # Usage: git goto-parent [--target <branch>]
    goto-parent  = "!f() { ./tools/git_goto_parent.py \"$@\"; }; f"

    # Find and checkout a child of the target branch (default: current)
    # Usage: git goto-child [--target <branch>]
    goto-child   = "!f() { ./tools/git_goto_child.py \"$@\"; }; f"

    # Update local stack segment (target+ancestors+descendants) via merges
    # Usage: git update-stack [--target <branch>]
    update-stack = "!f() { ./tools/git_update_stack.py \"$@\"; }; f"

    # Update ALL local stacks via merges using topological sort
    # Usage: git update-all
    update-all   = "!f() { ./tools/git_update_all.py \"$@\"; }; f"

    # Push full stack segment (target+ancestors+descendants) and sync GitHub PRs
    # Usage: git sync-stack [--target <branch>] [--remote <name>] [--draft] [--force]
    sync-stack   = "!f() { ./tools/git_sync_stack.py \"$@\"; }; f"

    # Prune all local branches identical (no diff) to ~their effective parent
    # Usage: git prune-all [--dry-run]
    prune-all = "!f() { ./tools/git_prune_all.py \"$@\"; }; f"
```

All of these tools work by adding an entry to the repo's gitconfig called
`branch.{branch_name}.parent` which keeps track of the parent branch. This is
then used to figure out what a "stack" is and then perform operations on it.

A normal workflow using these tools might look like this:

```
# Create a branch for the feature.
git new-branch dev/${USER}/my-feature

# .... hack away, make changes

# Commit; will be used as PR title
git commit -a -m 'My feature'

# Create a new branch for adding something on top of the feature.
git new-branch dev/${USER}/my-feature-2 --current-parent

# ... hack away, make changes

# Commit; will be used as PR title
git commit -a -m 'My feature changes'

# Make GitHub PRs out of the above, correctly setting up base branches and
# PR descriptions based on commit messages.
git sync-stack

# Go to my-feature to respond to review
git goto-parent

# ... make changes for review

git commit -a -m 'Respond to review'

# Update the stack so that my-feature-2 also has this commit.
git update-stack

# Sync to GitHub.
git sync-all

# ... my-feature is approved and merged on GitHub.

# Do a merge again to make everything up-to-date.
git update-stack

# Prune my-feature now it's no longer necessary
git prune-all
```
