How do I exit immediately in conditional command? [duplicate] - bash

I am making a presubmit script. It looks like this:
function presubmit() {
gradle test android
gradle test ios
gradle test server
git push origin master
}
I want the function to exit, if any of the tests fail so it won't push a bug to git. How?

1. Use subshell ( .. ) with set -e; to make it more concise, you can do this:
build() {( set -e # Fail early
build_cmd_step_1
build_cmd_step_2
build_cmd_step_3
...
)}
Then, the function will fail on the first failure and you can intercept the exit status:
build
exit_status=$?
if [ ${exit_status} -ne 0 ]; then
echo "We have error - build failed!"
exit "${exit_status}"
fi
2. Alternatively, the && \ chaining inside a function is also good (https://stackoverflow.com/a/51913013/1375784), though it might get bad if you have a bigger function.
Both methods can be good, depending on your use case (in some cases using subshells may cause some unwanted side effects)

The way I do it is to add && \ after every command in the function (except the last one).
function presubmit() {
gradle test android && \
gradle test ios && \
gradle test server && \
git push origin master
}

I would make script more granular:
#!/bin/bash
function test() {
gradle test android
gradle test ios
gradle test server
}
function push() {
git push origin master
}
# this subshell runs similar to try/catch
(
# this flag will make to exit from current subshell on any error inside test or push
set -e
test
push
)
# you catch errors with this if
if [ $? -ne 0 ]; then
echo "We have error"
exit $?
fi
We track error only inside test and push. You can add more actions outside of subshell where test and push run. You can also this way add different scope for errors (let consider it as try/catch)

You can do this:
# declare a wrapper function for gradle
gradle() {
command gradle "$#" || exit 1
}
presubmit() {
gradle test android
gradle test ios
gradle test server
git push origin master
}
declare -xf presubmit gradle
Call the function in a subshell as:
( presubmit )

Usually when I call a function and want an error message incase it fails I do this:
presubmit || { echo 'presubmit failed' ; exit 1; }
By adding the || flag, it will determine whether which expression is TRUE.
Hope this helps :)

Also others have given ways that map one to one to your case, I think a more generic view is better. Also using the || and && for such is a cryptic way of writing scripts (read: prone to ending up with bugs).
I think the following is much easier to work with long term:
function presubmit() {
if ! gradle test android
then
return 1
fi
if ! gradle test ios
then
return 1
fi
if ! gradle test server
then
return 1
fi
git push origin master
}
The return from the last command is returned by the function so we do not need to have an if/then there.
In your specific case, to avoid the duplication, you could use a for loop like so:
function presubmit() {
for name in android ios server
do
if ! gradle test ${name}
then
return 1
fi
done
git push origin master
}
Now, you may instead want to look at a pre-push hook which would probably be much better since whether you run your script or not, the push won't happen unless the hook succeeds.

Related

Record shellcheck findings in Jenkins

I'm looking for a way to record violation findings of shellcheck in my Jenkins Pipeline script. I was not able to find something so far. For other tools (Java, Python), I'm using Warnings Next Generation, but it does not seem to support shellcheck, yet. I'd like to have the violations visualized within my Jenkins Job dashboard. Does anyone have experience with that? Or perhaps a ready to use custom tool for Warnings NG?
I did find a feasible solution myself. Like suggested in the comments, spellcheck offers checkstyle format, which can be parsed and visualized with Warnings NG. The following Pipeline stage definition works fine.
stage('Analyze') {
steps {
catchError(buildResult: 'SUCCESS') {
sh """#!/bin/bash
# The null command `:` only returns exit code 0 to ensure following task executions.
shellcheck -f checkstyle *.sh > shellcheck.xml || :
"""
recordIssues(tools: [checkStyle(pattern: 'shellcheck.xml')])
}
}
}
Running the build generates a nice trend diagram like follows.
Running shellcheck for all files merging the output in a single xml file didn't play well with recordIssues in my case.
I had create individual report for each source file to make it work.
stage('Shellcheck') {
steps {
catchError(
buildResult: hudson.model.Result.SUCCESS.toString(),
stageResult: hudson.model.Result.UNSTABLE.toString(),
message: "shellcheck error detected, but allowing job to continue!")
{
sh '''
# shellcheck with all files in single xml doesnt play well with jenkins report
ret=0
for file in $(grep -rl '^#!/.*/bash' src); do
echo shellcheck ${file}
mkdir -p .checkstyle/${file}/
shellcheck -f checkstyle ${file} > .checkstyle/${file}/shellcheck.xml || (( ret+=$? ))
done
exit ${ret}
'''
}//catchError
}//steps
post {
always {
recordIssues(tools: [checkStyle(pattern: '.checkstyle/**/shellcheck.xml')])
}
}//post
}//stage

