Skip to main content

Overview

Stashing allows you to temporarily save uncommitted changes without creating a commit. This is useful when you need to switch branches, pull updates, or temporarily set aside work.

Save Progress

Temporarily save uncommitted changes

Clean Working Directory

Switch branches without committing

Branch-Specific

Stashes are tagged with the branch they came from

Auto-Cleanup

Desktop automatically manages and cleans up stashes

Understanding Stash

What is a Stash?

A stash is a temporary save of:
  • Modified tracked files
  • Staged changes
  • Untracked files (when explicitly included)
Stashes are stored in a stack (LIFO - Last In, First Out):
stash@{0}  <- Most recent
stash@{1}
stash@{2}  <- Oldest

When to Use Stash

Good Use Cases:

Switch Branches

Temporarily save work to switch to another branch

Pull Changes

Clear working directory before pulling

Experiment

Try something without committing current work

Interrupt Work

Save progress when switching contexts

Stashing in GitHub Desktop

Automatic Stashing

GitHub Desktop automatically stashes changes when:
1

Switch Branches with Changes

Try to switch branches while you have uncommitted changes
2

Stash Dialog Appears

GitHub Desktop asks what to do:
  • Bring my changes: Move changes to new branch (if possible)
  • Stash changes: Save changes and switch
  • Cancel: Stay on current branch
3

Choose Stash

If you select “Stash changes”, Desktop:
  • Creates a stash with branch name
  • Switches to the target branch
  • Saves stash for later restoration

Manual Stashing

Create a stash manually:
1

Have Uncommitted Changes

Make some changes but don’t commit them
2

Open Stash Options

Click Repository > Stash all changes
3

Confirm

Changes are saved to stash and working directory is cleaned

Stash Implementation

From the source code:

Desktop’s Stash Marker

// From app/src/lib/git/stash.ts
export const DesktopStashEntryMarker = '!!GitHub_Desktop'

// Pattern to identify Desktop-created stashes
const desktopStashEntryMessageRe = /!!GitHub_Desktop<(.+)>$/

// Stash message format
export function createDesktopStashMessage(branchName: string) {
  return `${DesktopStashEntryMarker}<${branchName}>`
}
GitHub Desktop tags stashes with !!GitHub_Desktop<branch-name> to:
  • Identify which stashes it created
  • Associate stashes with specific branches
  • Enable automatic cleanup
  • Differentiate from manually created stashes
Stashes created outside GitHub Desktop (via command line) are not affected by Desktop’s automatic management.

Creating a Stash

export async function createDesktopStashEntry(
  repository: Repository,
  branch: Branch | string,
  untrackedFilesToStage: ReadonlyArray<WorkingDirectoryFileChange>
): Promise<boolean> {
  // Stage untracked files first
  const fullySelectedUntrackedFiles = untrackedFilesToStage.map(x =>
    x.withIncludeAll(true)
  )
  await stageFiles(repository, fullySelectedUntrackedFiles)
  
  const branchName = typeof branch === 'string' ? branch : branch.name
  const message = createDesktopStashMessage(branchName)
  const args = ['stash', 'push', '-m', message]
  
  const result = await git(args, repository.path, 'createStashEntry')
  
  // Git returns this when there's nothing to stash
  if (result.stdout === 'No local changes to save\n') {
    return false
  }
  
  return true
}
Key Features:
  • Includes untracked files if specified
  • Tagged with branch name
  • Returns false if nothing to stash
  • Uses stash push with message

Listing Stashes

export async function getStashes(repository: Repository): Promise<StashResult> {
  const { formatArgs, parse } = createLogParser({
    name: '%gD',          // Stash name (stash@{0})
    stashSha: '%H',       // Commit SHA
    message: '%gs',       // Stash message  
    tree: '%T',           // Tree SHA
    parents: '%P',        // Parent commits
  })
  
  const result = await git(
    ['log', '-g', ...formatArgs, 'refs/stash', '--'],
    repository.path,
    'getStashEntries'
  )
  
  const desktopEntries: Array<IStashEntry> = []
  const entries = parse(result.stdout)
  
  // Filter for Desktop-created stashes
  for (const { name, message, stashSha, tree, parents } of entries) {
    const branchName = extractBranchFromMessage(message)
    
    if (branchName !== null) {
      desktopEntries.push({
        name,
        stashSha,
        branchName,
        tree,
        parents: parents.split(' '),
        files: { kind: StashedChangesLoadStates.NotLoaded },
      })
    }
  }
  
  return { 
    desktopEntries, 
    stashEntryCount: entries.length 
  }
}

