Git Bash shell fails to create symbolic links - msysgit

When I try to create a symbolic link from the Git Bash shell, it fails every time all the time:
ln -s /c/Users/bzisad0/Work testlink
Output:
ln: creating symbolic link `testlink' to `/c/Users/bzisad0/Work': Permission denied
The only thing it does, besides giving the error message, is create an empty directory named (in this case) testlink.
I don't see any problem with the ln executable. For instance, it is owned by me and marked as executable:
which ln
ls -hal /bin/ln
Output:
/bin/ln
-rwxr-xr-x 1 BZISAD0 Administ 71k Sep 5 11:55 /bin/ln
I also own the current directory (~, which is /c/Users/bzisad0):
ls -dhal .
Output:
drwxr-xr-x 115 BZISAD0 Administ 40k Sep 5 12:23 .
I have administrative rights, and I've tried opening the Git Bash shell with "Run as Administrator", but that makes no difference.
I've tried opening the Windows properties for ln.exe and setting the Privilege Level to "Run this program as an administrator" but that doesn't help.
I've gone into the Security → Advanced properties in Windows and made myself (rather than the Administrators group) the owner, but that doesn't fix anything either.
I'm at a loss. I don't know whether this error message is ultimately coming from ln, from Bash, or from Windows, or how I could possibly lack the permission. How can I get to the bottom of this?

It is possible, albeit extremely awkward, to create a symbolic link in MSysGit.
First, we need to make sure we are on Windows. Here's an example function to check that:
windows() { [[ -n "$WINDIR" ]]; }
Now, we can't do cmd /C, because MSysGit will fornicate with this argument and turn it into C:. Also, don't be tempted to use /K; it only works if you don't have a K: drive.
So while it will replace this value on program arguments, it won't on heredocs. We can use this to our advantage:
if windows; then
cmd <<< "mklink /D \"${link%/}\" \"${target%/}\"" > /dev/null
else
ln -s "$target" "$link"
fi
Also: note that I included /D because I'm interested in directory symlinks only; Windows has that distinction. With plenty of effort, you could write a ln() { ... } function that wraps the Windows API and serves as a complete drop-in solution, but that's... left as an exercise for the reader.
As a thank-you for the accepted answer, here's a more comprehensive function.
# We still need this.
windows() { [[ -n "$WINDIR" ]]; }
# Cross-platform symlink function. With one parameter, it will check
# whether the parameter is a symlink. With two parameters, it will create
# a symlink to a file or directory, with syntax: link $linkname $target
link() {
if [[ -z "$2" ]]; then
# Link-checking mode.
if windows; then
fsutil reparsepoint query "$1" > /dev/null
else
[[ -h "$1" ]]
fi
else
# Link-creation mode.
if windows; then
# Windows needs to be told if it's a directory or not. Infer that.
# Also: note that we convert `/` to `\`. In this case it's necessary.
if [[ -d "$2" ]]; then
cmd <<< "mklink /D \"$1\" \"${2//\//\\}\"" > /dev/null
else
cmd <<< "mklink \"$1\" \"${2//\//\\}\"" > /dev/null
fi
else
# You know what? I think ln's parameters are backwards.
ln -s "$2" "$1"
fi
fi
}
Also note a few things:
I just wrote this and briefly tested it on Windows 7 and Ubuntu, give it a try first if you're from 2015 and using Windows 9.
NTFS has reparse points and junction points. I chose reparse points, because it's more of an actual symbolic link and works for files or directories, but junction points would have the benefit of being an usable solution in Windows XP, except it's just for directories.
Some filesystems, the FAT ones in particular, do not support symbolic links. Modern Windows versions do not support booting from them anymore, but Windows and Linux can mount them.
Bonus function: remove a link.
# Remove a link, cross-platform.
rmlink() {
if windows; then
# Again, Windows needs to be told if it's a file or directory.
if [[ -d "$1" ]]; then
rmdir "$1";
else
rm "$1"
fi
else
rm "$1"
fi
}

For my setup, that is Git for Windows 2.11.0 installed on Windows 8.1, export MSYS=winsymlinks:nativestrict does the trick as
The Git Bash shell may need to be run as an administrator, as by default on Windows only administrators can create the symbolic links.
So, in order to make tar -xf work and create the required symbolic links:
Run Git Bash shell as an administrator
Run export MSYS=winsymlinks:nativestrict
Run tar

