# [RFC] New command: 'git snapshot'.

16 messages from 2009-02-09 to 2009-02-11. Participants: Fabio Augusto Dal Castel, Giuseppe Bilotta, Brandon Casey, Sitaram Chamarty, Jon Loeliger, Junio C Hamano, Jeff King, Geoffrey Lee, Matthieu Moy.
Thread: https://gitlist.dev/t/17677

## Fabio Augusto Dal Castel, 2009-02-09 18:54

Subject: [RFC] New command: 'git snapshot'.
Message-ID: <38cfbb550902091054u78f2e706u67752b4dc9de6c3b@mail.gmail.com>
URL: https://gitlist.dev/e/38cfbb550902091054u78f2e706u67752b4dc9de6c3b%40mail.gmail.com

```
Abstract: Requesting suggestions for a new command to save the current
working dir state without collateral effects.


Q. "Why another command? We already have stash!"

A. Stash was always subject of many controversies. Trying to stay
apart from near-religious debates like

* whether the syntax should be "dangerous" [sic] or "newbie friendly";
* whether untracked files should be stashed or not;
* whether stashes should expire or not

the fact is that different users have different needs and those
discussions arise when trying to apply different behaviors to same
command. Stash was always oriented to a 'pull into a dirty tree'
solution, and serves this purpose very well.

So, I propose a new 'git snapshot' command to use when 'git stash'
behaviour is not exactly what user needs.


Q. Why on earth would someone want this instead of our lovely stash?

A. Sometimes what we want is just a (you bet) "snapshot" of working
dir. Something like "Just remember. Do not touch."

In the excellent paper "Git From the Bottom Up" John Wiegley suggests
a "git-snapshot" script to be used in a cron job -- nothing more than:

git stash && git stash apply

However, this is not an optimal solution for a 'real' snapshot:

* It makes TWO unnecessary changes to working dir (to HEAD and back
again). Besides the heavier disk usage, this could, for example,
cause an editor using inotify to think that the current file was
externally changed by another program and annoy the user asking if he
wants to load the new content.

* It will not save untracked files. An user counting with periodic
snapshots and working furiously for three days may be disappointed (to
say the least) when discover that a significant part of his work were
NOT saved in history because contents actually were in new (untracked)
files.

* Stashes expire (already discussed in
http://thread.gmane.org/gmane.comp.version-control.git/84665 )

In resume, all that 'git snapshot' would NOT do.


Q. What are the differences between 'git stash' and 'git snapshot'?

A.

git stash                         git snapshot

temporary/short-term              permanent/long-term
reflog-based                      branch-based
applies a "git reset --hard"      leaves working dir / index untouched
does not stash untracked files    snapshots ALL files (except ignored)


Q. How it works?

A.[What follows is a textual description of my current implementation.
Of course, there is nothing carved in stone: suggestions and comments
are MORE than welcome.]

All snapshots are stored in a special branch ("<branch>_snapshots").
So, if you are on 'master' branch, a 'git snapshot' will create/use a
'master_snapshots' branch.

If anything differs from last snapshot (a change into index, into a
file, or a new untracked file) the command will:

1. Save the current index state;
2. Add untracked/updated files to index;
3. Create a new commit for the current state in snapshots branch;
4. Update the HEAD of snapshots branch to this new commit;
5. Restore the original index state (of step 1).

However, if the current state of working dir is equal to the last
snapshot taken, there is no need to make another identical copy and
the command will just exit.

The command would not work if the current branch is a detached head.
It is by design, but just because I had a no better idea of what to do
in this case <g>.

Typical usage:

	(hack hack hack)
	git-snapshot
	(hack hack hack)
	git-snapshot
	(...)

Open questions / To-do list:

- How to 'rollback' to a specific snapshot?
  - Make a 'git-rollback <snapshot>' ? It would:
   - Apply diffs between <snapshot> and current head (possible loss!).
   - Restore the original index (where to save it? 'Hidden' commit?)


Best Regards,

Fabio.





diff --git a/git-snapshot.sh b/git-snapshot.sh
new file mode 100644
index 0000000..5f19a6f
--- /dev/null
+++ b/git-snapshot.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 F.D.Castel.
+#
+
+. git-sh-setup
+require_work_tree
+cd_to_toplevel
+
+# Get current branch.
+current_branch=$(git symbolic-ref -q HEAD | sed -e 's|^refs/heads/||')
+test -z "$current_branch" &&
+	die 'fatal: Cannot take snapshot from a detached HEAD.'
+	
+# Save the current index state.
+original_index=$(git write-tree) ||
+	die "fatal: Error saving original index state."
+
+# Create commit message describing the changes.
+temp_file="$TMP/.git-snapshot"
+(
+	date -R
+	printf '\n* New files:\n'		# ToDo: How to get only untracked? (without added)
+	git ls-files --full-name -o
+	printf '\n* Changed files:\n'
+	git ls-files --full-name -m		# ToDo: How to get only modified?
(without deleted)
+	printf '\n* Deleted files:\n'
+	git ls-files --full-name -d
+) > "${temp_file}.message"
+
+# Add untracked/updated files to index.
+git add --all . ||
+	die "fatal: Error adding current state to index."
+
+# Set clean up trap (restore original index and delete temp files on exit).
+trap "git read-tree $original_index && rm -f '$temp_file.*'" 0
+
+# Create snapshots branch (if needed).
+snapshots_branch="${current_branch}_snapshots"
+git show-ref --verify --quiet -- "refs/heads/$snapshots_branch" ||
+	git branch $snapshots_branch
+
+# Compare changes with last snapshot.
+git diff --exit-code --raw --cached $snapshots_branch &&
+	die 'Nothing to do: no changes since the last snapshot.'
+
+# Create a new commit for the current state in snapshots branch.
+new_index=$(git write-tree) &&
+	snapshots_head=$(git rev-parse --verify $snapshots_branch) &&
+	new_commit=$(cat ${temp_file}.message | git commit-tree $new_index
-p $snapshots_head) ||
+		die "fatal: Error commiting current state into snapshots branch."
+
+# ToDo: Where to store the original index (for a future 'rollback')?
+#original_index_commit=$(printf '(original index for child commit)' |
git commit-tree $original_index)
+
+# Update the HEAD of snapshots branch to this new commit.
+git symbolic-ref HEAD refs/heads/$snapshots_branch &&
+	git update-ref -m "'Snapshotting $current_branch to $new_commit'"
HEAD $new_commit $snapshots_head &&
+	git symbolic-ref HEAD refs/heads/$current_branch ||
+		die "fatal: Error updating HEAD of snapshots branch."
+		
\ No newline at end of file

```