Restoring Stashes

Automatic Restoration

When switching back to a branch:
1

Switch to Branch

Change to a branch that has a stash
2

Stash Notification

GitHub Desktop notifies you if a stash exists for this branch
3

Restore Option

Click Restore stash to apply the changes
4

Stash Applied

Changes are restored to your working directory

Manual Restoration

Restore a specific stash:
1

View Stashes

Click Repository > View stashes
2

Select Stash

Choose the stash you want to restore
3

Pop Stash

Click Restore and delete to pop the stash

Pop vs. Apply

GitHub Desktop uses pop by default:
export async function popStashEntry(
  repository: Repository,
  stashSha: string
): Promise<void> {
  const expectedErrors = new Set<DugiteError>([DugiteError.MergeConflicts])
  const stashToPop = await getStashEntryMatchingSha(repository, stashSha)
  
  if (stashToPop !== null) {
    const args = ['stash', 'pop', '--quiet', `${stashToPop.name}`]
    
    await git(args, repository.path, 'popStashEntry', {
      expectedErrors,
    }).catch(e => {
      // If pop succeeds but exits with 1 (conflicts with no stderr),
      // manually drop the stash
      if (e instanceof GitError && 
          e.result.exitCode === 1 && 
          e.result.stderr.length === 0) {
        return dropDesktopStashEntry(repository, stashSha)
      }
      return Promise.reject(e)
    })
  }
}
Pop:
  • Applies stash changes
  • Removes stash from the stack
  • Default in GitHub Desktop
Apply (via command line):
  • Applies stash changes
  • Keeps stash in the stack
  • Allows applying to multiple branches

Stash Conflicts

When restoring a stash causes conflicts:
1

Conflict Detection

GitHub Desktop shows conflicted files
2

Resolve Conflicts

Fix conflicts like any merge:
  • Open in editor
  • Choose “ours” or “theirs”
  • Manually edit conflict markers
3

Stage Resolutions

Resolved files are automatically staged
4

Stash Removed

After successful pop, stash is deleted even with conflicts
Unlike merge/rebase, there’s no “continue” after stash conflicts. The stash is already applied (or deleted), you just need to resolve and commit.

Dropping Stashes

Delete Specific Stash

1

View Stashes

Open the stash list
2

Select Stash

Find the stash you want to delete
3

Delete

Right-click and select Delete stash
4

Confirm

Confirm the deletion
export async function dropDesktopStashEntry(
  repository: Repository,
  stashSha: string
) {
  const entryToDelete = await getStashEntryMatchingSha(repository, stashSha)
  
  if (entryToDelete !== null) {
    const args = ['stash', 'drop', entryToDelete.name]
    await git(args, repository.path, 'dropStashEntry')
  }
}

Automatic Cleanup

GitHub Desktop automatically cleans up stashes that are no longer needed (implementation details not in current source, but stashes are managed automatically).

Moving Stashes Between Branches

GitHub Desktop can reassign a stash to a different branch:
export async function moveStashEntry(
  repository: Repository,
  { stashSha, parents, tree }: IStashEntry,
  branchName: string
) {
  const message = `On ${branchName}: ${createDesktopStashMessage(branchName)}`
  const parentArgs = parents.flatMap(p => ['-p', p])
  
  // Create new commit with updated message
  const { stdout: commitId } = await git(
    ['commit-tree', ...parentArgs, '-m', message, '--no-gpg-sign', tree],
    repository.path,
    'moveStashEntryToBranch'
  )
  
  // Store the new stash
  await git(
    ['stash', 'store', '-m', message, commitId.trim()],
    repository.path,
    'moveStashEntryToBranch'
  )
  
  // Delete the old stash
  await dropDesktopStashEntry(repository, stashSha)
}
This allows Desktop to:
  • Move stash when you rename a branch
  • Reassign stash if branch is deleted
  • Keep stash associations accurate

Viewing Stash Contents

