127

Considering that Git does not recognize symbolic links that point outside of the repository, is there any problem using hard links?

Could Git break them? Can you please point me to detailed information?

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Alfredo Palhares
  • 1,421
  • 2
  • 9
  • 7
  • 2
    What are you trying to do and why? A hard link is no different from a normal file. If you were ever to pull a new version from another repository, it would overwrite the one you had - what's the point of linking to something outside the repo? – Carl Norum Sep 16 '10 at 17:46
  • 3
    Git will recognize symlinks that point to a path outside of the repository. – mipadi Sep 17 '10 at 07:38
  • No mipadi, the only way is waving the files in the repo and the symjbolic links in the their "real" location – Alfredo Palhares Sep 17 '10 at 18:54
  • For a use case, I have files in Google Drive that can be opened in diagrams.net, the folder of which is synced to my computer, and it would be cool if I could have those files in my git repo also, with checkouts or changes synced back and forth, but still have web app open them from Drive. – endolith Jul 23 '21 at 22:22

4 Answers4

112

The 'tree' object, representing directories in Git, stores file name and (subset of) permissions. It doesn't store inode number (or other kind of file id). Therefore hard links cannot be represented in git, at least not without third party tools such as metastore or git-cache-meta (and I am not sure if it is possible even with those tools).

Git tries to not touch files that it doesn't need to update, but you have to take into account that git doesn't try to preserve hardlinks, so they can be broken by git.


About symbolic links pointing outside repository: git has no problems with them and should preserve contents of symbolic links... but utility of such links is dubious to me, as whether those symlinks would be broken or not depends on the filesystem layout outside git repository, and not under control of git.

Derek Mahar
  • 27,608
  • 43
  • 124
  • 174
Jakub Narębski
  • 309,089
  • 65
  • 217
  • 230
  • 5
    Symlinks to paths outside of the repo can be useful. I've used them in webapps to point to database or media files not tracked by the repo. That way, the web app's config file can point to a static path, but the actual location of that path can vary between local development and server environments. – mipadi Sep 17 '10 at 07:40
  • @mipadi: BTW. Modern gitweb has special case for displaying symlinks leading after normalization outside repository. – Jakub Narębski Sep 17 '10 at 15:01
  • 6
    Yep, symlinks outside the repo are fine. I used them to point to a massive data directory that I didn't need (or want) versionned. Generally I use relative links. So in may case the repo and the data directory had to sit next to each other in some parent directory. You can do amazing tricks with a symlinks to ../foo. – Adrian Ratnapala Oct 29 '11 at 04:57
  • Unfortunately, the Git repository for *metastore* (git://git.hardeman.nu/metastore.git) is no longer available. – Derek Mahar Nov 28 '17 at 21:49
  • 1
    https://github.com/danny0838/git-store-meta is an alternative to *git-cache-meta*. – Derek Mahar Nov 28 '17 at 22:10
25

I found out that, using hooks, you can capture the git pull event (when there is something to pull...) writing the script event handler to .git/hooks/post-merge file.

First, you have to chmod +x it.

Then, put the ln commands inside it to recreate hard links at each pull. Neat huh!

It works, I just needed that for my project and ls -i shows that files were automatically linked after pull.


My example of .git/hooks/post-merge:

#!/bin/sh
ln -f $GIT_DIR/../apresentacao/apresentacao.pdf $GIT_DIR/../capa/apresentacao.pdf
ln -f $GIT_DIR/../avaliacoesMono/avaliacao_monografias_2011_Nilo.pdf $GIT_DIR/../capa/avaliacoes.pdf
ln -f $GIT_DIR/../posters/poster_Nilo_sci.pdf $GIT_DIR/../capa/poster.pdf
ln -f $GIT_DIR/../monografia/monografia_Nilo.pdf $GIT_DIR/../capa/monografia_Nilo.pdf

IMPORTANT: As you can see, the path to any file in your repository should begin with $GIT_DIR, then add the partial relative path to the file.

Also important: -f is necessary, because you are recreating the destination file.

EDIT

Modern git client seems to support symlinks and hardlinks inside of the repository naturally, even when pushing to a remote location and then cloning from it. I never had the need again to link outside a git repo though...

$ mkdir tmp
$ cd tmp
$ git --version
git version 2.24.3 (Apple Git-128)
$ git init .
Initialized empty Git repository in /Users/teixeira/tmp/.git/
$ mkdir x
$ cd x
$ echo 123 > original
$ cat original
123
$ cd ..
$ ln -s x/original symlink
$ cat symlink
123
$ ln x/original hardlink
$ cat hardlink
123
$ git add .
$ git commit -m 'Symlink and hardlink commit'
[master (root-commit) 8df3134] Symlink and hardlink commit
 3 files changed, 3 insertions(+)
 create mode 100644 hardlink
 create mode 120000 symlink
 create mode 100644 x/original

Cloning from local git repository

$ cd
$ git clone tmp/ teste_tmp
Cloning into 'teste_tmp'...
done.
$ cd teste_tmp/
$ ls
hardlink  symlink  x
$ cat symlink
123
$ cat hardlink
123

Cloning from remote repository

$ cd ~/tmp
$ git remote add origin https://github.com/myUser/myRepo.git
$ git push origin master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (5/5), 361 bytes | 361.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0)
To https://github.com/myUser/myRepo.git
 + 964dfce...8df3134 master -> master
