Background
I need to close multiple Git branches (hundreds) that have been left open on a remote repository.
I wanted to sort them by last-commit and put them in a text-file so others could confirm they were no longer needed.
I found a method that allowed me to dump them in a way that I could distribute (and open in Excel to sort by date)
git for-each-ref --format='%(committerdate:short) %09 %(authorname) %09 %(refname)' | sort -k5n -k2M -k3n -k4n >> branches.txt
And then I need to read the updated text-file back in and delete the remote branches:
#!/bin/bash
#prefix of the branches if they are all remote
prefix="refs/remotes/origin/"
prefix1="refs/remotes/"
# path to branches, compiled with:
# git for-each-ref --format='%(committerdate:short) %09 %(authorname) %09 %(refname)' | sort -k5n -k2M -k3n -k4n >> branches.txt
# read through entire input
input="branches.txt"
while IFS= read -r line
do
# echo "$line"]
IFS=' ' read -r -a array <<< "$line"
# search the array for the prefix of the branch
for index in "${!array[#]}"
do
if [[ ${array[$index]} == *"refs/remotes/origin"* ]]; then
#echo -e " \e[34m ${array[$index]} \e[39m Last commit: \e[34m" ${array[0]} ${array[1]} ${array[2]} ${array[3]} ${array[4]}
# echo ${array[$index]#"$prefix"}
#remove the prefix if they are not pulled locally
branch=${array[$index]#"$prefix"}
echo $(git push origin --delete $branch)
fi
done
done < "$input"
Issue
However, keep getting an error that the refspec does not exist:
fatal: invalid refspec ':PracticeBranch?'
I don't have the ' or ? in the name of the branch variable - so this is probably my ignorance of using echo with `bash but I don't know where it is coming from.
A command like:
git push origin --delete PracticeBranch
When calling directly from the shell?
EDIT 2: For those with the same question, please be sure to read the comments on the accepted answer, as they go more in depth about how to properly use the command. I will post the final script once it is done with my own explanation of what is going on.
I'm trying to write a simple bash function that clears the terminal screen, then performs multiple ls commands to show the different content types of the current directory in different colors.
I've managed to write something in Python which kind of does the same thing, but it has some big drawbacks (specifically, the behavior for special characters in filenames is "iffy" on Cygwin, and it's a pain to make it fit properly on the screen). I want it to look something like this:
*With non-dotfiles in green (haven't include them in the screenshot for privacy reasons).
I've managed to list both hidden directories and visible directories, but the files are giving me trouble. Every method of using ls to get all files I've tried uses the -l argument, or something like find or grep, all of which output the files in a single column (not what I want).
Is it possible to use the ls command (or something else) to output only the files or dotfiles of a directory while keeping ls's default output format?
EDIT 1: Here's what the script currently looks like (not much yet, but some people want to see it)
function test() {
clear;
GOLD=229;
RED=203;
BLUE=39;
WHITE=15;
GREEN=121;
# Colored legend.
tput sgr0;
tput setaf bold
# echo's "-n" suppresses the new line at the end.
tput setaf $GOLD;
echo -n "Working Directory ";
tput setaf $RED;
echo -n "Hidden Directories ";
tput setaf $BLUE;
echo -n "Visible Directories ";
tput setaf $WHITE;
echo -n "Hidden Files ";
tput setaf $GREEN;
echo "Visible Files";
pwd;
ls -d .*/; # List hidden directories.
ls -d */; # List visible directories.
# List dotfiles.
# List files.
}
to list only the dot files in the current directory
find . -maxdepth 1 -type f -name ".*" | xargs ls --color=tty
to list only the other files
find . -maxdepth 1 -type f -not -name ".*" | xargs ls --color=tty
For non-hidden files and directories, try
ls -d *
For hidden files and directories, use
ls -d .*
Using the answer provided by matzeri, I managed to get a nice start. That answer is still accepted, because it answered the question that I asked. Here's the current version of the script, which I've turned into a function for easy access. I'm gonna go through it and explain some things that needed to be modified from the answer to give me the result I wanted, but didn't specifically ask for.
c() {
stty -echo; # Disable keyboard inputs during function.
local HAS_DOTDIRS=false;
local HAS_DIRS=false;
local HAS_DOTFILES=false;
local HAS_FILES=false;
local BLUE=39;
local GOLD=229;
local GREEN=121;
local PINK=170;
local RED=203;
local WHITE=15;
local CYG_CWD=$(pwd);
local WIN_CWD=$(cygpath -w "$(pwd)" | sed 's/\\/\//g');
local DOTDIRS=$(ls -Ad .*/); # Get directories with '.' prefix.
local DIRS=$(ls -Ad */); # Get normal directories.
local DOTFILES=$(find . -maxdepth 1 -type f -name ".*" | sed 's/^..//');
local FILES=$(find . -maxdepth 1 -type f -not -name ".*" | sed 's/^..//');
clear;
tput sgr0;
tput setaf bold;
local LEGEND="$(tput setaf $GOLD)Cygwin Working Directory ";
LEGEND+="$(tput setaf $PINK)Windows Working Directory ";
if ! [ -z "$DOTDIRS" ] ; then
HAS_DOTDIRS=true
LEGEND+="$(tput setaf $RED)Hidden Directories ";
fi
if ! [ -z "$DIRS" ] ; then
HAS_DIRS=true
LEGEND+="$(tput setaf $BLUE)Visible Directories ";
fi
if ! [ -z "$DOTFILES" ] ; then
HAS_DOTFILES=true
LEGEND+="$(tput setaf $WHITE)Hidden Files ";
fi
if ! [ -z "$FILES" ] ; then
HAS_FILES=true
LEGEND+="$(tput setaf $GREEN)Visible Files";
fi
echo $LEGEND;
echo "";
echo "$(tput setaf $GOLD)$CYG_CWD";
echo "$(tput setaf $PINK)$WIN_CWD";
tput setaf $RED
ls_list "$HAS_DOTDIRS" "$DOTDIRS"
tput setaf $BLUE
ls_list "$HAS_DIRS" "$DIRS"
tput setaf $WHITE
ls_list "$HAS_DOTFILES" "$DOTFILES"
tput setaf $GREEN
ls_list "$HAS_FILES" "$FILES"
tput sgr0;
stty echo; # Enable keyboard inputs after function.
}
ls_list() {
# $1 - boolean - condition to print
# $2 - list - names to 'ls'
if $1 ; then
echo "$2" | xargs -d '\n' ls -d;
fi
}
Firstly, I wrapped the whole thing with stty to stop the user from messing-up the output with keyboard inputs.
Then, I create booleans that will allow the ls outputs at the end to be performed with a very minimal delay between them (if [ bool ] rather than checking the status of a list).
The color variables are simply used by the tput command, which lets you change the color of the terminal's text.
Note: Some of the color values might be off, that's because I had issues with my Cygwin theme (currently using Nord), and these values look like their names on my setup. Your mileage may vary.
To fellow Cygwin users: I've had a few issues with tput not working when it would've worked on an actual Linux machine. If you're having trouble with tput consider defining the LS_COLORS (which can be formatted to accept RGB values), and setting the CLICOLOR variable to true, then exporting both. This can replace the tput command, although you now might want to store the initial value of both, and you have to export the LS_COLORS variable constantly.
The CYG_CWD and WIN_CWD are there because Cygwin paths aren't the same as Windows paths, and I like having both. The sed command replaces Windows' "\" with "/" for easy copy+paste.
This is where the answer to the question starts. The DOTDIRS, DIRS, DOTFILES, and FILES variables will get lists.
The ls command already allows us to filter just directories and directories starting with ., via the ls -Ad */ and the ls -Ad *./ commands. ls ignores files starting with ., unless specified (like I do), ending with / searches for directories, and the -d parameter makes it name directories instead of showing us what's in them.
As for dotfiles and regular files, ls doesn't really let us get them, because you can't specify a character which defines a file like you can with / for directories. Instead, we can use the find . -maxdepth -type f with the appropriate use of the -name or -not -name arguments to filter our files. There is an issue, however: the command will prefix the results with ./. So .bashrc becomes ./.bashrc. I don't want that. The output is thus piped to the sed command, which substitutes the first two characters for nothing (effectively removing them).
From here, we simply determine which list has elements, set the appropriate boolean values to ensure quick ls outputs at the end, print the legend, all the while changing the colors the terminal uses with the tput command.
Eventually, the ls_list function is called. If the given boolean is true, it performs the echo "$2" | xargs -d '\n' ls -d command. Essentially, it echoes the list of items found, which is piped into the xargs -d '\n' command. xargs allows us to pass the values piped to it as parameters for the ls command. The -d '\n' arguments changes the separator from spaces to newlines, because some names might have spaces in them. What ls does, when you give it filenames, is simply prints them with the regular ls output format (which is exactly what I want). I added the -d parameter so that is names directories instead of showing their contents, since I'm using that function for all my lists.
Finally, reset whatever tput is with sgr0 and re-enable keyboard inputs with stty!
This is what the output looks like for my /home directory.
Final notes:
It feels slightly slower than my Python script, but it won't bug-out with special characters in filenames and it will always respect the terminal's window size, since that's what ls does. I think the "slowness" might be because the 4 xargs ls operations don't print at the same time, while the python one printed everything at once. I can live with it being 1-2 seconds vs 0.5-1 second.
Below is my pre-commit git hoook
#!/bin/bash
....
# if git diff -U0 "$FILE_PATH" | grep -iq 'todo'; # Double quoting $FILE_PATH doesnt' change anything
if git diff -U0 $FILE_PATH | grep -iq 'todo';
then
echo $FILE_PATH ' -> Contains TODO'
exit 1
else
echo 'nooooooooooooooooooooooooooooooooooo'
fi
I'm always getting the noooooooooooooooooooo message, however the command below, tried directly on my terminal, works well:
git diff -U0 my/file/path.php | grep -iq 'todo' && echo 'true' || echo 'false'
Output
true
UPDATE
When running bash .git/hooks/pre-commit it works, very strange!!
FYI
I don't know if it's an important information but .git/hooks/pre-commit is a symbolik link
Most likely, your pipe does not return status 0. To verify that this is the case (and not the way you write your compound statement), you could rewrite it as
git diff -U0 "$FILE_PATH" | grep -iq 'todo'
grep_status=$?
echo grep status is $grep_status
if (( grep_status == 0 ))
then
echo contains todo
else
echo no
fi
I also noticed that your code contains an unnecessary semicolon in the if line. I first thought that this semicolon might cause the weird behaviour, but at least on my bash, where I tried your code, it does not seem to do any harm. Still, I would remove it for the safe side.
I have written a bash script that finds any executable files in our scripts directory, then performs a grep on the resulting files to display a description, if it was included in the file.
A "description" is identified in each file as a line beginning with "# DESC:"
For some reason, the script also includes the grep command that is being run (but only once). Does anyone know why this is?
Script and output shown below. Why does the second line in the output happen?
Script
#!/bin/bash
# Find any FILES that are EXECUTABLE in the SCRIPTS
# directory and display any description, if there is one
find /opt/scripts/. -perm -111 -type f -maxdepth 1 | while read line ;
do
file=$(basename "$line")
printf "\033[1m%10s\033[0m : " $file
grep "# DESC:" "$line" | cut -c 9-
done
Output
desc : Displays all the scripts and their descriptions
DESC:" "$line" | cut -c 9-
showhelp : Displays the script help file
test : Script to perform system testing
Reason
Presumably your grepping script is also in /opt/scripts?
So it finds itself, and finds the grep subject '#DESC' and prints that.
You could fix that by adding a # DESC line to the top of your grep script, and just outputting the first result found by each grep using 'grep -m1'
grep -m1 '# DESC' "$line" | cut -c 9-
<humour>Otherwise its just turtles all the way down... ;-) </humour>
Alternative Fix
You could also improve the grep by using a regular expression and anchoring to the beginning of the line:
egrep -m1 '^# DESC' "$line" | cut -c 9-
I have been researching how to change this behavior all day with no luck, so here goes.
Is there a way in iterm2, when viewing git logs, to change the way the cmd+click functions on the git log hash? Ideally, I am hoping that cmd+click would would open a browser window with the correct github url where the change set could be viewed.
If this is not possible, please let me know. I believe this would be very helpful to others, I wish I had the magic wand to figure out how to configure this.
Thoughts?
While this is not ideal, here is how I was able to work around this issue. I built a commit hook! Not perfect, I know. Ideas?
#!/bin/sh
#
# Automatically adds branch name and branch description to every commit message.
# Edit .git/hooks/commit-msg & make sure it is excutable chmod +x
# Requires git config --add remote.github.url {value}
#
NAME=$(git branch | grep '*' | sed 's/* //')
DESCRIPTION=$(git config branch."$NAME".description)
TEXT=$(cat "$1" | sed '/^#.*/d')
GIT_COMMIT_SHORT_ID=$(git rev-parse --short HEAD)
GIT_COMMIT_ID=$(git rev-parse HEAD)
GIT_GITHUB_URL=$(git config --get remote.github.url)
if [ -n "$TEXT" ]
then
echo "$NAME"': '$(cat "$1" | sed '/^#.*/d') > "$1"
if [ -n "$DESCRIPTION" ]
then
echo "" >> "$1"
echo $DESCRIPTION >> "$1"
fi
echo $GIT_GITHUB_URL$GIT_COMMIT_ID >> "$1"
else
echo "Aborting commit due to empty commit message."
exit 1
fi