See what changes are in a stash:
export async function getStashedFiles(
  repository: Repository,
  stashSha: string
): Promise<ReadonlyArray<CommittedFileChange>> {
  const args = [
    'stash',
    'show',
    stashSha,
    '--raw',
    '--numstat',
    '-z',
    '--format=format:',
    '--no-show-signature',
    '--',
  ]
  
  const { stdout } = await git(args, repository.path, 'getStashedFiles')
  
  return parseRawLogWithNumstat(stdout, stashSha, `${stashSha}^`).files
}
Shows:
  • Files that were modified
  • Added/deleted line counts
  • File status (added, modified, deleted)

Stash Best Practices

Stash before pulling: If you have uncommitted changes and need to pull, stash first to avoid conflicts.
  1. Don’t Stash Too Long
    • Stashes are temporary storage
    • Apply or commit within a day or two
    • Old stashes can cause conflicts
  2. One Stash Per Branch
    • Avoid multiple stashes on same branch
    • Leads to confusion about which to apply
    • Commit instead if work is substantial
  3. Descriptive Stash Messages
    • When creating via CLI, use: git stash push -m "description"
    • Helps identify stash contents later
    • Desktop auto-tags with branch name
  4. Commit Instead When Appropriate
    • If work is meaningful, commit it
    • Stash is for very temporary saves
    • Commits are safer and more permanent
  5. Test After Popping
    • Stashed code might not work in updated codebase
    • Always test after restoring a stash
    • Resolve conflicts carefully

Common Stash Scenarios

Switch Branches Mid-Work

Scenario: Working on feature, need to quickly fix bug

1. Have uncommitted changes on feature-branch
2. Try to switch to main
3. Desktop prompts to stash
4. Select "Stash changes"
5. Fix bug on main, commit, push
6. Switch back to feature-branch
7. Desktop prompts to restore stash
8. Continue working

Pull with Uncommitted Changes

Scenario: Need to pull but have local changes

1. Stash current changes
2. Pull latest changes from remote
3. Restore stash
4. Resolve any conflicts
5. Continue working

Save Work Before Experimenting

Scenario: Want to try something without committing

1. Stash current work
2. Experiment with changes
3. If experiment works: commit it
4. If experiment fails: discard and restore stash

Stash vs. Alternatives

Stash vs. Commit

Use Stash:
  • Changes are incomplete
  • Need to switch contexts quickly
  • Work is experimental
  • Very short-term save (minutes to hours)
Use Commit:
  • Changes are a logical unit
  • Work should be saved permanently
  • Creating checkpoints
  • Longer-term save (hours to days)

Stash vs. WIP Commit

Use Stash:
  • Truly temporary
  • Don’t want commit in history
  • Very quick context switch
Use WIP Commit:
  • Want to push work to remote
  • Need to switch computers
  • Want to track with Git
  • Can amend later

Troubleshooting

If a stash doesn’t show up:
  • It may have been created via CLI without Desktop’s marker
  • Check all stashes with: git stash list
  • Desktop only shows stashes with !!GitHub_Desktop marker
  • CLI-created stashes need to be managed via CLI
If stash won’t apply:
  • Conflicts: Files have changed significantly since stash
  • Deleted files: Files in stash no longer exist
  • Uncommitted changes: Clear working directory first
  • Try applying manually: git stash pop stash@{0}
If you accidentally applied a stash to the wrong branch:
  • Undo with: git reset --hard HEAD (loses changes!)
  • Or commit the changes and cherry-pick to correct branch
  • Better: always check current branch before popping
If stash disappeared after conflicts:
  • Stash is still applied, just deleted from stack
  • Changes are in your working directory
  • Resolve conflicts and commit
  • Cannot re-stash the exact same entry
If you have many stashes:
  • Review with git stash list
  • Drop old ones: git stash drop stash@{n}
  • Or clear all: git stash clear (careful!)
  • Commit or drop - don’t accumulate stashes

Command Line Equivalents

For reference, Desktop’s stash operations map to:
# Create stash
git stash push -m "!!GitHub_Desktop<branch-name>"

# List Desktop stashes
git log -g --grep="!!GitHub_Desktop" refs/stash

# Pop stash  
git stash pop stash@{0}

# Drop stash
git stash drop stash@{0}

# View stash contents
git stash show -p stash@{0}

Build docs developers (and LLMs) love