equivalent of -e within a bash function [duplicate]

I am making a presubmit script. It looks like this:
function presubmit() {
gradle test android
gradle test ios
gradle test server
git push origin master
}
I want the function to exit, if any of the tests fail so it won't push a bug to git. How?
1. Use subshell ( .. ) with set -e; to make it more concise, you can do this:
build() {( set -e # Fail early
build_cmd_step_1
build_cmd_step_2
build_cmd_step_3
...
)}
Then, the function will fail on the first failure and you can intercept the exit status:
build
exit_status=$?
if [ ${exit_status} -ne 0 ]; then
echo "We have error - build failed!"
exit "${exit_status}"
fi
2. Alternatively, the && \ chaining inside a function is also good (https://stackoverflow.com/a/51913013/1375784), though it might get bad if you have a bigger function.
Both methods can be good, depending on your use case (in some cases using subshells may cause some unwanted side effects)
The way I do it is to add && \ after every command in the function (except the last one).
function presubmit() {
gradle test android && \
gradle test ios && \
gradle test server && \
git push origin master
}
I would make script more granular:
#!/bin/bash
function test() {
gradle test android
gradle test ios
gradle test server
}
function push() {
git push origin master
}
# this subshell runs similar to try/catch
(
# this flag will make to exit from current subshell on any error inside test or push
set -e
test
push
)
# you catch errors with this if
if [ $? -ne 0 ]; then
echo "We have error"
exit $?
fi
We track error only inside test and push. You can add more actions outside of subshell where test and push run. You can also this way add different scope for errors (let consider it as try/catch)
You can do this:
# declare a wrapper function for gradle
gradle() {
command gradle "$#" || exit 1
}
presubmit() {
gradle test android
gradle test ios
gradle test server
git push origin master
}
declare -xf presubmit gradle
Call the function in a subshell as:
( presubmit )
Usually when I call a function and want an error message incase it fails I do this:
presubmit || { echo 'presubmit failed' ; exit 1; }
By adding the || flag, it will determine whether which expression is TRUE.
Hope this helps :)
Also others have given ways that map one to one to your case, I think a more generic view is better. Also using the || and && for such is a cryptic way of writing scripts (read: prone to ending up with bugs).
I think the following is much easier to work with long term:
function presubmit() {
if ! gradle test android
then
return 1
fi
if ! gradle test ios
then
return 1
fi
if ! gradle test server
then
return 1
fi
git push origin master
}
The return from the last command is returned by the function so we do not need to have an if/then there.
In your specific case, to avoid the duplication, you could use a for loop like so:
function presubmit() {
for name in android ios server
do
if ! gradle test ${name}
then
return 1
fi
done
git push origin master
}
Now, you may instead want to look at a pre-push hook which would probably be much better since whether you run your script or not, the push won't happen unless the hook succeeds.

bats - how can i echo the file name in a bats script for reporting