A workaround is to run mklink from Bash. This also allows you to create either a symbolic link or a junction point.
Take care to send the mklink command as a single argument to cmd...
cmd /c "mklink link target"
Here are the options for mklink...
cmd /c mklink
Output:
Creates a symbolic link.
MKLINK [[/D] | [/H] | [/J]] Link Target
/D Creates a directory symbolic link. Default is a file
symbolic link.
/H Creates a hard link instead of a symbolic link.
/J Creates a Directory Junction.
Link specifies the new symbolic link name.
Target specifies the path (relative or absolute) that the new link
refers to.
If you want to create links via a GUI instead ... I recommend Link Shell Extension that is a Windows Explorer plugin for creating symbolic links, hard links, junction points, and volume mount points. I've been using it for years!
Link Shell Extension
Symbolic links can be a life saver if you have a smaller SSD drive on your system C: drive and need to symbolic link some bloated folders that don't need to be on SSD, but off onto other drives. I use the free WinDirStat to find the disk space hogs.

I believe that the ln that shipped with MSysGit simply tries to copy its arguments, rather than fiddle with links. This is because links only work (sort of) on NTFS filesystems, and the MSYS team didn't want to reimplement ln.
See, for example, http://mingw.5.n7.nabble.com/symbolic-link-to-My-Documents-in-MSYS-td28492.html

Do
Grant yourself privileges to create symbolic links.
Search for local security policies
Local Policies/User Rights Assignment/Create symbolic links
Take a moment to scold Windows. "Bad OS! Bad!"
Profit
This grants you the privilege to create symbolic links. Note, this takes effect on the next login.
The next step is to figure out how ln is configured:
env | grep MSYS
We are looking for MSYS=winsymlink: which controls how ln creates symbolic links.
If the variable doesn't exist, create it. Note, this will overwrite the existing MSYS environment variable.
setx MSYS winsymlinks:nativestrict
Do not
Run your shell as an administrator just to create symbolic links.
Explanation
The error is somewhat self-explanatory, yet elusive.
You lack the appropriate privileges to run the command.
Why?
Be default, Windows only grants symlink creation rights to Administrators.
Cygwin has to do a song and dance to get around Windows subpar treatment of symbolic links.
Why?
Something, something "security"
¯\_(ツ)_/¯
Edit:
I just realized OP had admin rights. I leave this answer up, hoping it's useful to others.

Extending Camilo Martin's answer as you need to use the /j parameter switch for Windows 10; otherwise the call will just return "You do not have sufficient privilege to perform this operation."
This works for Git Bash 2.20.1.windows.1/MINGW64 (Windows 10) without administrator rights (if you can read/write both /old/path and /link/path:
original_folder=$(cygpath -w "/old/path")
create_link_new_folder=$(cygpath -w "/link/path")
cmd <<< "mklink /j \"${create_link_new_folder}\" \"${original_folder}\"" > /dev/null

For anyone who's interested in how to accomplish this in Windows 10 Git Bash 2.28.0.0.1:
You have to prefix the ln -s command with the MSYS=.. instead of executing export MSYS=.. first, namely it's just one command:
MSYS=winsymlinks:nativestrict ln -s <TARGET> <NEW_LINK_NAME>

Since this is one of the top links that come up when searching for creating symbolic links in MSYS or Git Bash, I found the answer was to add
set MSYS=winsymlinks:native when calling git-cmd.exe (I run ConEmu) or uncomment the same line in the msys2_shell.bat file.

I prefer PowerShell to CMD, and thought I'd share the PowerShell version of this.
In my case it consists of making symbolic links linking ~/.$file to ~/dotfiles/$file, for dotfile configurations. I put this inside a .sh script and ran it with Git Bash:
powershell New-Item -ItemType SymbolicLink\
-Path \$Home/.$file\
-Target \$Home/dotfiles/$file

Instead of symbolic links on Windows, I found it easier to write a small Bash script that I place in my ~/bin directory.
To start Notepad++ with the npp command, I have this file:
~/bin/npp
#!/usr/bin/bash
'/c/Program Files (x86)/Notepad++/notepad++.exe' $#
And I get the path syntax right by dragging and dropping the file from Windows Explorer into Vim.
The Windows command mklink /J Link Target doesn't seem to work any more.

git bash honors the symbolic links created by cygwin. The caveat is that the symbolic link not use, e.g., '/cygdrive/c/directory' and instead use '/c/directory'.

Related

Unable to change directory in command line

I'm on c drive in command line
when I want to change the directory to D drive using :
cd /C D:\Riot Games
I get an error also with cmder
Assuming you're using Windows, and you're using the standard cmd.exe for your command line, you should be quoting your path arguments for safety even though the docs will tell you it isn't strictly necessary since the command doesn't treat spaces as delimiters. The correct syntax is simply:
cd "D:\Riot Games"
There is also no /C argument to the cd command. You may have meant to use:
cd /D "D:\Riot Games"
to change the current drive in addition to changing the current directory, but whether that's actually necessary isn't clear from your example.
On Windows CMD, you first need to switch to your other drive by just typing D:, then you can change directory by using the cd "Riot Games"

Autocomplete directories in a subfolder with the Fish shell

I'm having trouble getting the 'complete' function in the fish shell to behave as I would like and I've been searching for an answer for days now.
Summary
Essentially I need to provide tab directory auto-completion as if I was in a different directory to the one I am currently in. It should behave exactly as 'cd' and 'ls' do, but with the starting point in another directory. It seems like such a trivial thing to be able to do but I can't find a way to make it work.
Explanation
Example folder structure below
- root
- foo
- a
- dir1
- subdir1
- dir2
- subdir2
- b
- dir3
- subdir3
- dir4
- subdir4
I am running these scripts whilst in the 'root' directory, but I need tab auto-complete to behave as if I was in the 'foo' directory.
testfunc -d a/dir2/subdir2
Instead of
testfunc -d foo/a/dir2/subdir2
There are a lot of directories inside 'foo' and a lot of sub-directories within them, and this auto-complete behaviour is necessary to speed our process (this script is used extensively throughout the day).
Attempted Solution
I've tried using the 'complete' builtin to get this working by specifying the directory to use, but all this managed to do was auto-complete the first level of directories with a space after the argument instead of continuing to auto-complete like 'cd' would.
complete -x -c testfunc -a "(__fish_complete_directories ./foo/)"
Working bash version
I have already got this working in Bash and I am trying to port it over to fish. See below for the Bash version.
_testfunc()
{
local cur prev words cword
_init_completion || return
compopt +o default
case $prev in
testfunc)
COMPREPLY=( $( compgen -W '-d' -- "$cur" ) )
compopt +o nospace
return
;;
-d)
curdir=$(pwd)
cd foo/ 2>/dev/null && _filedir -d
COMPREPLY=( $( compgen -d -S / -- "$cur" ) )
cd $curdir
return
;;
esac
} &&
complete -o nospace -F _testfunc testfunc
This is essentially stepping into the folder that I want, doing the autocompletion, then stepping back into the original folder that the script was run in. I was hoping this would be easier in Fish after getting it working in Bash (I need to support these two shells), but I'm just pulling my hair out.
Any help would be really appreciated!
I am not a bash completions expert, but it looks like the bash completions are implemented by changing directories, running completions, and then changing back. You can do the same in fish:
function complete_testfunc
set prevdir $PWD
cd foo
__fish_complete_directories
cd $prevdir
end
complete -x -c testfunc -a "(complete_testfunc)"
does that work for you?

