diff --git a/README.md b/README.md index f30d4c0..9fd5402 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,66 @@ +# What is this + +A tool to help you when you accidentally wrote too much code in too many places +in a feature branch. This tool lets you preserve history and break the code +down by folders into individual branches. This is really useful for people who +work on decoupled component architectures. + +It seems it is a frequent pattern for engineers to get so focused on making +changes they often need a way to break it up and release it in smaller chunks +to make reviews easier for their peers. + # Usage -``` -git exfiltrator +1. Be sure you have the branch that needs to be broken up checked out +2. Be sure you are in the project root +3. Ensure that your branch was not already merged into your project's primary branch (if those changes were already applied, there is nothing to split) - - defines the target to merge towards. - - defines the subject branch that files will be extracted from - - same as in `git add`, except only able to add files that exist - between the and the target found in each commit. ``` - -# Known Uses -- Breaking up a large branch or Pull Request into many smaller ones. -- Releasing (or moving) code that is "ready" without waiting for code that is - "not ready" -- Wanting to remove some feature or migrate development to another branch - without losing history. +Usage: git-exfiltrator[-h] [-b] against-ref subject-ref pathspec + The branch you will merge changes into + The branch you want to create + the path you wish to split ("some/path/*") + (optional) The tool will attempt to auto-detect the common ancestor + between your branch and the against target. If your branch histories + are complicated you can manually provide the original ancestor commit. + +Break a big feature branch into a smaller specific branch with the changes from +one specific folder. Also, preserve your commit history. +``` # Benefits -- A late stage fix to development processes which generate massive PRs instead - of many small PRs. +- A late stage fix to development processes (or mindset) which generate massive + branches instead of many small ones. - Keeping history of work migrated to a new branch. -- Avoid using primitive cut+paste techniques. +- Avoid using primitive cut+paste techniques from one branch to another which + remove history and are error prone. # Known Drawbacks - Commit messages will be duplicated into the extracted branch. -- Operator should know how a git-tree works. +- The user should know how a git-tree works (or have access to someone who + does) if there are serious complications. +- Possibility of introducing multiple root ancestors (unknown if there are practical drawbacks). # Installation +You can install the script using this repo to keep things versioned correctly, +or you can try and do it the hack way. -You can install the script using this repo to keep things versioned correctly, or you can try and do it the hack way. +## Hacky way: +Cut+paste the script to your home folder and run the bash script whenever you +want to use it. -Hacky way: Cut+paste the script to your home folder and run the bash script whenever you want to use it. +## Supported way: +1. Clone the repo to your home folder. +2. Make your `$PATH` envvar able to resolve this script. Once you do, git will + understand `git exfiltrate` (without the `-`) anywhere you want to use it. +3. Then add this repo to your watchlist/subscriptions on GitHub so you know + when/if I provide any updates or useful improvements, and you can git-pull + the update as needed. -Supported way: Clone the repo to your home folder. Make your `$PATH` envvar able to resolve this script. Once you do, git will understand `git exfiltrate` (without the `-`) anywhere you want to use it. Then add this repo to your watchlist/subscriptions on GitHub so you know when/if I provide any updates or useful improvements. +(If someone would like to maintain a `.deb` or `brew` dependency I'd be happy +to link to it.) -(If someone would like to maintain a `.deb` or `brew` dependency I'd be happy to link to it.) - -# Example - -[![asciicast](https://asciinema.org/a/OwPrRxKT0IHauAdgXZ5p74naT.svg)](https://asciinema.org/a/OwPrRxKT0IHauAdgXZ5p74naT) +# Example usage Imagine you're faced with a large `feature-branch` and it's making too many changes to too many parts of the code (See "uses" below). Consider this trivial @@ -65,13 +87,15 @@ maintain a logical commit history. a/1 | 1 + ``` -Run git exfiltrator to extract the `b` folder into a new "extract" branch: +Run git exfiltrate to extract the `b` folder into a new "extract" branch: ``` -./git-exfiltrate master feature-branch "b/*" +git checkout feature-branch +./git-exfiltrate master feature-branch-extracted "b/*" ``` -The `-extracted` branch will be merged into the `subject` argument provided. +The `-extracted` branch will be created with just the contents of `b` from the branch. + Note that the extracted branch has an unrelated history from the original `feature-branch`. This means master has all of folder `b`, and a complete timeline of changes related to `b`, but none of the work from `a` or `c` is @@ -123,3 +147,12 @@ branch. I hope one day our entire industry (not just some pockets of it) will fully embrace and be capable of working only in small incremental changes. + +There was a previous "Stateless" version that let you operate on bare repos but +it proved too unintuitive in practice for a wide audience, so this new version +requires that you are checked out on the right branch and in the root folder. + +And yes, the heart of this program is a 1-liner but the real value here is +documenting how to do that one liner and making it dead-simple to an audience +who typically not want to figure it out while needing to deliver more code +quickly. diff --git a/git-exfiltrate b/git-exfiltrate index 89f981e..fdf6d1d 100755 --- a/git-exfiltrate +++ b/git-exfiltrate @@ -1,18 +1,53 @@ -#!/bin/sh +#!/usr/bin/env bash -destinationRef=$1 -originRef=$2 -pathspec=$3 -test -z "$destinationRef" && echo "ERROR: Please provide the destination reference." 1>&2 && exit 1 -test -z "$originRef" && echo "ERROR: Please provide the origin reference." 1>&2 && exit 1 -test -z "$pathspec" && echo "ERROR: Please provide the paths to be extracted." 1>&2 && exit 1 +set -Eeuo pipefail -git checkout "$originRef" +usage() { + cat < () + The branch you will merge changes into + The branch you want to create + the path you wish to split ("some/path/*") + (optional) The tool will attempt to auto-detect the common ancestor + between your branch and the against target. If your branch histories + are complicated you can manually provide the original ancestor commit. -git checkout -b "$originRef-extracted" +Break a big feature branch into a smaller specific branch with the changes from +one specific folder. Also, preserve your commit history. -git filter-branch --force --prune-empty --index-filter \ - "git ls-files | grep -Ev $pathspec | xargs git rm --cached" "$(git merge-base "$destinationRef" "$originRef")..HEAD" +EOF + exit +} -git checkout "$destinationRef" -git merge "$originRef-extracted" --no-edit --allow-unrelated-histories +AGAINST=${1:-} +NEW_BRANCH=${2:-} +PATHSPEC=${3:-} +if [ "$#" -lt 3 ]; then + usage +fi + +BASE=${4:-$(git merge-base "$AGAINST" HEAD)} + +git checkout -b "$NEW_BRANCH" || exit + +FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch --force --prune-empty --index-filter \ + "git ls-files | grep -Ev $PATHSPEC | xargs git rm --cached" "$BASE..HEAD" + + +cat <a/1 -git add a -git commit -m "Introduces a" - -echo "hello" >a/2 -git add a -git commit -m "adds to a" - -git checkout -b feature-branch -mkdir b c -touch b/b1 c/c1 a/a1 -echo "hello new module" >b/b1 -echo "hello new module" >c/c1 -echo "hello new module" >a/a1 - -git add --all . -git commit -m "Introduces a1 b1 c1" - -touch b/b2 c/c2 -echo "hello addition" >b/b2 -echo "hello addition" >c/c2 - -git add --all . -git commit -m "adds to b2 c2" - -git checkout master -touch a/3 -git add --all a -echo "foo" -git commit -m"Adds a/3" - -git checkout feature-branch - -echo "------------------" -# runs git-exfiltrate -../git-exfiltrate master feature-branch "b/*" -git lg --all --stat