I have some bats scripts that I run to test some functionality
how can I echo the bats file name in the script?
my bats script looks like:
#!/usr/bin/env bats
load test_helper
echo $BATS_TEST_FILENAME
#test "run cloned mission" {
blah blah blah
}
in order for my report to appear as:
✓ run cloned mission
✓ run cloned mission
✓ addition using bc
---- TEST NAME IS xxx
✓ run cloned mission
✓ run cloned mission
✓ addition using bc
---- TEST NAME IS yyy
✓ run cloned mission
✓ run cloned mission
✓ addition using bc
but got the error
2: syntax error:
operand expected (error token is ".bats
2")
what is the correct way to do it?
I don't want to change the sets names for it only to echo the filename between different tests.
Thanks.
TL;DR
Just output the file name from the setup function using a combination of prefixing the message with # and redirecting it to fd3 (documented in the project README).
#!/usr/bin/env bats
setup() {
if [ "${BATS_TEST_NUMBER}" = 1 ];then
echo "# --- TEST NAME IS $(basename ${BATS_TEST_FILENAME})" >&3
fi
}
#test "run cloned mission" {
blah blah blah
}
All your options
Just use BASH
The simplest solution is to just iterate all test files and output the filename yourself:
for file in $(find ./ -name '*.bats');do
echo "--- TEST NAME IS ${file}"
bats "${file}"
done
The downside of this solution is that you lose the summary at the end. Instead a summary will be given after each single file.
Use the setup function
The simplest solution within BATS is to output the file name from a setup function. I think this is the solution you are after.
The code looks like this:
setup() {
if [ "${BATS_TEST_NUMBER}" = 1 ];then
echo "# --- TEST NAME IS $(basename ${BATS_TEST_FILENAME})" >&3
fi
}
A few things to note:
The output MUST begin with a hash #
The MUST be a space after the hash
The output MUST be redirected to file descriptor 3 (i.e. >&3)
A check is added to only output the file name once (for the first test)
The downside here is that the output might confuse people as it shows up in red.
Use a skipped #test
The next solution would be to just add the following as the first test in each file:
#test "--- TEST NAME IS $(basename ${BATS_TEST_FILENAME})" {
skip ''
}
The downside here is that there will be an addition to the amount of skipped tests...
Use an external helper function
The only other solution I can think of would be to create a test helper that lives in global scope and keeps tracks of its state.
Such code would look something like this:
output-test-name-helper.bash
#!/usr/bin/env bash
create_tmp_file() {
local -r fileName="$(basename ${BATS_TEST_FILENAME})"
if [[ ! -f "${BATS_TMPDIR}/${fileName}" ]];then
touch "${BATS_TMPDIR}/${fileName}"
echo "---- TEST NAME IS ${fileName}" >&2
fi
}
remove_tmp_file() {
rm "${BATS_TMPDIR}/$(basename ${BATS_TEST_FILENAME})"
}
trap remove_tmp_file EXIT
create_tmp_file
Which could then be loaded in each test:
#!/usr/bin/env bats
load output-test-name-helper
#test "run cloned mission" {
return 0
}
The major downside here is that there are no guarantees where the output is most likely to end up.
Adding output from outside the #test, setup and teardown functions can lead to unexpected results.
Such code will also be called (at least) once for every test, slowing down execution.
Open a pull-request
As a last resort, you could patch the code of BATS yourself, open a pull-request on the BATS repository and hope this functionality will be supported natively by BATS.
Conclusion
Life is a bunch of tradeoffs. Pick a solution that most closely fits your needs.
I've figured out a way to do this, but it requires you changing how you handle your individual setup in each file.
Create a helper file that defines a setup function that does as Potherca described above:
global.bash:
test_setup() { return 0; }
setup() {
(($BATS_TEST_NUMBER==1)) \
&& echo "# --- $(basename "$BATS_TEST_FILENAME")" >&3
test_setup
}
Then in your test, instead of calling setup you would just load 'global'.
If you need to create a setup for a specific file, then instead of creating a setup function, you'd create a test_setup function.
Putting the echo in setup outputs the file name after the test name.
What I wound up doing is adding the file name to the test name itself:
test "${BATS_TEST_FILENAME##*/}: should …" {
…
}
Also, if going the setup route, the condition can be avoided with:
function setup() {
echo "# --- $(basename "$BATS_TEST_FILENAME")" >&3
function setup() {
…
}
setup
}

How to mark a build unstable in Jenkins when running shell scripts