$ cd ../
$ git clone https://github.com/myUser/myRepo.git
Cloning into 'myRepo'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 5 (delta 0), reused 5 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), done.
$ cd myRepo/
$ cat symlink
123
$ cat hardlink
123

https://github.com/mokacoding/symlinks also points an important thing: symlinks must be defined relatively.

Niloct
  • 9,491
  • 3
  • 44
  • 57
  • 13
    So it looks like each time you add a hardlink to your repo you need to also manually add a line to your post-merge hook script. Would be nice if your pre-commit hook made this fully automatic--detecting hard (and symbolic) links in your commit and writing the appropriate lines to your post-merge file. Git wouldn't have to store inode info in the repo, it would store bits of it in the hooks! But what a mess if the linked file was tracked in another git repo... would an edit to the file in one place propagate to the other repo smoothly? Perpetual merges with a circular push/pull loop? – hobs Jun 09 '13 at 21:04
  • Clever approach, but it's unfortunate that Git doesn't track hard links, even potentially representing them as copies on file systems that don't support hard links. – Derek Mahar Nov 16 '17 at 18:35
  • 1
    @hobs sorry for taking 7 years to reply :) You're right. If a file outside the repository were linked by 2 different repos, I think changing a link in one repo just wouldn't be seen in the other one. – Niloct Oct 09 '20 at 18:37
8

From this msysgit issue

Junction points are not symbolic links; therefore, symbolic links are simply unsupported in msysGit.

Also, hard links were never tracked by Git.

The comment about hard links concerns Git in general. The specific issue was Microsoft Windows-oriented (since it was about msysgit), and the discussion debates the potential support of symbolic links.

Eponymous
  • 6,143
  • 4
  • 43
  • 43
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
3

Google 'git preserve hard links' and it shows that git does not know how to preserve hard link structure AFAIK, perhaps by design.

Web projects of mine use hard links as follows:

www/products/index.php
www/products/dell_latitude_id577/index.php #(hard linked to above)
www/products/dell_inspiron_id323/index.php #(hard linked again to above)

me@server:www/products$ ls -l index.php
-rwxr-xr-x 3 me me 1958 Aug 22 22:10 index.php*

If I wanted to make changes to index.php I change it in one place and the hard links (product detail pages) point to the changes -- except git does not preserve this relationship during cloning and pulling on other computers.

me@server:www$ git pull

on another machine will create a new index.php for each hard link.

xce_git
  • 31
  • 1
  • 8
    You should implement some sort of routing in your webapp. Hard linking is bizarre. – Nowaker Jul 02 '12 at 21:43
  • 5
    Yeah, at least use symlinks. :) – Andres Riofrio Jul 26 '12 at 02:44
  • 3
    This is actually what I want, I don't want git to preserve hard links. I have a Jenkins directory with tons of workspace directories, and because of multibranch pipelines, there is a lot of duplication. So I have a nightly job that runs `hardlink --ignore-time` on `/var/lib/jenkins`, to reclaim some disk space. During the day some files get unhardlinked again after `git pull` or `mvn compile` but that is ok, I expect that to happen. If git were to preserve hard links, then my disk space recycling strategy wouldn't work. – Amedee Van Gasse Mar 11 '19 at 10:28