why doesn't make -C change $PWD as seen through a scripting language such as Perl?

Here is temp/Makefile:
all:
echo $$PWD
echo $(CURDIR)
perl -e 'print $$ENV{"PWD"}'
and now
$make -C temp
make: Entering directory `/home/mgaleck/temp'
/home/mgaleck/temp
/home/mgaleck/temp
/home/mgaleck
make: Leaving directory `/home/mgaleck/temp'
Why is the third value without temp?
According to Make manual, -C option causes to "change the directory" (working directory?) first.
Same thing happens with Python.
Because the PWD environment variable doesn't hold the current working directory; it holds whatever the current working directory was the last time sh set it. Anything other than a shell starting up, or a shell executing the cd builtin (or a similar builtin like pushd, in shells that have it), has no effect on PWD, and relying on PWD anywhere except in the shell is probably a silly idea. Use getcwd (C), Cwd::getcwd (Perl), os.getcwd (Python), etc. instead.

ssh-keygen: The System Cannot Find the Path Specified

ssh-agent, ssh-add all works on Cmder running PowerShell/Posh-Git
I have added C:\Program Files\Git\usr\bin Environment Variables
ssh-keyen works fine on Git Bash,
how can I make ssh-keygen to also work with Powershell/Posh-Git ?
EDIT:
It turns out that the error is due to the fact that ssh-keygen in PowerShell is running the ssh-keygen.bat file instead of ssh-keygen.exe
So setting an alias as mention below is the way to go.
I use Set-Alias instead of New-Alias because New-Alias requires me to reset my $profile every now and then, which is weird.
To reset, type . $profile
One way to do this is to add it to your path.
Unfortunately, there appears to be no way to add only the executable. In other words, you will be required to add the whole bin folder (namely C:\Program Files\Git\usr\bin) to your path.
I can show you how to add the whole directory to the path, but I don't think this is what you want. Instead, below is an alternative method that only adds the ssh-keygen.exe executable.
Navigate to $env:homepath\Documents\WindowsPowerShell (create it if you don't have it).
Create a file called profile.ps1.
Add to the file the following line of code.
New-Alias Ssh-Keygen "C:\Program Files\Git\usr\bin\ssh-keygen.exe"
Now, each time you launch Powershell, Ssh-Keygen will be available. It even works with tab completion (e.g. type ssh- and press tab, then it automatically becomes Ssh-Keygen).
Actually, you can add single executables from the git bin folder to your path by symlinking them into a folder that is contained in your path variable.
I also wanted to use some of the git tools inside powershell but I didn't want to load the git-bin-folder into path every time I wanted to use one of the tools and I also didn't want to overwrite tools like cp, find, ls and so on...
Create a folder that you can add to path. I created mine in C:\dev\bin and added it to the path variable.
Then you need to symlink all the dll-files from the git bin folder to your new bin folder because the tools you'll link need them in the same directory (the runpath won't be the git-bin-dir but the new bin-dir). I'll add a Script to do that below.
And finally you just need to create symlinks for all tools you'd like to use in powershell as well (hint: ssh.exe is really nice to use in powershell ;))
Here are my powershell functions that'll help you set up your new bin-folder with all the nice tools from git:
# generic symlink function
function Create-Symlink {
param(
[string]$link,
[string]$target
)
& cmd.exe /c mklink "$link" "$target"
}
# symlink all teh git dlls
function Create-GitDllSymlinks {
param(
[string]$newBinDir='C:\dev\bin',
[string]$gitBinDir='C:\Program Files (x86)\Git\bin'
)
$dlls = gci $gitBinDir -Filter *.dll
$dlls | foreach {
Create-Symlink -link (join-path $newBinDir $_.Name) -target $_.FullName
}
}
# to easily link your git tools
function Create-GitSymlink {
param(
[string]$executable,
[string]$newBinDir='C:\dev\bin',
[string]$gitBinDir='C:\Program Files (x86)\Git\bin'
)
if (-not $executable.EndsWith('.exe')) {
$executable = ($executable + '.exe')
}
Create-Symlink -link (join-path $newBinDir $executable) -target (join-path $gitBinDir $executable)
}
# create all dll symlinks needed
Create-GitDllSymlinks
# link ssh and ssh-keygen to use in powershell
Create-GitSymlink -executable ssh-keygen
Create-GitSymlink -executable ssh
Another nice thing to do is this:
create a bash.bat file in your new bin-folder
write this into that .bat file:
"C:\Program Files (x86)\Git\bin\sh.exe" --login -i
quickly switch between powershell and bash:
when in powershell, type bash and git-bash will start inside the same window with access to all git tools in the git-bin-folder.
want to go back to powershell? just type exit!
Edit:
I reinstalled my computer a few days ago (with Win 10) and ran into some issues with my solution above. Apparently, git and/or the cygwin environment in git has been updated and the cygwin applications now search for their unix-environment-paths differently.
For example, ssh always said that it couldn't find my home path:
Could not create directory '/home/myuser/.ssh'.
The authenticity of host 'hostx (IP)' can't be established.
ECDSA key fingerprint is SHA256:xxxxxx.
Are you sure you want to continue connecting (yes/no)?
Using procmon I found out, that it was looking for files in c:\etc and c:\home\myuser. Setting environment variables like HOME or USERPROFILE didn't work, so I just made two additional symlinks to satisfy my cygwin tools:
PS bin # cmd /c mklink /D C:\etc "C:\Program Files\Git\etc"
symbolic link created for C:\etc <<===>> C:\Program Files\Git\etc
PS bin # cmd /c mklink /D C:\home "C:\Users"
symbolic link created for C:\home <<===>> C:\Users
I solved the problem by generating my own ssh key pair using command
$ ssh-keygen -t rsa -C "your_email#mail.com"
in powershell. If you already have a SSH key, then don't a generate new key, as they will be overwritten. You can use ssh-keygen command, only if you have installed Git with Git Bash.
When you run the above command, it will create 2 files in the ~/.ssh directory.
~/.ssh/id_rsa − It is private key or identification key.
~/.ssh/id_rsa.pub − public key
See link generate ssh key

How do I create an alias for a directory in ipython?

On Windows I want to create an alias for my working directory so I can quickly cd into it.
I have tried this command
%alias $UWHPSC echo 'c:/Users/xxxx/Documents/uwhpsc'
cd $UWHPSC
which gives the following error
[Error 2] The system cannot find the file specified: u'$UWHPSC'
c:\Users\xxxx\Documents\uwhpsc
%cd has a notion of bookmarks, which persist across IPython sessions:
%bookmark UWHPSC c:/Users/xxxx/Documents/uwhpsc
%cd UWHPSC
See the output of %bookmark? for more info.
Just define a normal Python variable, and then use it with a $ in the cd command:
UWHPSC = 'c:/Users/xxxx/Documents/uwhpsc'
cd $UWHPSC