## Giuseppe Bilotta, 2009-02-09 19:52

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <gmq1hu$ccn$1@ger.gmane.org>
URL: https://gitlist.dev/e/gmq1hu%24ccn%241%40ger.gmane.org
In-Reply-To: <38cfbb550902091054u78f2e706u67752b4dc9de6c3b@mail.gmail.com>

```
On Monday 09 February 2009 19:54, Fabio Augusto Dal Castel wrote:

> Q. What are the differences between 'git stash' and 'git snapshot'?
> 
> A.
> 
> git stash                         git snapshot
> 
> temporary/short-term              permanent/long-term
> reflog-based                      branch-based
> applies a "git reset --hard"      leaves working dir / index untouched
> does not stash untracked files    snapshots ALL files (except ignored)

I like this snapshot idea, and I clearly see the difference
between this and stash.

For example, I use stash when I want to move away from the current
hacking because a new, more urgent change must be done somewhere
else.

Instead, I see a usecase for git snapshot for progressive
temporary snapshot while working towards a more complex feature
while needing temporary intermediate checkpoints: an effect
similar to what I currently achieve using git commit (a first
time) and git commit --amend as my work progresses.

In this respect, I wouldn't agree with the first difference you
remarked, but that's just the usecase I have in mind.

> Q. How it works?
> 
> A.[What follows is a textual description of my current implementation.
> Of course, there is nothing carved in stone: suggestions and comments
> are MORE than welcome.]
> 
> All snapshots are stored in a special branch ("<branch>_snapshots").
> So, if you are on 'master' branch, a 'git snapshot' will create/use a
> 'master_snapshots' branch.

I'm not sure I like the idea of creating these branches with these
branchnames. What about using another refs/ subtree? So
refs/snapshots/somebranchname would contain the snapshot paired
with refs/heads/somebranchname.

-- 
Giuseppe "Oblomov" Bilotta

```

## Brandon Casey, 2009-02-09 22:36

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <etsYQzEDjdk-_NxhvO3i6EyShR6eZ202GBdQx7ZZpPHH5iNfWiuV6g@cipher.nrlssc.navy.mil>
URL: https://gitlist.dev/e/etsYQzEDjdk-_NxhvO3i6EyShR6eZ202GBdQx7ZZpPHH5iNfWiuV6g%40cipher.nrlssc.navy.mil
In-Reply-To: <38cfbb550902091054u78f2e706u67752b4dc9de6c3b@mail.gmail.com>

```
Fabio Augusto Dal Castel wrote:

> * Stashes expire (already discussed in
> http://thread.gmane.org/gmane.comp.version-control.git/84665 )

Correction: stash expiration is now configurable and does not expire by
            default thanks to Junio.

Not sure if I'd use this snapshot tool, but per-branch stash would
probably be useful.  If stashes were per-branch, then it would probably
be pretty easy to build this snapshot tool on top of it.

-brandon

```

## Sitaram Chamarty, 2009-02-10 04:51

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <slrngp21uj.i22.sitaramc@sitaramc.homelinux.net>
URL: https://gitlist.dev/e/slrngp21uj.i22.sitaramc%40sitaramc.homelinux.net
In-Reply-To: <etsYQzEDjdk-_NxhvO3i6EyShR6eZ202GBdQx7ZZpPHH5iNfWiuV6g@cipher.nrlssc.navy.mil>

```
On 2009-02-09, Brandon Casey <casey@nrlssc.navy.mil> wrote:

> Not sure if I'd use this snapshot tool, but per-branch stash would
> probably be useful.  If stashes were per-branch, then it would probably
> be pretty easy to build this snapshot tool on top of it.

I use cross-branch stashes all the time.  Stash it here, go
there, and pop the stash.  I hope that does not change :-)

```

## Jon Loeliger, 2009-02-10 19:47

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <1234295272.10335.26.camel@ld0161-tx32>
URL: https://gitlist.dev/e/1234295272.10335.26.camel%40ld0161-tx32
In-Reply-To: <slrngp21uj.i22.sitaramc@sitaramc.homelinux.net>

```
On Tue, 2009-02-10 at 04:51 +0000, Sitaram Chamarty wrote:

> I use cross-branch stashes all the time.  Stash it here, go
> there, and pop the stash.  I hope that does not change :-)

Perhaps 'git checkout -m other_branch'?

jdl

```

## Junio C Hamano, 2009-02-10 20:31

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <7v7i3ym3tr.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7v7i3ym3tr.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <1234295272.10335.26.camel@ld0161-tx32>

```
Jon Loeliger <jdl@freescale.com> writes:

> On Tue, 2009-02-10 at 04:51 +0000, Sitaram Chamarty wrote:
>
>> I use cross-branch stashes all the time.  Stash it here, go
>> there, and pop the stash.  I hope that does not change :-)
>
> Perhaps 'git checkout -m other_branch'?

Sure, or even "git checkout other_branch" without -m.

"Stash it here, go there, and pop the stash" is what you would use when
you have many changes that you _know_ "checkout -m" will run a 3-way merge
to produce a heavy conflict, and you suspect you may need to be able to
retry the unstashing after taking a break.

If you are lucky and manage to resolve the conflicts easily (or did not
even get conflicts when applying), then pop will drop the stash and you
have everything you want in your index and the work tree.  Otherwise, your
work tree would be a mess, and you may have to "reset --hard" out to start
from scratch, but then the stash will still be there.

```

