xargs (or something else) without space before parameter - linux

I would like to execute something like this (git squash):
git rebase -i HEAD~3
extracting the 3 from git log:
git log | blabla | xargs git rebase -i HEAD~
This does not work because xargs inserts a space after HEAD~.
The problem is that I want to alias this command, so I cannot just use
git rebase -i HEAD~`git log | blabla`
because the number would be evaluated just when I define the alias.
I don't have to use xargs, I just need an alias (preferably not a function).

You can use the -I option of xargs:
git log | blabla | xargs -I% git rebase -i HEAD~%

Try this:
git log | blabla | xargs -i bash -c 'git rebase -i HEAD~{}'

Related

Git: speed up this command for searching Git blame for todos

I'm using this command:
git ls-tree -r --name-only HEAD -- . | grep -E '\.(ts|tsx|js|jsx|css|scss|html)$' | xargs -n1 git blame -c -e | sed -E 's/^.+\.com>\s+//' | LC_ALL=C grep -F 'todo: ' | sort
This gets all the todos in my codebase, sorted by date. This is mostly from Use git to list TODOs in code sorted by date introduced, I'm not very good with the command line.
However, the grep 'todo: ' part takes a long time. It takes 1min for ~400 files, without any particularly large files. Is it possible to speed this up somehow?
Edit: I realized it's the git blame that's slow, not grep, so I did a search before running git blame:
git ls-tree -r --name-only HEAD -- . | grep -E '\\.(ts|tsx|js|jsx|css|scss|html)$' | LC_ALL=C xargs grep -F -l 'todo: ' | xargs -n1 git blame -c -e | sed -E 's/^.+\\.com>\\s+//' | LC_ALL=C grep -F 'todo: ' | sort
Now it's 6s.

Diffrence between $(git ls-files -s | wc -l ) and $(git ls-files -s >out && wc -l <out)

Are the two commands $(git ls-files -s | wc -l) and $(git ls-files -s >out && wc -l <out) different or same,
as when first is written in the second form, i end up getting errors.
When you pipe the output of one program into the input of another, as in:
$(git ls-files -s | wc -l)
...the programs run concurrently. wc will start counting lines as soon as it receives them. The pipe also directs the output of git to the input of wc without any intermediate file.
Note that in this case, wc will run even if the git command fails for some reason, so you'll get the wc output (in most cases, 0).
In your second example:
$(git ls-files -s >out && wc -l <out)
...the git command runs first, and stores its results in a file called out. Then, if that was successful, wc runs and counts the lines. Because of &&, if the git command fails, wc won't run at all. In either case, you'll have a file named out laying around with the results of the git command in it.
Piping is generally better; it'll run faster and if you don't need to keep the intermediate results, it won't have any side effects.

Is there a 'git sed' or equivalent?

