zsh: Find out VCS of current directory - version-control

I'd like to create a zsh widget to perform common tasks of a version control systems with a single button. E.g. pressing F1 should call "svn status", if the current directory is part of a Subversion checkout. If it is in a git repository, it should call "git status -s".
Now, creating the widget is no big deal. But how do I determine which VCS is in the current directory?
I know about vcs_info and I use a lot. But I couldn't find any way to retrieve the most basic information, it provides. Any ideas?

What about testing the existence of the specific meta data directories?
if test -d CVS; then
# CVS
elif test -d .hg; then
# Mercurial
elif test -d .git; then
# Git
elif test -d .svn; then
# Subversion
else
# unknown
fi

Thanks to all for comments. My solution now looks like this:
vcs-status() {
setopt no_autopushd
cmd=""
cur_dir=`pwd`
while [[ `pwd` != "/" ]]; do
if [[ -d .svn ]]; then
cmd="svn status^M"
break
elif [[ -d .git ]]; then
cmd="git status -s^M"
break
fi
cd ..
done
cd $cur_dir
if [[ -n $cmd ]]; then
zle -U $cmd
fi
setopt autopushd
}
zle -N vcs-status
bindkey "\eOP" vcs-status
Suggestions to cool zsh'ish improvements are welcome :-)
I also implemented the suggestion of Thomas, using svn info and git rev-parse. But then I noticed a problem: If I have an SVN working copy checked out somewhere inside a GIT working copy (yes, this is necessary occationally), or vice versa, I would only find the first one I'm checking. With the solution above, I find the "inner-most", which is what I want. In fact, this is a problem that also has been annoying me about vcs_info for a while.

Related

Is there a linter check to confirm that a related file has been updated in the commit?

