« I made a module!Named regex captures »

Thu, Mar 07, 2013

[Icon][Icon]Occasionally helpful stuff

• Post categories: Omni, Programming, Helpful

So you're in the middle of some work on one Git branch, and it's not at all ready to commit. But you suddenly get an urgent interruption. You don't want to pollute your branch with a broken commit; you don't trust stash; you can't throw the work away. What to do?

Answer: Commit the code with a message of "WIP" or something else to indicate the status. Then switch to the other branch, do what you have to. Come back to your in-progress branch, and run

git reset --soft HEAD^

And you reset back to before your WIP commit, but with all the changes you had made staged and ready to resume with.

So you need to make changes to all files that contain a given string. grep will find those files for you, but then you have to edit them. Copy & paste time? No!

grep 'foo' dir/ -r

finds all files containing 'foo' in the dir/ directory. Then

vi `!! -l`

will open them in your editor.

So you have a bash alias set up that saves you lots of time. Say, maybe:

alias quickedit='vi ~/some/file/you/need/a/lot/with/a/long/name.txt'

Now you want to do something that's almost, but not quite, that command. Perhaps

$ vi ~/some/OTHER/file/you/need/a/lot/with/a/long/name.txt

Quick way to do it? Write in the command

$ quickedit

but instead of [enter], press ctrl-meta-e

('meta' will usually be either [alt] or [esc])

This will very helpfully expand your alias into the command that will actually be run by bash. So you go instantly from

$ quickedit


$ vi ~/some/file/you/need/a/lot/with/a/long/name.txt

And now that you have the command right there in your command-line, you can edit it the way you want.

So you want to modify all javascript files that contain vars "foo" and "bar" in your current working directory.

find . -name *.js

will find you all the javascript files.

grep 'var.*foo' . -rl

will find all the files that declare the "foo" variable.

Backticks will get us part of the way there:

grep 'var.*foo' `find . -name *.js` -rl

to find all JS files that declare a "foo" variable. But nesting backticks is Not Fun (TM), so further grepping is going to get tricky.

$() to the rescue!

grep 'var.*foo' $(find . -name *.js) -rl

is equivalent to the previous command. And then:

grep 'var.*bar' $(grep 'var.*foo' $(find . -name *.js) -rl)

finds us all the JS files that declare both 'foo' and 'bar' variables. And then

vi $(grep 'var.*bar' $(grep 'var.*foo' $(find . -name *.js) -rl) -rl)

will find and open for editing all of the files you want.

Fun fun fun.

So you find that you keep wanting to edit the same batch files as you edited in a previous git commit (This happens particularly often when you use the WIP workaround above a lot)

Save the Perl script below to somewhere in your path. I give it the name 'vic'. It has three ways of being called:

$ vic

Called on its own, it opens all the files from your last commit.

$ vic d

Called with the single argument 'd', it opens all the files that have uncommitted changes, i.e. the ones that are making your checkout dirty.

$ vic <sha>

Paste in any recognizable commit SHA, and it will open all the same files that were edited in that commit.

This command is one I regularly find to be a godsend, it saves me a massive amount of copy&pasting.

#!/usr/bin/env perl

use strict;
use warnings;

my $arg = $ARGV[0] || '';
my @files;

# If 'd' was passed in, get the files that make the current repo dirty
if ($arg eq 'd') {
    # We only need the 'short' output - basically the list of files
    my @gitstat = `git status -s`;

    # Filter down to just the filenames we want - basically, the second 'argument'
    @files = map { s/^\s*\S*\s+(\S+)/$1/; $_; } @gitstat;
else {
    # Get last/specified git commit
    my @gitstat = `git show --stat $arg`;

    # Filter down to just the filenames we want
    # Filenames are in the format ' <name> | <number> +-
    @files = map { s#^\s+(\S+)\s.*#$1#; $_ } grep { m#^\s\S+\s+\|\s+\d+\s+[+-]+# } @gitstat;

# Don't go crazy on big commits
if (my $num = scalar @files > 9) {
    say "There are $num files, are you sure you want to do this?";
    my $answer = <>;
    exit unless $answer =~ m#y#i;

# Open the files in vi
my $files;
map { chomp $_; $files .= "$_ "; } @files;
exec("vi $files");


Comment from: dams [Visitor] · http://damien.krotkine.com
You really need to know about git stash ...
08/03/13 @ 12:39
Comment from: oneandoneis2 [Member] · http://geekblog.oneandoneis2.org/
I think you missed the "you don't trust stash" comment.

I know about it, I just don't trust it for anything other than immediate storage - stash; pull; unstash.

Putting real work into stash and then leaving it for hours or days to work elsewhere? No. That's insane.
08/03/13 @ 12:42
Comment from: Prakash [Visitor] · http://kailasa.net
As far as I know (which isn't much) about git, both stashes and commits are managed by git, except that stashes are not part of the commit history. Could you explain the reason why you would trust the stashes less than commits?
08/03/13 @ 15:05
Comment from: oneandoneis2 [Member] · http://geekblog.oneandoneis2.org/
One big reason was the day I watched a co-worker cheerfully use "git stash clean" because his stash list was so cluttered; then realise he'd just nuked hours of work and had no idea how to get any of it back. It's a lot easier to recover a lost commit than a lost stash.

More pragmatically, a WIP-commit as described is gauranteed to be not just in git's history, but also on the right branch and attached to the correct parent sha. A stash can leave you in the position of "I know I did this work, I can't remember where" and applying a stash to the wrong branch/commit. Which is a pain.

Mostly, it's simply a case of "Use the right tool for the right job" - if you're using the stash instead of commits for long-term storage, you're using a hammer to pound in screws.
08/03/13 @ 16:10
Darren Duncan
Comment from: Darren Duncan [Visitor] · http://muldis.com
Depending on the situation, sometimes the best way to handle the Git problem mentioned above is to just leave your existing checkout alone, and create another checkout directory from the same repository, as if you were a second user, do your work there and commit as usual, then update the original working directory. Git lets you do that I believe, have multiple working directories for the same local repository.
10/03/13 @ 00:59
Bruce Dawson
Comment from: Bruce Dawson [Visitor] · http://randomascii.wordpress.com
You say "$(find . -name *.js)" but the *.js part needs to be in quotes. That is it needs to be:

$(find . -name "*.js")

Otherwise the shell expands *.js and you can only find files that match file names in the current directory. Paste error I assume.
01/04/13 @ 22:23

[Links][icon] My links

[Icon][Icon]About Me

[Icon][Icon]About this blog

[Icon][Icon]My /. profile

[Icon][Icon]My Wishlist


[FSF Associate Member]

April 2014
Mon Tue Wed Thu Fri Sat Sun
 << <   > >>
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30        


User tools

XML Feeds

eXTReMe Tracker

Valid XHTML 1.0 Transitional

Valid CSS!

[Valid RSS feed]

blog soft