## Fabio Augusto Dal Castel, 2009-02-10 20:40

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <38cfbb550902101240x1202c592ra7eb01d66e22da43@mail.gmail.com>
URL: https://gitlist.dev/e/38cfbb550902101240x1202c592ra7eb01d66e22da43%40mail.gmail.com
In-Reply-To: <etsYQzEDjdk-_NxhvO3i6EyShR6eZ202GBdQx7ZZpPHH5iNfWiuV6g@cipher.nrlssc.navy.mil>

```
Brandon,

> If stashes were per-branch, then it would probably
> be pretty easy to build this snapshot tool on top of it.

Or the other way around <g>.

Remember that 'stash' is actually TWO commands in one:
* Save current state
* Reset to HEAD

My primary reason to use snapshots is to AVOID the second step.

```

## Fabio Augusto Dal Castel, 2009-02-10 20:48

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <38cfbb550902101248v16f0f0f0v6a48cabf498317cf@mail.gmail.com>
URL: https://gitlist.dev/e/38cfbb550902101248v16f0f0f0v6a48cabf498317cf%40mail.gmail.com
In-Reply-To: <38cfbb550902101232l4c83b6dfjc70e1e2f79a8c3c1@mail.gmail.com>

```
Giuseppe

> I use stash when I want to move away from the current
> hacking because a new, more urgent change must be done
> somewhere else

Yes. That is exactly what stash is for. Temporary fixes. Now...


> Instead, I see a usecase for git snapshot for progressive
> temporary snapshot while working towards a more complex feature
> while needing temporary intermediate checkpoints

That's the idea. You could take a snapshot automatically every x
minutes. For example: I'm testing it taking a snapshot on every
build/compile of my project (and it is going fine, BTW).


> similar to what I currently achieve using git commit (a first
> time) and git commit --amend as my work progresses.

Except that, in this solution, you have only ONE saved state.
Also, it needs to be done manually. I wanted something automatic (like
John Wiegley's sugestion)



> In this respect, I wouldn't agree with the first difference you
> remarked, but that's just the usecase I have in mind.

Making another analogy: I see stash like a stack (you push/stash, and
after you pop/apply). And I don't see stacks as a good long-term
storages <g> (ok, you CAN 'cheat' and see all items other than the
first)

Snapshots would be like a queue: You can keep it entirely, or you can
keep only the last 'n' interesting snapshots, removing the others.




> I'm not sure I like the idea of creating these branches with these
> branchnames. What about using another refs/ subtree?

I'm also not sure <g>. The idea of refs/ is a good one, too (and could
solve the problem of 'where to store the original index?'). But my
first idea of using branches was to avoid a load of another
'maintenance' commands ('snapshot list', 'snapshot delete', etc) and
to use a more known facility (branches).



Best regards,

Fabio.

```

## Jeff King, 2009-02-10 23:00

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <20090210230054.GD26954@coredump.intra.peff.net>
URL: https://gitlist.dev/e/20090210230054.GD26954%40coredump.intra.peff.net
In-Reply-To: <38cfbb550902101240x1202c592ra7eb01d66e22da43@mail.gmail.com>

```
On Tue, Feb 10, 2009 at 06:40:34PM -0200, Fabio Augusto Dal Castel wrote:

> > If stashes were per-branch, then it would probably
> > be pretty easy to build this snapshot tool on top of it.
> 
> Or the other way around <g>.
> 
> Remember that 'stash' is actually TWO commands in one:
> * Save current state
> * Reset to HEAD
> 
> My primary reason to use snapshots is to AVOID the second step.

Doesn't that argue for "git stash --no-reset" or similar instead of a
separate command?

-Peff

```