I once saw (can't remember where) a lint check in a file that looked something like:
"If this file is modified, I will complain if files /somehwere/a and /somehwere/b aren't modified in the same commit".
Do you know of any linter capable of handling something like this? Maybe not a linter but something that can be integrated to a github repo?
If you have a CI system set up for your repository, you can run a command like the following, which will exit nonzero if there's a problem commit, and zero if all commits are fine (with $BASE and $HEAD set appropriately):
git rev-list $BASE..$HEAD | \
xargs -L1 sh -c 'lines=$(git show --name-only $0 | grep -e somewhere/a -e somewhere/b | wc -l); \
test $lines -ne 1 || { echo "bad commit $0"; false; }'
Any suitable CI system can test this by running a shell script and failing if it exits nonzero.

How to automatically activate virtualenvs when cd'ing into a directory

I have a bunch of projects in my ~/Documents. I work almost exclusively in python, so these are basically all python projects. Each one, e.g. ~/Documents/foo has its own virtualenv, ~/Documents/foo/venv (they're always called venv). Whenever I switch between projects, which is ~10 times a day, I do
deactivate
cd ..
cd foo
source venv/bin/activate
I've reached the point where I'm sick of typing deactivate and source venv/bin/activate. I'm looking for a way to just cd ../foo and have the virtualenv operations handled for me.
I'm familiar with VirtualEnvWrapper which is a little heavy-handed in my opinion. It seems to move all your virtualenvs somewhere else, and adds a little more complexity than it removes, as far as I can tell. (Dissenting opinions welcome!)
I am not too familiar with shell scripting. If you can recommend a low-maintenance script to add to my ~/.zshrc that accomplishes this, that would be more than enough, but from some quick googling, I haven't found such a script.
I'm a zsh/oh-my-zsh user. oh-my-zsh doesn't seem to have a plugin for this. The best answer to this question would be someone contributing an oh-my-zsh plugin which does this. (Which I might do if the answers here are lackluster.
Add following in your .bashrc or .zshrc
function cd() {
builtin cd "$#"
if [[ -z "$VIRTUAL_ENV" ]] ; then
## If env folder is found then activate the vitualenv
if [[ -d ./.env ]] ; then
source ./.env/bin/activate
fi
else
## check the current folder belong to earlier VIRTUAL_ENV folder
# if yes then do nothing
# else deactivate
parentdir="$(dirname "$VIRTUAL_ENV")"
if [[ "$PWD"/ != "$parentdir"/* ]] ; then
deactivate
fi
fi
}
This code will not deactivate the virtualenv even if someone goes into subfolder. Inspired by answers of #agnul and #Gilles.
If the virtualenv is made by pipenv, then please consider this wiki page.
Furthermore, for added security please consider direnv.
Put something like this in your .zshrc
function cd() {
if [[ -d ./venv ]] ; then
deactivate
fi
builtin cd $1
if [[ -d ./venv ]] ; then
. ./venv/bin/activate
fi
}
Edit: As noted in comments cd-ing into a subfolder of the current virtual env would deactivate it. One idea could be to deactivate the current env only if cd-ing into a new one, like
function cd() {
builtin cd $1
if [[ -n "$VIRTUAL_ENV" && -d ./venv ]] ; then
deactivate
. ./venv/bin/activate
fi
}
that could still be improved, maybe turning it into a "prompt command" or attempting some prefix matching on the folder names to check there's a virtual env somewhere up the path, but my shell-fu is not good enough.
You should try something like autoenv if not direnv.
The first one is considered to be "lightweight", while the second one "simply, higher quality software", listening respectively to each one's author, talking about the other one's project. Thus, they seem to me fairly good options, to try both!
Anyway, both have been tested on zsh shells.
In particular, autoenv is really simple to use, after installing it:
$ git clone git://github.com/inishchith/autoenv.git ~/.autoenv
$ echo 'source ~/.autoenv/activate.sh' >> ~/.bashrc
just "follow the white rabbit " and try for example
$ mkdir project
$ echo "echo 'whoa'" > project/.env
$ cd project
whoa
"If a directory contains a .env file, it will automatically be executed when you cd into it. When enabled (set AUTOENV_ENABLE_LEAVE to a non-null string), if a directory contains a .env.leave file, it will automatically be executed when you leave it."
Have a look at https://github.com/inishchith/autoenv for more detailed instructions!...
Rather than writing a custom script you could use direnv. It's not a zsh specific solution (for that you could try zsh-autoenv), but is well-maintained and easy to use with zsh. Once you've installed it, you'd want to put eval "$(direnv hook zsh)" at the end of your .zshrc. At that point you can do:
$ source ~/.zshrc
$ cd foo
$ echo "layout python" > .envrc
direnv: error .envrc is blocked. Run `direnv allow` to approve its content.
$ direnv allow
direnv: loading .envrc
direnv: export +VIRTUAL_ENV ~PATH
Now you should be in your virtualenv. You can test by running pip freeze to see that your virtualenv specific packages are installed. To deactivate
$ cd ..
direnv: unloading
By far the easiest option (in 2019+) is to add virtualenvwrapper into your ~/.zshrc plugins
For example:
plugins=(
git pip python brew virtualenvwrapper
)
For anyone using (or considering to use) pyenv this can be achieved very conveniently using the pyenv-virtualenv plugin as described here.
Basically you simply add a .python-version file to the directory in which the name of the virtualenv is specified.
For posterity: I used #MS_'s solution but ran into the problem where cding directly from one project to another deactivates the old virtualenv but doesn't activate the new one. This is a slightly modified version of that solution which solves this problem:
# auto activate virtualenv
# Modified solution based on https://stackoverflow.com/questions/45216663/how-to-automatically-activate-virtualenvs-when-cding-into-a-directory/56309561#56309561
function cd() {
builtin cd "$#"
## Default path to virtualenv in your projects
DEFAULT_ENV_PATH="./env"
## If env folder is found then activate the vitualenv
function activate_venv() {
if [[ -f "${DEFAULT_ENV_PATH}/bin/activate" ]] ; then
source "${DEFAULT_ENV_PATH}/bin/activate"
echo "Activating ${VIRTUAL_ENV}"
fi
}
if [[ -z "$VIRTUAL_ENV" ]] ; then
activate_venv
else
## check the current folder belong to earlier VIRTUAL_ENV folder
# if yes then do nothing
# else deactivate then run a new env folder check
parentdir="$(dirname ${VIRTUAL_ENV})"
if [[ "$PWD"/ != "$parentdir"/* ]] ; then
echo "Deactivating ${VIRTUAL_ENV}"
deactivate
activate_venv
fi
fi
}
This is a zsh only solution.
This is an improvement over daveruinseverything's answer which is an improvement over MS_'s answer.
We are using precmd hook instead of overwriting cd.
We have added another extra feature. Suppose the directory structure is
├── .venv
│ ├── bin
│ │ └── activate
├── subdir
│ ├── subdir1
│ │ ├── subdir2
│ │ │ └── test2.txt
│ │ └── test1.txt
│ └── test.txt
└── testing.py
If you now open new terminal in subdir2, or directly cd to subdir2 from other place, it will activate the venv.
The solution is:
autoload -Uz add-zsh-hook
add-zsh-hook precmd automatically_activate_python_venv
function automatically_activate_python_env() {
if [[ -z $VIRTUAL_ENV ]] ; then
activate_venv
else
parentdir="$(dirname ${VIRTUAL_ENV})"
if [[ "$PWD"/ != "$parentdir"/* ]] ; then
deactivate
activate_venv
fi
fi
}
function activate_venv() {
local d n
d=$PWD
until false
do
if [[ -f $d/.venv/bin/activate ]] ; then
source $d/.venv/bin/activate
break
fi
d=${d%/*}
# d="$(dirname "$d")"
[[ $d = *\/* ]] || break
done
}
that is the solution without cd'ing, with zsh set to setop auto_cd w'll be able to change directories without cd, just type directory name and hit enter.
it is anhence of above solution:
# auto activate virtualenv
# Modified solution based on https://stackoverflow.com/questions/45216663/how-to-automatically-activate-virtualenvs-when-cding-into-a-directory/56309561#56309561
function auto_active_env() {
## Default path to virtualenv in your projects
DEFAULT_ENV_PATH="./env"
## If env folder is found then activate the vitualenv
function activate_venv() {
if [[ -f "${DEFAULT_ENV_PATH}/bin/activate" ]] ; then
source "${DEFAULT_ENV_PATH}/bin/activate"
echo "Activating ${VIRTUAL_ENV}"
fi
}
if [[ -z "$VIRTUAL_ENV" ]] ; then
activate_venv
else
## check the current folder belong to earlier VIRTUAL_ENV folder
# if yes then do nothing
# else deactivate then run a new env folder check
parentdir="$(dirname ${VIRTUAL_ENV})"
if [[ "$PWD"/ != "$parentdir"/* ]] ; then
echo "Deactivating ${VIRTUAL_ENV}"
deactivate
activate_venv
fi
fi
}
chpwd_functions=(${chpwd_functions[#]} "auto_active_env")
Similar to Jake's answer but supports cding from one virtualenv to another. In this case it deactivates the old virtualenv then activates the new one.
function cd() {
builtin cd "$#"
if [[ ! -z "$VIRTUAL_ENV" ]] ; then
# If the current directory is not contained
# within the venv parent directory -> deactivate the venv.
cur_dir=$(pwd -P)
venv_dir="$(dirname "$VIRTUAL_ENV")"
if [[ "$cur_dir"/ != "$venv_dir"/* ]] ; then
deactivate
fi
fi
if [[ -z "$VIRTUAL_ENV" ]] ; then
# If config file is found -> activate the vitual environment
venv_cfg_filepath=$(find . -maxdepth 2 -type f -name 'pyvenv.cfg' 2> /dev/null)
if [[ -z "$venv_cfg_filepath" ]]; then
return # no config file found
fi
venv_filepath=$(cut -d '/' -f -2 <<< ${venv_cfg_filepath})
if [[ -d "$venv_filepath" ]] ; then
source "${venv_filepath}"/bin/activate
fi
fi
}
Here is (yet) another solution to automatically activate a virtual environment; it's based on a number of the answers already posted here.
This will work for any Virtual Environment name or directory (not just ./env, ./venv, etc.). Also supports subdirectories, as well as cd-ing into symlinks of virtual environment (parent) folders.
This code searches for a pyvenv.cfg file instead of a particular named directory. If one is found within a subdirectory of the current folder, the environment is automatically activated. Once inside a virtual environment, that state is retained until you move out of the parent virtual environment directory, at which point the environment is deactivated.
Place this inside your .bashrc or .bash_profile.
function cd() {
builtin cd "$#"
if [[ -z "$VIRTUAL_ENV" ]] ; then
# If config file is found -> activate the vitual environment
venv_cfg_filepath=$(find . -maxdepth 2 -type f -name 'pyvenv.cfg' 2> /dev/null)
if [[ -z "$venv_cfg_filepath" ]]; then
return # no config file found
fi
venv_filepath=$(cut -d '/' -f -2 <<< ${venv_cfg_filepath})
if [[ -d "$venv_filepath" ]] ; then
source "${venv_filepath}"/bin/activate
fi
else
# If the current directory is not contained
# within the venv parent directory -> deactivate the venv.
cur_dir=$(pwd -P)
venv_dir="$(dirname "$VIRTUAL_ENV")"
if [[ "$cur_dir"/ != "$venv_dir"/* ]] ; then
deactivate
fi
fi
}
Personally I think it's an improvement on a lot of the solutions here, since it should work for any virtual environment
This is my solution, which:
checks if already at the currently active venv, and do nothing in that case
if there is a venv folder, deactivate the active one if there is one
activate the new venv whatever if there was an old one or not.
In my bash_aliases:
function cd() {
builtin cd "$#"
if [ $(dirname "$VIRTUAL_ENV") == $(pwd) ] ; then
# Already at the active virtual env
return
fi
if [[ -d ./venv ]] ; then
if type deactivate > /dev/null 2>&1 ; then
printf "Deactivating virtualenv %s\n" "$VIRTUAL_ENV"
deactivate
fi
source ./venv/bin/activate
printf "Setting up virtualenv %s\n" "$VIRTUAL_ENV"
fi
}
This is my solution:
If VIRTUAL_ENV is not set then:
Check if we're inside a virtual env
If yes, then activate it
Else (VIRTUAL_ENV is defined), check that the current folder starts with $VIRTUAL_ENV (removing the /venv part) and verify that the deactivate command exists
Deactivate teh environment
This is the script:
function cd() {
builtin cd $1
if [[ -z "${VIRTUAL_ENV}" ]]; then
if [[ -d ./venv && -f ./venv/bin/activate ]]; then
source ./venv/bin/activate
fi
elif [[ ! "$(pwd)" == ${VIRTUAL_ENV:0:n-5}* && ! -z "$(command -v deactivate)" ]]; then
deactivate
fi
}
Note: You need to add this to .bashrc. If it doesn't work, check if your .profile is not overriding your command (it happened to me)
Based on #MS_'s solution:
function cd() {
builtin cd "$#"
## If env folder is found then activate the vitualenv
if [[ -d ./venv ]] ; then
source ./venv/bin/activate
fi
if [[ -n "$VIRTUAL_ENV" ]] ; then
## check the current folder belong to earlier VIRTUAL_ENV folder
# if yes then do nothing
# else deactivate
parentdir="$(dirname "$VIRTUAL_ENV")"
if [[ "$PWD"/ != "$parentdir"/* ]] ; then
deactivate
fi
fi
}
I've used direnv in the past, as others have mentioned. Lyft use aactivator for this exact scenario.
Once the venv is built it must be activated (added to $PATH). We use aactivator to automatically activate the venv each time a user enters the service directory and deactivates as they leave.
I tried direnv as suggested by others but found it a bit too opinionated and it didn't exactly do what I wanted.
The solution below is for fish shell users only. Also, it assumes a pyproject.toml file and stdlib's venv, but that can be easily changed.
The fish shell has the concept of event handlers so you can easily define a function that gets run whenever an "event" gets triggered (Unix signals, environment variables changing, etc). See this a blog post for a quick introduction.
Add the following to your ~/.config/fish/config.fish (and modify as needed):
function venv_activate --on-variable PWD
if test ! -e pyproject.toml
if test -n "$VIRTUAL_ENV"
deactivate
end
return
end
# echo "Found pyproject.toml"
if test ! -d .venv
# echo "Creating $(python -V) venv"
python -m venv .venv --prompt (basename (pwd))
end
source .venv/bin/activate.fish
end
As a side note, the above works nicely with pyenv's .python-version files as well.
You don't have to execute deactivate on the directory where venv exists. When virtual environment is active you can deactivate anywhere.
So, say you have 2 venvs, <somepath>/project1/venv and <somepath>/project2/venv, and currently project1/venv is active.
If you want to switch project2/venv, do below.
cd project2
deactivate && source ./venv/bin/activate
It will deactivate previous one and activate current one.
Now the above you can just make an alias, or shell function in ~/.zshrc as below:
function avenv(){
deactivate
source ./venv/bin/activate
}
go the path where you want to activate the venv and just run avenv.
Python venv has a feature called --prompt, while creating venv you can mention the prompt, so that in the terminal you will understand which venv is active.
python3 -m venv venv --prompt PROJECT1_VENV
Now in terminal, it will come as (PROJECT1_VENV) -> ~
You can use a zsh hook function that runs whenever you change directories. For example, you could add this to your ~/.zshrc:
# Define a function to activate/deactivate virtualenvs based on the current directory
function venv_cd() {
# Check if the current directory has a venv subdirectory
if [[ -d venv ]]; then
# If yes, activate it if it's not already active
if [[ "$VIRTUAL_ENV" != "$PWD/venv" ]]; then
source venv/bin/activate
fi
else
# If no, deactivate the current virtualenv if any
if [[ -n "$VIRTUAL_ENV" ]]; then
deactivate
fi
fi
}
Add the function to the chpwd hook, which runs after every cd
add-zsh-hook chpwd venv_cd
Optionally, run the function once at the start of the session
venv_cd
This should automatically activate the virtualenv in the current directory if it exists, or deactivate it if you move to a directory without one.
Here is an alternative which sets an env variable when cd'ing into a virtualenv directory and checks if an active subdir is a child-directory of said virtualenv.
This will also deactivate when exiting or cd'ing to another directory.
This assumes you're using Pyenv to manage your Python installations, however you can change the pyvenv.cfg notation to anything else.
function cd() {
builtin cd "$#"
if [[ -f ./pyvenv.cfg ]] ; then
export VENV_CURRENT=$PWD
source ./bin/activate
else
if [[ $(env | fgrep VENV_CURRENT) ]]; then
if ! [[ $(pwd | fgrep $VENV_CURRENT) ]]; then
unset VENV_CURRENT
deactivate
fi
fi
fi
}

How can I make a shell script indicate that it was successful?

If I have a basic .sh file containing the following script code:
#!/bin/sh
rm -rf "MyFolder"
How do I make this running script file display results to the terminal that will indicate if the directory removal was successful?
You don't really need to make it say it was successful. You could have it say something only on error ✖, and then silence means success ✔.
That's how the Unix philosophy works:
The rule of silence, also referred to as the silence is golden rule, is an important part of the Unix philosophy that states that when a program has nothing surprising, interesting or useful to say, it should say nothing. It means that well-behaved programs should treat their users' attention and concentration as being valuable and thus perform their tasks as unobtrusively as possible. That is, silence in itself is a virtue. http://www.linfo.org/rule_of_silence.html
That's the way rm itself behaves.
If you are asking about the general case, as suggested by your question's title, you can run your script with sh -x scriptname to see what it's doing. It's also quite common to write diagnostic output into the script itself, and control it with an option.
#!/bin/sh
verbose=false
case $1 in -v | --verbose )
verbose=true
shift ;;
esac
say () {
$verbose || return
echo "$0: $#" >&2
}
say "Removing $dir ..."
rm -rf "$dir" || say "Failed."
If you run this script without any options, it will run silently, like a well-behaved Unix utility should. If you run it with the -v option, it will print some diagnostics to standard error.
rm -rf "My Folder" && echo "Done" || echo "Error!"
You can read more on creating a sequence of pipelines in bash manual
In the bash (and other similar shells) the ? environment variable gives you the exit code of the last executed command. So you can do:
#!/bin/sh
rm -rf "My Folder"
echo $?
UPDATE
If once the rm command has been executed the directory doesn't exist (because it has been successfully removed or because it didn't exist when the command was executed) the script will print 0. If the directory exists (which will mean that the command has been unable to remove it) then the script will print an exit code other than 0. If I understand properly the question this is exactly the requested behavior. If it is not, please correct me.
The previous answers was wrong : rm don't exit with error code > 0 when the dir isn't present.
Instead, I recommend to use :
dir='/path/to/dir'
if [[ -d $dir ]]; then
rm -rf "$dir"
fi
If you want rm to return a status, remove -f flag.
Example on Linux Mint (the dir doesn't exists):
$ rm -rf /tmp/sdfghjklm
$ echo $?
0
$ rm -r /tmp/sdfghjklm
$ echo $?
1

How to find out what commit a checked out file came from

When I check out a file with git checkout $commit $filename and I forget $commit but still remember $filename, how do I find out what $commit was?
First a non-git answer. Check your shell command history. Well, if you didn't use a shell with command history then you don't...
The git answer. You generally cannot find THE $commit. Generally the same contents might have been part of many commits and I don't think git keeps a log of what single file you have checked out (it keeps a log of previous values of HEAD)
Here is a brute force script git-find-by-contents. Call it with your $filename as parameter, and it will show you all commits where this file was included. As the name says
it searches by contents. So it will find files with any name, as long as the contents matches.
#! /bin/sh
tmpdir=/tmp/$(basename $0)
mkdir $tmpdir 2>/dev/null
rm $tmpdir/* 2>/dev/null
hash=$(git hash-object $1)
echo "finding $hash"
allrevs=$(git rev-list --all)
# well, nearly all revs, we could still check the log if we have
# dangling commits and we could include the index to be perfect...
for rev in $allrevs
do
git ls-tree --full-tree -r $rev >$tmpdir/$rev
done
cd $tmpdir
grep $hash *
rm -r $tmpdir
I would not be surprised if there is a more elegant way, but this has worked for me a couple of times in similar situations.
EDIT: a more techy version of the same problem appears here: Which commit has this blob?
I don't think you can. Git just loads that version of the file into the index and your working dir. There is no reference keeping track of what version it loaded
If this is something that you do often, you could write up a script that could do it using git diff --cached and march through all the commits that changed that file.
You can use
git log <filename>
to find out which commit you want to checkout
If you're lucky, you could maybe try a non-git way:
history | grep "git checkout.*filename"
Try this (untested ;):
$ for commit in $(git log --format=%h $filename); do
> if diff <(git show $commit:$filename) $filename >/dev/null; then
> echo $commit
> fi
> done
Simple and elegant:
$ git log -S"$(cat /path/to/file)"
Only works if the content is unique, then again that's the same for the hash-comparison answers that came before.
It also displays only the first version that matches, rather than all.
Here are the details of a script I polished up as the answer to a similar question, and here you can see it in action:
(source: adamspiers.org)

Is there an easy way to revert an entire P4 changelist?

Let's say I checked in a changelist (in Perforce) with lots of files and I'd like to revert the entire changelist. Is there an easy way to "revert" the entire changelist in one fell swoop?
Currently I do something like this for each file in the changelist:
p4 sync //path/to/file#n (where "n" is the previous version of the file)
cp file file#n
p4 sync //path/to/file
p4 edit //path/to/file
cp file#n file
rm file#n
As you can imagine, this is quite cumbersome for a large changelist.
The posted answers provide correct answers, but note also that there is an actual menu option in P4V to do this for you now. It's in the latest 2008.2 Beta, and so should be officially released the the next week or three.
This link gives details.
It should be a lot simpler to use than the earlier answers, but I've not had the opportunity to try it myself yet.
Update This has now been fully released. See Perforce downloads.
This looks interesting. I haven't tried it personally.
The official answer from Perforce is at http://kb.perforce.com/UserTasks/ManagingFile..Changelists/RevertingSub..Changelists but the procedure is not all that much easier than the one you suggest. The script suggested by #ya23 looks better.
For some reason, the awk step does not work for me. I'm running from a Windows environment with emulated Unix command line tools. However, the following does work:
p4 describe -s [changelist_number] | grep // | sed "s/\.\.\. //" | sed "s/#.*//" | p4 -ztag -x - where | grep "... path " | sed "s/\.\.\. path //"
Here are possible locations to get Unix command line tools in a Windows environment:
http://sourceforge.net/projects/getgnuwin32/?source=typ_redirect
http://unxutils.sourceforge.net/
I have the same problem when I want to delete an entire changelist. so I use the following script (notice that it also deletes the changelist's shelve and the changelist itself. if you only want to revert, copy the relevant lines).
Also, make sure the sed applies to your version of p4.
#!/bin/bash
set -e
if [[ $# -ne 1 ]]; then
echo "usage: $(basename $0) changelist"
exit 1
fi
CHANGELIST=$1
#make sure changelist exist.
p4 describe -s $CHANGELIST > /dev/null # set -e will exit automatically if fails
p4 shelve -d -c $CHANGELIST 2> /dev/null || true # changelist can be shelveless
files_to_revert=$(p4 opened 2> /dev/null | grep "change $CHANGELIST" | sed "s/#.*//g")
if [[ -n "$files_to_revert" ]]; then
p4 revert $files_to_revert
fi
p4 change -d $CHANGELIST
The problem starts when you want to revert an entire changelist ( as a bulk ) that you've just submitted, and you need to start reverting files of #n-1 one by one fast ( because it's production ) ...
Wanted to support ya23's answer- the link of a Python script - it's really really easy to use ( and really easy to miss his comment )
You give it the revision you want to rollback, and it prepares everything automatically ( each file's #n-1 & merging and everything ) ... you just submit.