Let's say I want to rename a method in source code contained in a git repository. I could do this by hand, but the method name might be in multiple places (e.g., unit test, documentation, actual method). To check where the method is used, I use 'git grep'. I get 'git grep' to show only lines that I want to change, and then I don't have a workflow to automatically change those lines.
I'm looking for an automated way (hopefully using git tools) to do this last step. I was hoping there was some sort of 'git sed' or equivalent, but I can't find any.
The interface I'm thinking would be nice: git sed 's/old-method-name/new-method-name/g'
You could use git ls-files in combination with xargs and sed:
git ls-files -z | xargs -0 sed -i -e 's/old-method-name/new-method-name/g'
Thanks to both Noufal and Greg for their posts. I combined their solutions, and found one that uses git grep (more robust than git ls-files for my repo, as it seems to list only the files that have actual src code in them - not submodule folders for example), and also has the old method name and new method name in only one place:
In the [alias] block of my ~/.gitconfig file:
sed = ! git grep -z --full-name -l '.' | xargs -0 sed -i -e
To use:
git sed 's/old-method-name/new-method-name/ig'
You could do a
for i in $(git grep --full-name -l old_method_name)
do
perl -p -i -e 's/old_method_name/new_method_name/g' $i
done
stick that in a file somewhere and then alias it as git sed in your config.
Update: The comment by tchrist below is a much better solution since it prevents perl from spawning repeatedly.
Here's a solution that combines those of of Noufal and claytontstanley and avoids touching files that won't change.
In the [alias] block of my ~/.gitconfig file:
psed = !sh -c 'git grep --null --full-name --name-only --perl-regexp -e \"$1\" | xargs -0 perl -i -p -e \"s/$1/$2/g\"' -
To use:
git psed old_method_name new_method_name
Yes, there's. In Ubuntu the package git-extras provides the command. Install it:
$ sudo apt-get install git-extras
Use it like bellow e.g. to correct a spelling issue quickly:
$ git sed 'qoute' 'quote'
Unfortunately it doesn't support file filters like what git grep does:
$ git grep -e 'class' -- '*.py'
The same functionality also exists on Mac and other operating systems. Checkout its installation page.
Unhappy with most other solutions provided (which is basically just a string-replace on git tracked files) I wrote my own script: git-sed.
It supports any expression sed supports (e.g git sed '1{/^$/d}')
Can run on a subset of paths in the repo (git sed 's/foo/bar' src tests)
Multiple expressions (git sed -e 's/foo/bar' -e '/bar/d').
etc...
Just drop it anywhere in PATH to use it or add an alias pointing to the full path.
Note that starting git 2.1 (Q3 2014), you can set "full-name" by default for git grep.
(See commit 6453f7b by Andreas Schwab)
"git grep" learned grep.fullname configuration variable to force "--full-name" to be default.
This may cause regressions on scripted users that do not expect this new behaviour.
That means the previous solutions can benefit from:
git config grep.full-name true
And use:
psed = !sh -c 'git grep --null --name-only --perl-regexp -e \"$1\" | xargs -0 perl -i -p -e \"s/$1/$2/g\"' -
See git-search-replace on github - it's designed for this exactly.
I have written a git sed which supports file filtering:
#!/bin/bash
split=$(($# + 1))
for i in $(seq 1 $#); do
if [[ "${!i}" = "--" ]]; then
split=$i
fi
done
git ls-files -z "${#:$split:$#}" | xargs -0 sed -b -i "${#:1:$(($split - 1))}"
(You probably don't want the -b parameter on non-Windows platforms; it's necessary on Windows to preserve Windows-style newlines.)
You can then add an alias in your .gitconfig:
[alias]
sed = ! <path to git-sed>
so that you can use it like git sed -e <your expression> -- <path filter>.

Can't add a file separated with space to git

I have been writing a script to add untracked files using git add .
The loop I use in my script is
for FILE in $(git ls-files -o --exclude-standard); do
git add $FILE
git commit -m "Added $FILE"
git push origin master
done
The script runs fine till it faces a filename which has space in it. for Eg., I cant add the file Hello 22.mp4.(Note that there is a SPACE between Hello and 22). The above loop would take the file as 2 separate files, Hello and 22.mp4 and exit with error.
Does someone know how to add it as a single file?
Thanks
What's happening is the shell is expanding the $(...) into a bunch of words, and it's obviously interpreting a file with spaces embedded as multiple files obviously. Even with the prior suggestions of quoting the git add command, it wouldn't work. So the loop is getting run with wrong arguments, as shown by this output with set -x:
ubuntu#up:~/test$ ls -1
a a
ubuntu#up:~/test$ set -x; for FILE in $(git ls-files -o --exclude-standard); do git add "$FILE"; git commit -m "Added $FILE"; done
+ set -x
++ git ls-files -o --exclude-standard
+ for FILE in '$(git ls-files -o --exclude-standard)'
+ git add a
...
The proper solution is to quote the git add $file and have git ls-files NULL separate the filenames by passing -z to git ls-files and use a while loop with a null delimiter:
git ls-files -o --exclude-standard -z | while read -r -d '' file; do
git add "$file"
git commit -m "Added $file"
git push origin master
done
If you are using bash alternative to the solution provided by #AndrewF, you can make use of IFS bash internal variable to change the delimiter from space to newline, something on these lines:
(IFS=$'\n'
for FILE in $(git ls-files -o --exclude-standard); do
git add $FILE
git commit -m "Added $FILE"
git push origin master
done
)
This is just for your information. The response of AndrewF is more informative covering debugging option & usage of while instead of for.
Hope this helps!
Try putting the $FILE var in quotes:
git add "$FILE"
That'll quote the filename, thus allowing spaces in it.
Replace git add $FILE with git add "$FILE". That way it will be interpreted as a single element.
I know that this is very late but here is one way to do it using the standard xargs linux command:
git ls-files -o --exclude-standard | xargs -L 1 -I{} -d '\n' git add '{}'
You can test it by simply echoing the command as follows:
git ls-files -o --exclude-standard | xargs -L 1 -I{} -d '\n' echo "git add '{}'"
To add as a single file add a backslash before the space in the filename:
git add pathtofilename/filenamewith\ space.txt

linux shell stream redirection to run a list of commands directly

I have this svn project... to get a list of unadded files (in my case, hundreds):
svn status |grep "^?"
outputs
? somefile.txt
? somefile1.txt
? somefile2.txt
I was recently introduced to sed... so now I have a list of commands I want to run
svn status | grep "^?"|sed "s/^?/svn add/"
outputs
svn add somefile.txt
svn add somefile1.txt
svn add somefile2.txt
I realize I could just pipe it to a file
svn status | grep "^?"|sed "s/^?/svn add/" >out.sh && sh out.sh && rm out.sh
But I'd like to avoid writing to a temporary file. Is there a way I pipe it to some command like this:
svn status | grep "^?"|sed "s/^?/svn add/" |some_command_that_runs_each_line
What about bash/sh?
svn status | grep "^?"|sed "s/^?/svn add/" | bash
What you are looking for is the xargs command:
svn status | grep "^?" | sed "s/^..//" | xargs svn add
You can also use substitution:
svn add `svn status | grep "^?"` | cut -c 3-`
How about:
for i in `svn status | grep "^?"|sed "s/^?/svn add/"`
do
$i
done

Resources