Substitute character in Makefile variable after evaluation - makefile

I have a Makefile that I'm using to build Docker containers. I'm detecting the current git branch and then using that as a tag for the build, but some git branches may contain slashes, which is an invalid character for Docker tags.
I'm trying to replace the slashes with a "-" within the Makefile, but I need to detect the git branch first. Here's a very simplified version of what I have:
NAME = foo
BRANCH = `git rev-parse --abbrev-ref HEAD`
TAG = $(subst /,-,$(BRANCH))
build:
docker build --tag=$(NAME):$(TAG) .
The issue is that if BRANCH ends up being set to "foo/bar" after detecting the git branch, then TAG also ends up being "foo/bar". However, if I explicitly set BRANCH to "foo/bar" (instead of detecting it), then TAG gets correctly set to "foo-bar", which is a valid tag.
I'm pretty new to Makefiles, but my guess is that the shell command is being evaluated too late, and the subst command is attempting to do the replacing on the shell command itself, and not on the result of the command.
I've also tried using the ":=" for immediate evaluation (if I understood the docs correctly):
BRANCH := `git rev-parse --abbrev-ref HEAD`
to no avail.
Is there a better way to do what I'm attempting, or am I stuck having to pass in the branch name manually to the make command?

Backticks are expanded by the shell, not make. The contents of your $(BRANCH) make variable is the literal string with the backticks in it. You can see this by adding $(info BRANCH := $(BRANCH)) to your makefile.
So when your $(subst) call runs the slash in the branch name hasn't shown up yet and so can't get replaced.
Either do the substitution at recipe/shell time with sed or a substitution parameter expansion or use $(shell) in the make variable assignment to have make perform the git call.
BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
If you use BRANCH more than once in any given recipe then you may want to use a simply-expanded variable instead and use
BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
but this will have make run that git command at make parse time regardless of whether or not anything actually being built needs it (but with the normal assignment make will run the git command once each time the $(BRANCH) variable is expanded).

Related

How to parse correctly an argument with foward slash in bash?

