Slice and dice your commits with these git flags
Tags: git, productivity, beginners
Published: 2020-01-13 18:16:09


Ah `git.` The canonical example of "easy to learn, acquire a PhD to master". Perhaps someday I'll learn about the subtle mysteries of `git bisect` or `git rev-parse` (wut?) but for now, here are some practical (and simple!) commands you can use every day:

### git add --pick

You always only work on one thing at a time in between commits, right? Uh, yeah, me too! :grin: For those rare times you don't, you can use this command to only add **selected parts** of a changed file. `git` adorably calls these selections "hunks" :muscle:

Let's see what this would look like! Here's a repo with just one file that I've made changes to, with the changes displayed via `git diff`:

```text
$ git diff
diff --git a/sweet-sweet-file.js b/sweet-sweet-file.js
index 85db7f4..e3752aa 100644
--- a/sweet-sweet-file.js
+++ b/sweet-sweet-file.js
@@ -1,3 +1,7 @@
// sweet-sweet-file.js

+export const relevantFunction = () => 'I am so relevant to this commit';
+
 export const initialFunction = () => 'Ahoy!';
+
+export const notReleventFunction = () => 'I am so related to something else';
```

Here the `+` signs indicate lines in the file that are additions that haven't been added to the staging area yet (`-` signs would indicate removed lines, but we don't have any).

I'm ready to commit `relevantFunction`, but got distracted and also fleshed out `notRelevantFunction`, so I don't want to just `git add` this file in its entirety. Enter `git add --pick` (or `git add -p` for short), which presents me with a command line interface for picking which parts of the file I want to add to the staging area:

```text
$ git add -p
diff --git a/sweet-sweet-file.js b/sweet-sweet-file.js
index 85db7f4..e3752aa 100644
--- a/sweet-sweet-file.js
+++ b/sweet-sweet-file.js
@@ -1,3 +1,7 @@
 // sweet-sweet-file.js

+export const relevantFunction = () => 'I am so relevant to this commit';
+
 export const initialFunction = () => 'Ahoy!';
+
+export const notReleventFunction = () => 'I am so related to something else';
Stage this hunk [y,n,q,a,d,s,e,?]?
```

Ooo, cryptic one letter options! You can ~~git~~ get more detail by entering `?`

```text
Stage this hunk [y,n,q,a,d,s,e,?]? ?
y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later hunks in the file
s - split the current hunk into smaller hunks
e - manually edit the current hunk
? - print help
@@ -1,3 +1,7 @@
 // sweet-sweet-file.js

+export const relevantFunction = () => 'I am so relevant to this commit';
+
 export const initialFunction = () => 'Ahoy!';
+
+export const notReleventFunction = () => 'I am so related to something else';
Stage this hunk [y,n,q,a,d,s,e,?]?
```

Now we're talking! Git will work its way through the file and identify "hunks" of changes, asking you what you want to do with each one. `y` will add the hunk, `n` will skip it, and `q` will get you out of here.

There are some additional interesting options, particularly `s`, which allows you to get really surgical and split up hunks into smaller pieces! This is exactly what we want here, as we only want `relevantFunction`. Let's do it:

```text
Stage this hunk [y,n,q,a,d,s,e,?]? s
Split into 2 hunks.
@@ -1,3 +1,5 @@
 // sweet-sweet-file.js

+export const relevantFunction = () => 'I am so relevant to this commit';
+
 export const initialFunction = () => 'Ahoy!';
Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?
```

Hooray! Now we can hit `y` to just add this part of the file that we want, and then `n` to discard the second hunk with `notRelevantFunction` in it, returning us to the command line as we've reached the end of the file:

```text
Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y
@@ -3 +5,3 @@
 export const initialFunction = () => 'Ahoy!';
+
+export const notReleventFunction = () => 'I am so related to something else';
Stage this hunk [y,n,q,a,d,K,g,/,e,?]? n

$
```

Let's see where we stand with `git status`:

```text
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   sweet-sweet-file.js

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   sweet-sweet-file.js
```
The first time I used this command, it blew my mind a bit: `sweet-sweet-file.js` is both staged... and not-staged! 🤯 Once you get past the idea of committing _files_ and instead committing _changes_ (which git is perfectly able and willing to do) this feels totally normal (and awesome!).

### git diff --staged

Now that we've partially committed a file, how can we confirm we did it right? Let's try `git diff`:

```text
$ git diff
diff --git a/sweet-sweet-file.js b/sweet-sweet-file.js
index c48b8c3..e3752aa 100644
--- a/sweet-sweet-file.js
+++ b/sweet-sweet-file.js
@@ -3,3 +3,5 @@
 export const relevantFunction = () => 'I am so relevant to this commit';

 export const initialFunction = () => 'Ahoy!';
+
+export const notReleventFunction = () => 'I am so related to something else';
```

