threads / rfc / 17677

[RFC] New command: 'git snapshot'.

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

## tl;dr

16 messages between Feb 9, 2009 and Feb 11, 2009.

replies: 15people: 9as markdown or json

Fabio Augusto Dal Castel· Feb 9, 2009, 18:54 UTC · lore
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· Feb 9, 2009, 19:52 UTC · re: Fabio Augusto Dal Castel · lore

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

On Monday 09 February 2009 19:54, Fabio Augusto Dal Castel wrote:
Show 10 quoted lines
> 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.

Show 9 quoted lines
> 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· Feb 9, 2009, 22:36 UTC · re: Fabio Augusto Dal Castel · lore

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

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· Feb 10, 2009, 04:51 UTC · re: Brandon Casey · lore

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

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· Feb 10, 2009, 19:47 UTC · re: Sitaram Chamarty · lore

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

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· Feb 10, 2009, 20:31 UTC · re: Jon Loeliger · lore

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

Jon Loeliger <jdl@freescale.com> writes:
Show 6 quoted lines
> 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.

Sitaram Chamarty· Feb 11, 2009, 01:22 UTC · re: Jon Loeliger · lore

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

On 2009-02-10, Jon Loeliger <jdl@freescale.com> wrote:
Show 6 quoted lines
> 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... :-)

Fabio Augusto Dal Castel· Feb 10, 2009, 20:40 UTC · re: Brandon Casey · lore

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

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.
Jeff King· Feb 10, 2009, 23:00 UTC · re: Fabio Augusto Dal Castel · lore

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

On Tue, Feb 10, 2009 at 06:40:34PM -0200, Fabio Augusto Dal Castel wrote:
Show 10 quoted lines
> > 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· Feb 10, 2009, 23:08 UTC · re: Jeff King · lore

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

Jeff King <peff@peff.net> writes:
Show 15 quoted lines
> 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· Feb 10, 2009, 23:38 UTC · re: Junio C Hamano · lore

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

On Tue, Feb 10, 2009 at 03:08:31PM -0800, Junio C Hamano wrote:
Show 10 quoted lines
> >> 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· Feb 10, 2009, 23:39 UTC · re: Junio C Hamano · lore

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

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
Jeff King· Feb 11, 2009, 13:43 UTC · re: Geoffrey Lee · lore

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

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
Matthieu Moy· Feb 11, 2009, 09:04 UTC · re: Jeff King · lore

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

Jeff King <peff@peff.net> writes:
Show 10 quoted lines
> 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
Fabio Augusto Dal Castel· Feb 11, 2009, 20:40 UTC · re: Matthieu Moy · lore

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

> 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.

Fabio Augusto Dal Castel· Feb 10, 2009, 20:48 UTC · lore

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

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.

← back to recent threads