I wrote a script in which I try to wrap some AOSP repo commands. One of the arguments I pass to the script is a branch in the following format: refs/tags/NAME.
When I pass this directly to repo init, it resolves just fine. However, when I pass to my script, I get the error: fatal: Couldn't find remote ref refs/tags/NAME
Below is my script:
#!/bin/bash
URL='private url ommited'
# initially, I tried just to BRANCH=$3 which didn't work either
BRANCH=`echo $3 | sed 's!/!\/!g'`
MANIFEST="$5"
REPO_URL='another private url ommited'
REPO_BRANCH='ommited'
# point to objects
repo init -u $URL -b $BRANCH -m $MANIFEST --repo-url=$REPO_URL --repo-branch=$REPO_BRANCH --depth=1
# download code
time repo sync --no-tags
First I assumed the issue was related to "/" and tried to sed it with "\/", but I get the same error. Now I assume there is some bash logic I don't understand correctly.
For now, we can assume that branch will always be the third argument and manifest the fifth. In the future, I may refactor this. Also, this is pure bash and will just run on this particular machine.
EDIT:
I've just run the script with /bin/bash -x and my variable BRANCH is returning refs/tags/NAME as it should, but the repo init command doesn't seem to recognize when the variable BRANCH is expanded to refs/tags/NAME. In fact, if I pass the "$3" directly to repo init, it doesn't work either.
I also tried to pass the whole arguments array "$#" as the other variables allocated in the script, such as URL, MANIFEST and others also passed to the scripts, but the repo init command doesn't interpret it correctly also (which seems related to the URL and what led me to believe that foward slashes where the one responsible for this issue).
All my tries where made by the following steps:
create a new directory
cd to it
try to run the script and notice the fail
rm -rf .repo
repo init with the same arguments
I've just found the solution for this issue. I think that repo might have some issue with bash expansion or I just don't really get what was going wrong.
However, to solve this I used the below:
INIT=$(eval echo $#)
repo init -u $INIT
Instead passing the $# directly to repo init command or setting the variables by hand. Just echo $# didn't work either.

How to run a git hook only when running git worktree add command

I am wanting to run a shell script when I invoke the command git worktree add. Reading the docs for post-checkout seems like it would run for git worktree add but it would also run for other commands which I don't want to use it for, such as git checkout.
Is there any other hook I could use? Or perhaps I could use post checkout but have the script setup so it exits if it isn't the git worktree add command?
The reason I want to do this is to run a set of commands to set up my directory that is required when I run git worktree add, but I wouldn't need to do this setup for a normal git repository that is just using git checkout commands.
I have come up with a solution, though I am not sure how rebust it is. I use the post-checkout hook as a bash script, and I have the following lines at the top.
if ! [[ "$0" =~ ".bare/hooks/post-checkout" ]] \
|| ! [[ "$1" == "0000000000000000000000000000000000000000" ]]; then
echo "WARNING: post-checkout hook running for reason otherwise than git worktree add, bailing";
exit 0;
fi
# Do something down here every time we run `git worktree add`
How this works: $0 is the path of the script and $1 is the reference of the previous head.
By convention I have been cloning my bare repository into a directory called .bare which is what the first statement is checking. The second statement is checking that the previous ref of HEAD is that string of 0s. This catches and exits if you are just using other checkout commands such as git checkout because in that case the previous HEAD is not the string of 0s because it was pointing to a commit. However, it seems that because I create a new HEAD every time we run git worktree add it sets the previous HEAD ref to that string of 0s, which allows me to assert on that condition.
This does work for my use case so I am not sure if there is a better way or not. If anyone has a better suggestion then let me know.
P.S. I can get the directory of the new branch simply using pwd in my post-checkout script, which is useful for the commands that I want to run in the hook.

"git push origin master" gives weird behavior in bash script

I have this simple function in my bashrc file
function aupgrade {
cat ~/.bash_aliases > ~/bash/.bash_aliases
cd ~/bash
git add .
if [[ $1 == "" ]]; then
git commit -m "Update"
else
git commit -m "$1"
fi
git push origin master
cd - 1>/dev/null
}
This function has a purpose, this is the expected behavior:
First, replace the content of the .bash_aliases file in the bash repository with the stdout of cat ~/.bash_aliases
Second, go into the ~/bash directory which is a git respository
Third, stage all changes
Fourth, if when calling the aupgrade function the following argument is nothing, just commit with the "Update" message, but if the user wrote an argument, like aupgrade "New commit!", commit the changes with such argument as the message, git commit -m $1
Fifth, push the changes
Sixth, go back to the previous directory
BUT, it doesnt do that, instead it just does:
First, replace the content of the .bash_aliases file in the bash repository with the stdout of cat ~/.bash_aliases
Second, go into the ~/bash directory which is a git respository
Third, stage all changes
Fourth, commit with the "Update" message although there is an argument
Fifth, push the changes
Sixth, go back to the previous directory
This is weird. This looks product of the git push origin master line. It's not a conditional problem, because when I wrote another function like this but without the git push origin master line it worked as desired.
Why does this happen? Is there any solution?
This is what set -x shows to me, it's really weird
So I have tested your code, and the only problem I could find is that passing your first argument as a string with multiple exclamation marks inserts the same (or previous?) command unless you escape it:
It looks like this is exactly what happened for you, but you had "set -x" ran beforehand, thus it replaced the double exclamation marks with that command.
I'd also recommend storing function arguments in named local variables for cleaner code.

Cannot get the exact bash prompt I want

What's throwing me off mostly is trying to get new lines as I want. And trust me, I've googled and searched through this site, but can't get exactly what I want. I either get parsing errors or not exactly what I want.
What I want is this
/c/what/ever/folder/here
(gitbranch)
$ STARTTYPING HERE
first line green, second yellow, third white. What I've been able to come up with gives me this:
/c/what/ever/folder/here
(gitbranch) $ STARTTYPING HERE
note the extra space before (gitbranch) and lack of newline before the "$"
here is my PS1 so far:
parse_git_branch() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
}
PS1="\[\033[32m\]\w\[\033[33m\]\n\$(parse_git_branch)\[\033[00m\] $ "
I'm wanting this because we sometimes have long branch and directory names and hate having my prompt start anywhere but to the far left
I think involving sed is generating some output that is really confusing the expansions inside the double-quoted string you're using for PS1.
You could switch to splicing special strings, single-quoted strings, and double quoted ones together (e.g. "$foo"'"not $expanded'$'\n') if you like, but that's a bit confusing.
Instead, why not skip parsing git branch's output at all, and just ask for the branch directly?
Assuming that you want the branch name (second line) not to show up at all when not in a Git repository, this works:
branch=$(git rev-parse --abbrev-ref HEAD 2> /dev/null)
PS1="\[\033[32m\]\w${branch:+\[\033[33m\]\n($branch)}\[\033[00m\]\n$ "
The magic is in the :+ parameter expansion statement: it assigns/returns everything on the right of the plus if the left side is non-empty; nothing otherwise. Think of it a bit like the $cond ? $trueval : $falseval ternary operator in other programming languages.
If you want a default string instead of no second line, try:
PS1="\[\033[32m\]\w\[\033[33m\]\n(${branch:-no branch detected})\[\033[00m\]\n$ "
When you're in a brand new repo, but you haven't committed anything yet, that code will prompt that you're in branch HEAD. If you'd like it to say master or something else, do something like this:
branch=$(git rev-parse --abbrev-ref HEAD 2> /dev/null)
if [ "$branch" = "HEAD" ]; then
branch="master, brand new"
fi
You're probably already doing this, but if you want your prompt to update when you change directories, running that code once won't work; it'll "stick" with whatever the git branch of where you first ran it was. To make that happen, plug that code into your PROMPT_COMMAND bash variable via a function, like so:
prompt_with_git() {
branch=$(git rev-parse --abbrev-ref HEAD 2> /dev/null)
PS1="\[\033[32m\]\w${branch:+\[\033[33m\]\n($branch)}\[\033[00m\]\n$ "
}
PROMPT_COMMAND=prompt_with_git
Once you've sourced that, things should work.
Note that this requires a Git that supports the rev-parse command and arguments used here. Do git rev-parse --abbrev-ref HEAD in a committed repo folder manually to check if yours does; if there's an error, you may need to upgrade your Git.