## Junio C Hamano, 2009-02-10 23:08

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <7vy6wdkhzk.fsf@gitster.siamese.dyndns.org>
URL: https://gitlist.dev/e/7vy6wdkhzk.fsf%40gitster.siamese.dyndns.org
In-Reply-To: <20090210230054.GD26954@coredump.intra.peff.net>

```
Jeff King <peff@peff.net> writes:

> On Tue, Feb 10, 2009 at 06:40:34PM -0200, Fabio Augusto Dal Castel wrote:
>
>> > If stashes were per-branch, then it would probably
>> > be pretty easy to build this snapshot tool on top of it.
>> 
>> Or the other way around <g>.
>> 
>> Remember that 'stash' is actually TWO commands in one:
>> * Save current state
>> * Reset to HEAD
>> 
>> My primary reason to use snapshots is to AVOID the second step.
>
> Doesn't that argue for "git stash --no-reset" or similar instead of a
> separate command?

How is it different from "git stash create"?

```

## Jeff King, 2009-02-10 23:38

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <20090210233801.GA9617@coredump.intra.peff.net>
URL: https://gitlist.dev/e/20090210233801.GA9617%40coredump.intra.peff.net
In-Reply-To: <7vy6wdkhzk.fsf@gitster.siamese.dyndns.org>

```
On Tue, Feb 10, 2009 at 03:08:31PM -0800, Junio C Hamano wrote:

> >> Remember that 'stash' is actually TWO commands in one:
> >> * Save current state
> >> * Reset to HEAD
> >> 
> >> My primary reason to use snapshots is to AVOID the second step.
> >
> > Doesn't that argue for "git stash --no-reset" or similar instead of a
> > separate command?
> 
> How is it different from "git stash create"?

According to the man page, "git stash create" doesn't even store it in a
ref. I think the point would be to store it in a ref somewhere (as "git
stash save" does), but not do the reset.

But I have never once used "git stash create", so maybe I am
misunderstanding it.

-Peff

```

## Geoffrey Lee, 2009-02-10 23:39

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <83d7aaa40902101539m3c40deeeo2d452f6dbb7c379c@mail.gmail.com>
URL: https://gitlist.dev/e/83d7aaa40902101539m3c40deeeo2d452f6dbb7c379c%40mail.gmail.com
In-Reply-To: <7vy6wdkhzk.fsf@gitster.siamese.dyndns.org>

```
On Tue, Feb 10, 2009 at 3:08 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Jeff King <peff@peff.net> writes:
> How is it different from "git stash create"?

Git stash doesn't touch untracked files, whereas git snapshot would.
Take another closer look at the table in the original post titled
"What are the differences between 'git stash' and 'git snapshot'?"

-Geoffrey Lee

```

## Sitaram Chamarty, 2009-02-11 01:22

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <slrngp4a3b.ual.sitaramc@sitaramc.homelinux.net>
URL: https://gitlist.dev/e/slrngp4a3b.ual.sitaramc%40sitaramc.homelinux.net
In-Reply-To: <1234295272.10335.26.camel@ld0161-tx32>

```
On 2009-02-10, Jon Loeliger <jdl@freescale.com> wrote:
> On Tue, 2009-02-10 at 04:51 +0000, Sitaram Chamarty wrote:
>
>> I use cross-branch stashes all the time.  Stash it here, go
>> there, and pop the stash.  I hope that does not change :-)
>
> Perhaps 'git checkout -m other_branch'?

Aaah -- thanks!  I should have known there'd be an easier
way... :-)

```

## Matthieu Moy, 2009-02-11 09:04

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <vpq3aelcpjk.fsf@bauges.imag.fr>
URL: https://gitlist.dev/e/vpq3aelcpjk.fsf%40bauges.imag.fr
In-Reply-To: <20090210230054.GD26954@coredump.intra.peff.net>