In a project I'm working on, we are using shell scripts to execute different tasks. Some are sh/bash scripts that run rsync, and some are PHP scripts. One of the PHP scripts is running some integration tests that output to JUnit XML, code coverage reports, and similar.
Jenkins is able to mark the jobs as successful / failed based on exit status. In PHP, the script exits with 1 if it has detected that the tests failed during the run. The other shell scripts run commands and use the exit codes from those to mark a build as failed.
// :: End of PHP script:
// If any tests have failed, fail the build
if ($build_error) exit(1);
In Jenkins Terminology, an unstable build is defined as:
A build is unstable if it was built successfully and one or more publishers report it unstable. For example if the JUnit publisher is configured and a test fails then the build will be marked unstable.
How can I get Jenkins to mark a build as unstable instead of only success / failed when running shell scripts?
Modern Jenkins versions (since 2.26, October 2016) solved this: it's just an advanced option for the Execute shell build step!
You can just choose and set an arbitrary exit value; if it matches, the build will be unstable. Just pick a value which is unlikely to be launched by a real process in your build.
It can be done without printing magic strings and using TextFinder. Here's some info on it.
Basically you need a .jar file from http://yourserver.com/cli available in shell scripts, then you can use the following command to mark a build unstable:
java -jar jenkins-cli.jar set-build-result unstable
To mark build unstable on error, you can use:
failing_cmd cmd_args || java -jar jenkins-cli.jar set-build-result unstable
The problem is that jenkins-cli.jar has to be available from shell script. You can either put it in easy-to-access path, or download in via job's shell script:
wget ${JENKINS_URL}jnlpJars/jenkins-cli.jar
Use the Text-finder plugin.
Instead of exiting with status 1 (which would fail the build), do:
if ($build_error) print("TESTS FAILED!");
Than in the post-build actions enable the Text Finder, set the regular expression to match the message you printed (TESTS FAILED!) and check the "Unstable if found" checkbox under that entry.
You should use Jenkinsfile to wrap your build script and simply mark the current build as UNSTABLE by using currentBuild.result = "UNSTABLE".
stage {
status = /* your build command goes here */
if (status === "MARK-AS-UNSTABLE") {
currentBuild.result = "UNSTABLE"
}
}
you should also be able to use groovy and do what textfinder did
marking a build as un-stable with groovy post-build plugin
if(manager.logContains("Could not login to FTP server")) {
manager.addWarningBadge("FTP Login Failure")
manager.createSummary("warning.gif").appendText("<h1>Failed to login to remote FTP Server!</h1>", false, false, false, "red")
manager.buildUnstable()
}
Also see Groovy Postbuild Plugin
In my job script, I have the following statements (this job only runs on the Jenkins master):
# This is the condition test I use to set the build status as UNSTABLE
if [ ${PERCENTAGE} -gt 80 -a ${PERCENTAGE} -lt 90 ]; then
echo WARNING: disc usage percentage above 80%
# Download the Jenkins CLI JAR:
curl -o jenkins-cli.jar ${JENKINS_URL}/jnlpJars/jenkins-cli.jar
# Set build status to unstable
java -jar jenkins-cli.jar -s ${JENKINS_URL}/ set-build-result unstable
fi
You can see this and a lot more information about setting build statuses on the Jenkins wiki: https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+CLI
Configure PHP build to produce xml junit report
<phpunit bootstrap="tests/bootstrap.php" colors="true" >
<logging>
<log type="junit" target="build/junit.xml"
logIncompleteSkipped="false" title="Test Results"/>
</logging>
....
</phpunit>
Finish build script with status 0
...
exit 0;
Add post-build action Publish JUnit test result report for Test report XMLs. This plugin will change Stable build to Unstable when test are failing.
**/build/junit.xml
Add Jenkins Text Finder plugin with console output scanning and unchecked options. This plugin fail whole build on fatal error.
PHP Fatal error:
Duplicating my answer from here because I spent some time looking for this:
This is now possible in newer versions of Jenkins, you can do something like this:
#!/usr/bin/env groovy
properties([
parameters([string(name: 'foo', defaultValue: 'bar', description: 'Fails job if not bar (unstable if bar)')]),
])
stage('Stage 1') {
node('parent'){
def ret = sh(
returnStatus: true, // This is the key bit!
script: '''if [ "$foo" = bar ]; then exit 2; else exit 1; fi'''
)
// ret can be any number/range, does not have to be 2.
if (ret == 2) {
currentBuild.result = 'UNSTABLE'
} else if (ret != 0) {
currentBuild.result = 'FAILURE'
// If you do not manually error the status will be set to "failed", but the
// pipeline will still run the next stage.
error("Stage 1 failed with exit code ${ret}")
}
}
}
The Pipeline Syntax generator shows you this in the advanced tab:
I find the most flexible way to do this is by reading a file in the groovy post build plugin.
import hudson.FilePath
import java.io.InputStream
def build = Thread.currentThread().executable
String unstable = null
if(build.workspace.isRemote()) {
channel = build.workspace.channel;
fp = new FilePath(channel, build.workspace.toString() + "/build.properties")
InputStream is = fp.read()
unstable = is.text.trim()
} else {
fp = new FilePath(new File(build.workspace.toString() + "/build.properties"))
InputStream is = fp.read()
unstable = is.text.trim()
}
manager.listener.logger.println("Build status file: " + unstable)
if (unstable.equalsIgnoreCase('true')) {
manager.listener.logger.println('setting build to unstable')
manager.buildUnstable()
}
If the file contents are 'true' the build will be set to unstable. This will work on the local master and on any slaves you run the job on, and for any kind of scripts that can write to disk.
I thought I would post another answer for people that might be looking for something similar.
In our build job we have cases where we would want the build to continue, but be marked as unstable. For ours it's relating to version numbers.
So, I wanted to set a condition on the build and set the build to unstable if that condition is met.
I used the Conditional step (single) option as a build step.
Then I used Execute system Groovy script as the build step that would run when that condition is met.
I used Groovy Command and set the script the following
import hudson.model.*
def build = Thread.currentThread().executable
build.#result = hudson.model.Result.UNSTABLE
return
That seems to work quite well.
I stumbled upon the solution here
http://tech.akom.net/archives/112-Marking-Jenkins-build-UNSTABLE-from-environment-inject-groovy-script.html
In addition to all others answers, jenkins also allows the use of the unstable() method (which is in my opinion clearer).
This method can be used with a message parameter which describe why the build is unstable.
In addition of this, you can use the returnStatus of your shell script (bat or sh) to enable this.
For example:
def status = bat(script: "<your command here>", returnStatus: true)
if (status != 0) {
unstable("unstable build because script failed")
}
Of course, you can make something with more granularity depending on your needs and the return status.
Furthermore, for raising error, you can also use warnError() in place of unstable(). It will indicate your build as failed instead of unstable, but the syntax is same.
The TextFinder is good only if the job status hasn't been changed from SUCCESS to FAILED or ABORTED.
For such cases, use a groovy script in the PostBuild step:
errpattern = ~/TEXT-TO-LOOK-FOR-IN-JENKINS-BUILD-OUTPUT.*/;
manager.build.logFile.eachLine{ line ->
errmatcher=errpattern.matcher(line)
if (errmatcher.find()) {
manager.build.#result = hudson.model.Result.NEW-STATUS-TO-SET
}
}
See more details in a post I've wrote about it:
http://www.tikalk.com/devops/JenkinsJobStatusChange/
As a lighter alternative to the existing answers, you can set the build result with a simple HTTP POST to access the Groovy script console REST API:
curl -X POST \
--silent \
--user "$YOUR_CREDENTIALS" \
--data-urlencode "script=Jenkins.instance.getItemByFullName( '$JOB_NAME' ).getBuildByNumber( $BUILD_NUMBER ).setResult( hudson.model.Result.UNSTABLE )" $JENKINS_URL/scriptText
Advantages:
no need to download and run a huge jar file
no kludges for setting and reading some global state (console text, files in workspace)
no plugins required (besides Groovy)
no need to configure an extra build step that is superfluous in the PASSED or FAILURE cases.
For this solution, your environment must meet these conditions:
Jenkins REST API can be accessed from slave
Slave must have access to credentials that allows to access the Jenkins Groovy script REST API.
If you want to use a declarative approach I suggest you to use code like this.
pipeline {
stages {
// create separate stage only for problematic command
stage("build") {
steps {
sh "command"
}
post {
failure {
// set status
unstable 'rsync was unsuccessful'
}
always {
echo "Do something at the end of stage"
}
}
}
}
post {
always {
echo "Do something at the end of pipeline"
}
}
}
In case you want to keep everything in one stage use catchError
pipeline {
stages {
// create separate stage only for problematic command
stage("build") {
steps {
catchError(stageResult: 'UNSTABLE') {
sh "command"
}
sh "other command"
}
}
}
}
One easy way to set a build as unstable, is in your "execute shell" block, run exit 13
You can just call "exit 1", and the build will fail at that point and not continue. I wound up making a passthrough make function to handle it for me, and call safemake instead of make for building:
function safemake {
make "$#"
if [ "$?" -ne 0 ]; then
echo "ERROR: BUILD FAILED"
exit 1
else
echo "BUILD SUCCEEDED"
fi
}