Probably what we should expect: `git` is telling us that `notRelevantFunction` is an un-added addition to `sweet-sweet-file.js`. With no options, `git diff` will just look at what's different between the file's existing state and what's staged and/or committed. If you just want to see the difference between what's _staged_ and what's committed, you can add the `--staged` flag (`--cached` also works!):

```text
$ git diff --staged
diff --git a/sweet-sweet-file.js b/sweet-sweet-file.js
index 85db7f4..c48b8c3 100644
--- a/sweet-sweet-file.js
+++ b/sweet-sweet-file.js
@@ -1,3 +1,5 @@
 // sweet-sweet-file.js

+export const relevantFunction = () => 'I am so relevant to this commit';
+
 export const initialFunction = () => 'Ahoy!';
```

There we go -- confirmation that committing what's in the staging area will just commit `relevantFunction` :tada:


### git checkout --pick

Sometimes instead of affirmatively _adding_ hunks, it's easier to just subtract a few from an otherwise solid set of changes to a file.

You can use `git checkout <file>` to reset a file to it's last committed state, but if you only want to remove a few things, `--pick` works for `git checkout` too! Think of it as basically the opposite of `git add --pick`. Here we've made another set of changes:

```text
$ git diff
diff --git a/sweet-sweet-file.js b/sweet-sweet-file.js
index c48b8c3..cb9c9c7 100644
--- a/sweet-sweet-file.js
+++ b/sweet-sweet-file.js
@@ -1,5 +1,9 @@
 // sweet-sweet-file.js

+export const no = () => 'This is something experimental that should go away';
+
 export const relevantFunction = () => 'I am so relevant to this commit';

 export const initialFunction = () => 'Ahoy!';
+
+export const anEvenBetterFunction = () => 'This function is so good';
```

Let's rid ourselves of the `no` function, so we can just have the changes we want and commit the entire file:

```text
$ git checkout --pick
diff --git a/sweet-sweet-file.js b/sweet-sweet-file.js
index c48b8c3..cb9c9c7 100644
--- a/sweet-sweet-file.js
+++ b/sweet-sweet-file.js
@@ -1,5 +1,9 @@
 // sweet-sweet-file.js

+export const no = () => 'This is something experimental that should go away';
+
 export const relevantFunction = () => 'I am so relevant to this commit';

 export const initialFunction = () => 'Ahoy!';
+
+export const anEvenBetterFunction = () => 'This function is so good';
Discard this hunk from worktree [y,n,q,a,d,s,e,?]?
```

Notice the new command line prompt asking if you want to *Discard* this hunk from the "worktree" (your unstaged/uncommitted changes). Like before, we need to split the hunk in two:

```text
Discard this hunk from worktree [y,n,q,a,d,s,e,?]? s
Split into 2 hunks.
@@ -1,5 +1,7 @@
 // sweet-sweet-file.js

+export const no = () => 'This is something experimental that should go away';
+
 export const relevantFunction = () => 'I am so relevant to this commit';

 export const initialFunction = () => 'Ahoy!';
Discard this hunk from worktree [y,n,q,a,d,j,J,g,/,e,?]?
```

Then rid ourselves of the hunk we want to discard by entering `y`:

```text
Discard this hunk from worktree [y,n,q,a,d,j,J,g,/,e,?]? y
@@ -3,3 +5,5 @@
 export const relevantFunction = () => 'I am so relevant to this commit';

 export const initialFunction = () => 'Ahoy!';
+
+export const anEvenBetterFunction = () => 'This function is so good';
Discard this hunk from worktree [y,n,q,a,d,K,g,/,e,?]?
```
And keeping the one we want (i.e. *don't* discard) with `n`, returning us to the command line. Now we can use plain old `git diff` to see that only the changes we want are ready to be `git add`'ed:

```text
λ git diff
diff --git a/sweet-sweet-file.js b/sweet-sweet-file.js
index c48b8c3..cb8545c 100644
--- a/sweet-sweet-file.js
+++ b/sweet-sweet-file.js
@@ -3,3 +3,5 @@
 export const relevantFunction = () => 'I am so relevant to this commit';

 export const initialFunction = () => 'Ahoy!';
+
+export const anEvenBetterFunction = () => 'This function is so good';
```

### Conclusion
Adding some options to `git add`, `git checkout` and `git diff` allows you to commit more surgically within files, and make sure you did it right! Are there other options for fine grain commit polishing? Let me know in the comments!