```
Jeff King <peff@peff.net> writes:

> On Tue, Feb 10, 2009 at 06:40:34PM -0200, Fabio Augusto Dal Castel wrote:
>
>> Remember that 'stash' is actually TWO commands in one:
>> * Save current state
>> * Reset to HEAD
>> 
>> My primary reason to use snapshots is to AVOID the second step.
>
> Doesn't that argue for "git stash --no-reset" or similar instead of a
> separate command?

I also think adding options to "git stash" would be better than
creating a new command. The "git has too many commands" is already one
of the blocking factors for newcommers.

And indeed, I don't think the choice in the comparison table between
stash and snapshot should be all-or-nothing. There could be individual
options like --save-untracked, --per-branch, ... (--no-reset would
probably be redundant with stash create, but maybe stash create needs
a --keep-object-somewhere-in-a-reference like option). Then, having
"git snapshot" would just be a matter of creating the accurate alias.

-- 
Matthieu

```

## Jeff King, 2009-02-11 13:43

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <20090211134322.GB19223@coredump.intra.peff.net>
URL: https://gitlist.dev/e/20090211134322.GB19223%40coredump.intra.peff.net
In-Reply-To: <83d7aaa40902101539m3c40deeeo2d452f6dbb7c379c@mail.gmail.com>

```
On Tue, Feb 10, 2009 at 03:39:19PM -0800, Geoffrey Lee wrote:

> Git stash doesn't touch untracked files, whereas git snapshot would.
> Take another closer look at the table in the original post titled
> "What are the differences between 'git stash' and 'git snapshot'?"

Sure, I was just responding to that particular statement about reset.
But I think it generalizes. Why not "--untracked" as an option?

In other words, there are several behaviors that people might not like
about stash, and I think they can be combined in multiple ways. So one
solution is to make another command which chooses a different set of
behaviors. But what about the person who wants "--untracked" but not
"--no-reset"? Do they make a third command?

So it is much more flexible to make orthogonal switches that can be
turned on and off independently. And of course if you have a workflow
which always uses a particular set of switches, it is convenient to hide
it behind an alias.  And if there are just a few workflows that are
common to a lot of people, those can graduate to become git commands.

But this proposal seems to be starting in the opposite direction, with a
new command that is closely related to stash but changes a few
behaviors. I haven't seen a convincing argument that between stash and
snapshot, git will now serve all or most people's workflows (and we
don't need another command that does something in between).

-Peff

```

## Fabio Augusto Dal Castel, 2009-02-11 20:40

Subject: Re: [RFC] New command: 'git snapshot'.
Message-ID: <38cfbb550902111240v6e593bfw5c8347d92fe8f767@mail.gmail.com>
URL: https://gitlist.dev/e/38cfbb550902111240v6e593bfw5c8347d92fe8f767%40mail.gmail.com
In-Reply-To: <vpq3aelcpjk.fsf@bauges.imag.fr>

```
> Doesn't that argue for "git stash --no-reset" or similar instead of a
> separate command?

Yes. And also for an "--untracked" (as already suggested).

Since stashes does not expire anymore (as correctly pointed by
Brandon), a snapshot could be reduced to an alias for:

git stash --no-reset --untracked

(except for the branch storage)


However, the rationale behind a new command was also to avoid the
'loss of identity' of stash (as currently implemented). I always saw
stash as a way to allow a temporary hack or a pull. If we start adding
a lot of switches into stash that ultimately would change its main
purpose, should it yet be called 'stash'? (something like a 'git
commit --no-commit' ?)

(Please, don't get me wrong: I'm just raising food for thoughts, here)

Maybe the 'stash' command and multiples switches would be more
appropriate if 'reset' was NOT the default behavior. Something like:

git stash [--untracked] [--reset]

where the current 'git stash' would be 'git stash --reset'.Of course,
this would be a significant breaking change.

I know... I know...  "Heresy!" You'd say... <g>

But... what about it? Why, after all, stash MUST do a reset?

"Do one thing. Do it well"?

Regards!
Fabio.

```