How to change the git directory delimiter?

When working inside a windows command prompt, all of my paths indicate director separators with a backslash \, when using GIT commands, all of the paths are instead using forwardslash /. How do I change GIT's output to mirror my command line output?
Example inconsistent directory indicators;
D:\git\demo>git status --s
A test/subdir/foo.txt
How do I change GIT's output to mirror my command line output?
First, all git commands are executed in a git bash sub-shell, which explains why you see '/'.
A '\' is an escape character for a bash session, which is why it is not used in any bash command output.
You would need to use a git wrapper (a git.pat set in your PATH) in order to replace any / by \.
git.bat:
C:\prgs\git\latest\bin\git.exe %*|C:\prgs\git\latest\usr\bin\sed.exe -e 's:/:\\\\:'
Make sure git.bat is set before git.exe in your %PATH%: type where git to check the order in which git(s) are discovered.
And replace C:\prgs\git\latest by the path your Git is installed.
By specifying the full path for git.exe and for sed.exe, you are sure to use the right executable.
Since what you're looking for seems to be not specifically "how do I make Git use \ in file paths" but rather "how do I make Git generate file paths with \", you can pipe the output through sed (which is packaged in Git Bash) like so:
$ git status --s | sed 's/\//\\/g'
M dir\file.py
?? dir\input001.txt
?? dir\output001.txt
and to avoid typing sed every single time you can configure a Git alias to do it for you:
[alias]
ws = "!ws() { : git status ; git status --short $# | sed 's/\\//\\\\/g' ; } && ws"
which will let you do this:
$ git ws
M dir\file.py
?? dir\input001.txt
?? dir\output001.txt

Resources