Disable auto-completion of remote branches in Git Bash?

I'm working on a fairly large git repo with a couple of thousand (remote) branches. I am used to using auto-completion (using [TAB]) in the console (Git Bash in that case), so I unconsciously do that for git commands, too.
e.g. I'd type
git checkout task[TAB]
with the effect that the console stalls for often minutes. Is there a way to limit auto-completion to local branches only?
With Git 2.13 (Q2 2017), you can disable (some of) the branch completion.
git checkout --no-guess ...
# or:
export GIT_COMPLETION_CHECKOUT_NO_GUESS=1
See commit 60e71bb (21 Apr 2017) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit b439747, 01 May 2017)
As documented in contrib/completion/git-completion.bash now:
You can set the following environment variables to influence the behavior of the completion routines:
GIT_COMPLETION_CHECKOUT_NO_GUESS
When set to "1", do not include "DWIM" suggestions in git-checkout
completion (e.g., completing "foo" when "origin/foo" exists).
Note: DWIM is short for Do What I Mean, where a system attempts to anticipate what users intend to do, correcting trivial errors automatically rather than blindly executing users' explicit but potentially incorrect inputs.
completion: optionally disable checkout DWIM
When we complete branch names for "git checkout", we also complete remote branch names that could trigger the DWIM behavior. Depending on your workflow and project, this can be either convenient or annoying.
For instance, my clone of gitster.git contains 74 local "jk/*" branches, but origin contains another 147.
When I want to checkout a local branch but can't quite remember the name, tab completion shows me 251 entries. And worse, for a topic that has been picked up for pu, the upstream branch name is likely to be similar to mine, leading to a high probability that I pick the wrong one and accidentally create a new branch.
Note: "picked up for pu": see a What's cooking in git.git: it starts with:
Commits prefixed with '-' are only in 'pu' (proposed updates) while commits prefixed with '+' are in 'next'.
This is part of the Git Workflow Graduation process.
pu (proposed updates) is an integration branch for things that are not quite ready for inclusion yet
This patch adds a way for the user to tell the completion
code not to include DWIM suggestions for checkout.
This can already be done by typing:
git checkout --no-guess jk/<TAB>
but that's rather cumbersome.
The downside, of course, is that you no longer get completion support when you do want to invoke the DWIM behavior.
But depending on your workflow, that may not be a big loss (for instance, in git.git I am much more likely to want to detach, so I'd type "git checkout origin/jk/<TAB>" anyway).
I'm assuming that you are using the git-completion.bash script, and that you only care about git checkout.
To accomplish this, I just changed one line in the definition of the _git_checkout () function in git-completion.bash:
< __gitcomp_nl "$(__git_refs '' $track)"
---
> __gitcomp_nl "$(__git_heads '' $track)"
My understanding is that this only affects the tab-completion action (because of its location within the * case of the switch-case statement).
If you installed git-completion via homebrew, it's located here:
/usr/local/etc/bash_completion.d/git-completion.bash
Following erik.weathers' answer above, I made the following change so autocompletion can work for both local and remote based on the current prefix. By default, it'll only search local, but if I specify origin/… it'll know I want to search remote branches too.
In the _git_checkout () method, change
__gitcomp_nl "$(__git_refs '' $track)"
to:
# only search local branches instead of remote branches if origin isn't specified
if [[ $cur == "origin/"* ]]; then
__gitcomp_nl "$(__git_refs '' $track)"
else
__gitcomp_nl "$(__git_heads '' $track)"
fi
Of course, you can change origin to something else or you can have it search through through a list of remote prefixes if you have more than 1.
You can hack /etc/bash_completion.d/git
You'll need to edit __git_refs ()
Note that the change in behaviour will apply every where (so even with git push/pull where you might not want it to). You could of course, make a copy of the function or pass an extra parameter, but I leave that to you
You could think that you just the local branches with the alias co and all the branches with the complete command checkout.
You could perform the following. In your .bashrc, you redefine the _git_checkout() function. You let this function unchanged, except the end:
if [ $command -eq "co" ]; then
__gitcomp "$(__git_refs_local '' $track)"
else
__gitcomp "$(__git_refs '' $track)"
fi
Then, you just have to define a new function, __git_refs_local, where you remove the remote stuff.
Carey Metcalfe wrote a blog post containing a solution that also edits the auto-completion function, but with slightly newer code than other answers. He also defines an alias checkoutr that keeps the old auto-complete behavior in case it’s ever needed.
In short, first create the checkoutr alias with this command:
git config --global alias.checkoutr checkout
Then find git-completion.bash, copy the _git_checkout function into your shell’s RC file so that it gets redefined, and inside that function, replace this line:
__git_complete_refs $track_opt
with the following lines:
if [ "$command" = "checkoutr" ]; then
__git_complete_refs $track_opt
else
__gitcomp_direct "$(__git_heads "" "$cur" " ")"
fi
See the blog post for more details and potential updates to the code.
Modifying $(brew --prefix)/etc/bash_completion.d/git-completion.bash is not a good idea because it will be overwritten every time you update Git through Homebrew.
Combining all the answers I overwrite only _git_checkout function from the completion file in my .bash_profile after sourcing the completion file:
_git_checkout ()
{
__git_has_doubledash && return
case "$cur" in
--conflict=*)
__gitcomp "diff3 merge" "" "${cur##--conflict=}"
;;
--*)
__gitcomp "
--quiet --ours --theirs --track --no-track --merge
--conflict= --orphan --patch
"
;;
*)
# check if --track, --no-track, or --no-guess was specified
# if so, disable DWIM mode
local flags="--track --no-track --no-guess" track=1
if [ -n "$(__git_find_on_cmdline "$flags")" ]; then
track=''
fi
# only search local branches instead of remote branches if origin isn't
# specified
if [[ $cur == "origin/"* ]]; then
__gitcomp_nl "$(__git_refs '' $track)"
else
__gitcomp_nl "$(__git_heads '' $track)"
fi
;;
esac
}
I'm not using Git Bash myself, but if this is the same as mentioned in
http://tekrat.com/2008/04/30/bash-autocompletion-git-super-lazy-goodness/, you should be able to replace git branch -a with a plain git branch in
_complete_git() {
if [ -d .git ]; then
branches=`git branch -a | cut -c 3-`
tags=`git tag`
cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=( $(compgen -W "${branches} ${tags}" -- ${cur}) )
fi
}
complete -F _complete_git git checkout
(in your .profile or similar) and get what you want.
FWW here is a hack to __git_complete_refs that does the trick
__git_complete_refs ()
{
local remote track pfx cur_="$cur" sfx=" "
while test $# != 0; do
case "$1" in
--remote=*) remote="${1##--remote=}" ;;
--track) track="yes" ;;
--pfx=*) pfx="${1##--pfx=}" ;;
--cur=*) cur_="${1##--cur=}" ;;
--sfx=*) sfx="${1##--sfx=}" ;;
*) return 1 ;;
esac
shift
done
echo cur_ $cur_ > a
if [[ $GIT_COMPLETION_CHECKOUT_NO_GUESS != 1 || $cur_ == "origin"* ]]; then
__gitcomp_direct "$(__git_refs "$remote" "$track" "$pfx" "$cur_" "$sfx")"
else
__gitcomp_direct "$(__git_heads "" "$cur_")"
fi
}

Resources