Re: [PATCH] Add git-save script

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Nanako Shiraishi <nanako3@xxxxxxxxxxxxxx> writes:

> I wrote this little script to save the changes I made, perform the update,
> and then come back to where I was, but on top of the updated commit.
>
> Here is how to use my script:
>
>     $ git save
>     $ git pull
>     $ git save restore

Heh, I earlier said that I'd start looking at the "stash", but
haven't done anything about it so far (I was busy with the day
job).  More honestly, I was being lazy, and sometimes lazy are
rewarded, with a contribution from a new git hacker.

I think this is more or less in the direction I was planning to
head to; thanks for the patch.

Having said that, I have a few comments.  Style, robustness,
and portability.

> diff --git a/git-save.sh b/git-save.sh
> new file mode 100755
> index 0000000..b45652e
> --- /dev/null
> +++ b/git-save.sh
> @@ -0,0 +1,74 @@
> +#! /bin/sh

Style.  "#!/bin/sh" (no space after she-bang).

> +
> +. git-sh-setup
> +require_work_tree
> +
> +function save_work () {

No noiseword "function" for portability.

When you say "#!/bin/sh", you are writing for the family of
generic Bourne shells, not specifically for korn nor bash.  For
example, dash is a fine POSIX shell, but does not grok function
noiseword.  When in doubt, please stay away from things not in
POSIX (e.g. function, [[, ]], ${parameter//pattern/string/}).

> +	save=$( (
> +		i_tree=$(git-write-tree)
> +
> +		TMP=$GIT_DIR/.git-save-tmp
> +		GIT_INDEX_FILE=$TMP
> +		export GIT_INDEX_FILE
> +
> +		git-read-tree $i_tree
> +		git-add -u
> +		w_tree=$(git-write-tree)
> +		rm $TMP
> +		git-read-tree --prefix=base/ HEAD^{tree}
> +		git-read-tree --prefix=indx/ $i_tree
> +		git-read-tree --prefix=work/ $w_tree
> +		git-write-tree
> +		rm $TMP
> +	) )

Hmph.

I see many problems here, although the basic idea is sound and
"interesting".  I especially like the use of three trees in a
single commit, without any parenthood.

 - $GIT_DIR could contain shell metachararcters / whitespace, so
   could $TMP as well.  Always quote such variables, or you risk
   a surprise from "rm".

 - You probably would not want to create a new "save" if your
   working tree and the index are clean.  To test for the
   condition, you can do something like:

	git-diff --quiet --cached && git-diff --quiet

 - I generally prefer cleaning temporary files with "rm -f", and
   also set a trap upon exit, like this:

	# near the beginning of the script
	TMP="$GIT_DIR/.git-save-tmp-$$"
	trap 'rm -f "$TMP"' 0 
	... and then much later ...

	save=$( (
		GIT_INDEX_FILE=$TMP
		export GIT_INDEX_FILE
                ...
	) )

 - I wonder what happens if any of the commands in the above
   sequence errors out.  For example, I think you will get a
   failure from the first git-write-tree if you are in the
   middle of a merge.  You would want to error out as soon as
   you see a problem.

 - Although you keep a separate tree for the index (before the
   "git add -u" to grab the working tree changes) in the saved
   data, it does not seem to be used.  It _might_ make sense to
   replace "git add -u" with "git add ." so that work/ tree
   contains even untracked (but not ignored) files, and on the
   restore side unstage the paths that appear in work/ but not
   in indx/.  I dunno.

> +
> +	head=$(git-log --abbrev-commit --pretty=oneline -n 1 HEAD)
> +	if branch=$(git symbolic-ref -q HEAD); then
> +		branch=${branch#refs/heads/}
> +	else
> +		branch='(no branch)'
> +	fi &&

Minor style.  Please don't write "; then\n".  Line-up "then",
"elif", "else", and "fi"; it is much easier to read that way.

> +	msg=$(printf 'WIP on %s "%s"' "$branch" "$head")
> +
> +	saved=$(printf '%s' "$msg" | git-commit-tree "$save")
> +	git update-ref -m "$msg" refs/heads/saved $saved
> +	printf 'Saved %s\n' "$msg" >&2
> +}
> +
> +function list_save () {
> +	git-log --abbrev-commit --pretty=oneline -g "$@" saved
> +}

I really like the way reflog is used for this.  Your saves do
not necessarily tied to a single branch (so there are no
parent-child relation between each save entry), but you can
still identify what each of the save is about because your log
message has branch name in it.

The sha1 at the beginning of oneline output, although they are
abbreviated, seem not very useful in this context, by the way.
I presume your intended use case is to view "git save list"
output, and say "git save restore saved@{2}" to apply the one
third from the top.  For that use, the commit object name is
irrelevant.  Maybe sed it out like this to make it even
prettier?

	git-log --pretty=oneline -g "$@" saved
        sed -e 's/^[0-9a-f]* //'

> +function show_save () {
> +	save=$(git rev-parse --verify --default saved "$1")
> +	git diff-tree --stat $save:base $save:work
> +}

Nice, but after trying this myself a bit, I seriously wished for
"git save show -p", so I did it myself, like this:

	show_save () {
		flags=$(git rev-parse --no-revs --flags "$@")
		if test -z "$flags"
		then
			flags=--stat
		fi
		save=$(git rev-parse --revs-only --no-flags --default saved "$@")
		git diff-tree $flags $save:base $save:work
	}

It's a dense (and ancient style) plumbing code so needs a bit of
explanation:

 - The first git-rev-parse looks at "$@", discards everything
   that are not options and discards object names.  So 'git save
   show -p some' will give you "-p" in flags.

 - The second one discards flags, and grabs 'some' out of '-p
   some', or defaults to "saved".

> +function restore_save () {
> +	save=$(git rev-parse --verify --default saved "$1")
> +	h_tree=$(git rev-parse --verify $save:base)
> +	i_tree=$(git rev-parse --verify $save:indx)
> +	w_tree=$(git rev-parse --verify $save:work)
> +
> +	git-merge-recursive $h_tree -- HEAD^{tree} $w_tree
> +}

The same "robustness" comments for the save_work function apply
here.  You probably do not want to restore on a dirty tree; the
intended use case is "stash away, pull, then restore", so I
think it is Ok to assume that you will only be restoring on a
clean state (and it would make the implementation simpler).

The three-way merge is done correctly here, and I would imagine
the users would feel the UI quite natural _if_ this merge
conflicts.  "git diff" would show only the conflicted paths
(because the updates coming from the old working tree is placed
in the index for cleanly merged paths), editing a conflicted
file and "git add $that_path" would resolve.  That's exactly the
same workflow for a conflicted merge.

However, I think it is a bit counterintuitive to update the
working tree change to the index if the merge did not conflict.
It might be better to run an equivalent of "git reset", so that
after "git save restore", the user can say "git diff" (not "git
diff HEAD") to view the local changes.  Perhaps...

	if git-merge-recursive ...
	then
        	# Cleanly merged..
		added="$TMP" &&
                git-diff --cached --name-only --diff-filter=A >"$added" &&
		git-read-tree HEAD &&
                git-update-index --add --stdin <"$added" ||
			die "Cannot unstage local modification"
		git-update-index --refresh
	fi

The --diff-filter dance is so that we do not "forget" newly
added files when we do "read-tree HEAD".

> +
> +if [ "$1" = list ]; then
> +	shift
> +	if [ "$#" = 0 ]; then
> +		set x -n 10
> +		shift
> +	fi
> +	list_save "$@"
> +elif [ "$1" = show ]; then
> +	shift
> +	show_save "$@"
> +elif [ "$1" = restore ]; then
> +	shift
> +	restore_save "$@"
> +else
> +	save_work
> +	git reset --hard

I am not absolutely sure if "git reset --hard" belongs here.
You can certainly type one less command in your example sequence
("stash; pull; restore").  But I suspect there may be a case
that would be more useful if "git save" did not do the reset
itself.  I dunno....

> +fi
> +
> +

Style.  This is a perfect place to use "case/esac".

	case "$1" in
	list)
        	do this thing
                ;;
	...
	*)
        	do default thing
                ;;
        esac

Much easier to read, isn't it?

-
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux