Git Symlinks no Windows




(8)

Nossos desenvolvedores usam uma combinação de sistemas operacionais baseados no Windows e no Unix. Portanto, links simbólicos criados em máquinas Unix se tornam um problema para os desenvolvedores do Windows. No windows (msysgit), o link simbólico é convertido em um arquivo de texto com um caminho para o arquivo para o qual ele aponta. Em vez disso, gostaria de converter o link simbólico em um link simbólico atual do Windows.

A solução ( atualizada ) que tenho para isso é:

  • Escreva um script pós-checkout que procure recursivamente por arquivos de texto "symlink".
  • Substitua-os pelo link simbólico do Windows (usando o mklink) com o mesmo nome e extensão que o "symlink".
  • Ignore este symlink de janelas adicionando entrada em .git / info / exclude

Eu não implementei isso, mas acredito que essa seja uma abordagem sólida para esse problema.

Questões:

  1. Quais, se houver, desvantagens você vê para essa abordagem?
  2. Este script pós-pagamento é mesmo implementável? Por exemplo, posso recursivamente descobrir os arquivos "symlink" fictícios que git cria?
  3. Alguém já trabalhou em tal script?

A versão mais recente do git scm (testet 2.11.1) permite ativar links simbólicos. Mas você tem que clonar o repositório com os links simbólicos novamente git clone -c core.symlinks=true <URL> . Você precisa executar este comando com direitos de administrador. Também é possível criar links simbólicos no Windows com o mklink. Confira o wiki .

wiki


Aqui está um script em lote para converter links simbólicos no repositório, apenas para arquivos, com base na resposta de Josh Lee. Script com alguma verificação adicional para direitos de administrador está em https://gist.github.com/Quazistax/8daf09080bf54b4c7641 .

@echo off
pushd "%~dp0"
setlocal EnableDelayedExpansion

for /f "tokens=3,*" %%e in ('git ls-files -s ^| findstr /R /C:"^120000"') do (
     call :processFirstLine %%f
)
REM pause
goto :eof

:processFirstLine
@echo.
@echo FILE:    %1

dir "%~f1" | find "<SYMLINK>" >NUL && (
  @echo FILE already is a symlink
  goto :eof
)

for /f "usebackq tokens=*" %%l in ("%~f1") do (
  @echo LINK TO: %%l

  del "%~f1"
  if not !ERRORLEVEL! == 0 (
    @echo FAILED: del
    goto :eof
  )

  setlocal
  call :expandRelative linkto "%1" "%%l"
  mklink "%~f1" "!linkto!"
  endlocal
  if not !ERRORLEVEL! == 0 (
    @echo FAILED: mklink
    @echo reverting deletion...
    git checkout -- "%~f1"
    goto :eof
  )

  git update-index --assume-unchanged "%1"
  if not !ERRORLEVEL! == 0 (
    @echo FAILED: git update-index --assume-unchanged
    goto :eof
  )
  @echo SUCCESS
  goto :eof
)
goto :eof

:: param1 = result variable
:: param2 = reference path from which relative will be resolved
:: param3 = relative path
:expandRelative
  pushd .
  cd "%~dp2"
  set %1=%~f3
  popd
goto :eof

Ele deve ser implementado no msysgit, mas há duas desvantagens:

  • Os links simbólicos só estão disponíveis no Windows Vista e posteriores (não devem ser um problema em 2011, e ainda assim é ...), já que versões mais antigas suportam apenas junções de diretório.
  • (a grande) A Microsoft considera os links simbólicos um risco de segurança e, portanto, somente os administradores podem criá-los por padrão. Você precisará elevar os privilégios do processo do git ou usar o fstool para alterar esse comportamento em todas as máquinas em que você trabalha.

Fiz uma busca rápida e há trabalho sendo feito ativamente sobre isso, veja a questão 224 .


Eu estava fazendo exatamente a mesma pergunta há algum tempo (não aqui, apenas em geral) e acabei chegando a uma solução muito semelhante à proposta do OP. Primeiro, forneço respostas diretas para as perguntas 1 2 e 3 e depois postarei a solução que acabei usando.

  1. Há de fato algumas desvantagens na solução proposta, principalmente em relação a um potencial maior de poluição do repositório, ou acidentalmente adicionando arquivos duplicados enquanto eles estão em seus estados "links simbólicos do Windows". (Mais sobre isso em "limitações" abaixo.)
  2. Sim, um script pós-checkout é implementável! Talvez não como uma etapa de git checkout pós- git checkout literal, mas a solução abaixo atendeu às minhas necessidades bem o suficiente para que um script de pós-verificação literal não fosse necessário.
  3. Sim!

A solução:

Nossos desenvolvedores estão na mesma situação dos OPs: uma mistura de hosts Windows e Unix, repositórios e submodules com muitos symlinks git e nenhum suporte nativo (ainda) na versão de lançamento do MsysGit para manipulação inteligente desses symlinks em hosts Windows .

Obrigado a Josh Lee por apontar o fato de que o git confirma links simbólicos com o filemode especial 120000 . Com esta informação, é possível adicionar alguns aliases git que permitem a criação e manipulação de links simbólicos git em hosts do Windows.

  1. Criando links simbólicos para o git no Windows

    git config --global alias.add-symlink '!'"$(cat <<'ETX'
    __git_add_symlink() {
      if [ $# -ne 2 ] || [ "$1" = "-h" ]; then
        printf '%b\n' \
            'usage: git add-symlink <source_file_or_dir> <target_symlink>\n' \
            'Create a symlink in a git repository on a Windows host.\n' \
            'Note: source MUST be a path relative to the location of target'
        [ "$1" = "-h" ] && return 0 || return 2
      fi
    
      source_file_or_dir=${1#./}
      source_file_or_dir=${source_file_or_dir%/}
    
      target_symlink=${2#./}
      target_symlink=${target_symlink%/}
      target_symlink="${GIT_PREFIX}${target_symlink}"
      target_symlink=${target_symlink%/.}
      : "${target_symlink:=.}"
    
      if [ -d "$target_symlink" ]; then
        target_symlink="${target_symlink%/}/${source_file_or_dir##*/}"
      fi
    
      case "$target_symlink" in
        (*/*) target_dir=${target_symlink%/*} ;;
        (*) target_dir=$GIT_PREFIX ;;
      esac
    
      target_dir=$(cd "$target_dir" && pwd)
    
      if [ ! -e "${target_dir}/${source_file_or_dir}" ]; then
        printf 'error: git-add-symlink: %s: No such file or directory\n' \
            "${target_dir}/${source_file_or_dir}" >&2
        printf '(Source MUST be a path relative to the location of target!)\n' >&2
        return 2
      fi
    
      git update-index --add --cacheinfo 120000 \
          "$(printf '%s' "$source_file_or_dir" | git hash-object -w --stdin)" \
          "${target_symlink}" \
        && git checkout -- "$target_symlink" \
        && printf '%s -> %s\n' "${target_symlink#$GIT_PREFIX}" "$source_file_or_dir" \
        || return $?
    }
    __git_add_symlink
    ETX
    )"
    

    Uso: git add-symlink <source_file_or_dir> <target_symlink> , onde o argumento correspondente ao arquivo ou diretório de origem deve assumir a forma de um caminho relativo ao symlink de destino. Você pode usar esse alias da mesma maneira que usaria normalmente o ln .

    Por exemplo, a árvore do repositório:

    dir/
    dir/foo/
    dir/foo/bar/
    dir/foo/bar/baz      (file containing "I am baz")
    dir/foo/bar/lnk_file (symlink to ../../../file)
    file                 (file containing "I am file")
    lnk_bar              (symlink to dir/foo/bar/)
    

    Pode ser criado no Windows da seguinte maneira:

    git init
    mkdir -p dir/foo/bar/
    echo "I am baz" > dir/foo/bar/baz
    echo "I am file" > file
    git add -A
    git commit -m "Add files"
    git add-symlink ../../../file dir/foo/bar/lnk_file
    git add-symlink dir/foo/bar/ lnk_bar
    git commit -m "Add symlinks"
    
  2. Substituindo links simbólicos do git por hardlinks NTFS + junções

    git config --global alias.rm-symlinks '!'"$(cat <<'ETX'
    __git_rm_symlinks() {
      case "$1" in (-h)
        printf 'usage: git rm-symlinks [symlink] [symlink] [...]\n'
        return 0
      esac
      ppid=$$
      case $# in
        (0) git ls-files -s | grep -E '^120000' | cut -f2 ;;
        (*) printf '%s\n' "[email protected]" ;;
      esac | while IFS= read -r symlink; do
        case "$symlink" in
          (*/*) symdir=${symlink%/*} ;;
          (*) symdir=. ;;
        esac
    
        git checkout -- "$symlink"
        src="${symdir}/$(cat "$symlink")"
    
        posix_to_dos_sed='s_^/\([A-Za-z]\)_\1:_;s_/_\\\\_g'
        doslnk=$(printf '%s\n' "$symlink" | sed "$posix_to_dos_sed")
        dossrc=$(printf '%s\n' "$src" | sed "$posix_to_dos_sed")
    
        if [ -f "$src" ]; then
          rm -f "$symlink"
          cmd //C mklink //H "$doslnk" "$dossrc"
        elif [ -d "$src" ]; then
          rm -f "$symlink"
          cmd //C mklink //J "$doslnk" "$dossrc"
        else
          printf 'error: git-rm-symlink: Not a valid source\n' >&2
          printf '%s =/=> %s  (%s =/=> %s)...\n' \
              "$symlink" "$src" "$doslnk" "$dossrc" >&2
          false
        fi || printf 'ESC[%d]: %d\n' "$ppid" "$?"
    
        git update-index --assume-unchanged "$symlink"
      done | awk '
        BEGIN { status_code = 0 }
        /^ESC\['"$ppid"'\]: / { status_code = $2 ; next }
        { print }
        END { exit status_code }
      '
    }
    __git_rm_symlinks
    ETX
    )"
    
    git config --global alias.rm-symlink '!git rm-symlinks'  # for back-compat.
    

    Uso:

    git rm-symlinks [symlink] [symlink] [...]
    

    Este alias pode remover links simbólicos git um por um ou todos de uma vez em uma só penada. Os links simbólicos serão substituídos por hardlinks NTFS (no caso de arquivos) ou junções NTFS (no caso de diretórios). O benefício de usar hardlinks + junções sobre links simbólicos NTFS "verdadeiros" é que as permissões elevadas do UAC não são necessárias para que sejam criadas.

    Para remover links simbólicos de submódulos, apenas use o suporte embutido do git para iterar sobre eles:

    git submodule foreach --recursive git rm-symlinks
    

    Mas, para cada ação drástica como essa, é bom ter uma reversão ...

  3. Restaurando links simbólicos do git no Windows

    git config --global alias.checkout-symlinks '!'"$(cat <<'ETX'
    __git_checkout_symlinks() {
      case "$1" in (-h)
        printf 'usage: git checkout-symlinks [symlink] [symlink] [...]\n'
        return 0
      esac
      case $# in
        (0) git ls-files -s | grep -E '^120000' | cut -f2 ;;
        (*) printf '%s\n' "[email protected]" ;;
      esac | while IFS= read -r symlink; do
        git update-index --no-assume-unchanged "$symlink"
        rmdir "$symlink" >/dev/null 2>&1
        git checkout -- "$symlink"
        printf 'Restored git symlink: %s -> %s\n' "$symlink" "$(cat "$symlink")"
      done
    }
    __git_checkout_symlinks
    ETX
    )"
    
    git config --global alias.co-symlinks '!git checkout-symlinks'
    

    Uso: git checkout-symlinks [symlink] [symlink] [...] , que desfaz git rm-symlinks , efetivamente restaurando o repositório ao seu estado natural (exceto por suas alterações, que devem permanecer intactas).

    E para submódulos:

    git submodule foreach --recursive git checkout-symlinks
    
  4. Limitações:

    • Diretórios / arquivos / links simbólicos com espaços em seus caminhos devem funcionar. Mas guias ou novas linhas? YMMV… (Por isso quero dizer: não faça isso, porque não vai funcionar.)

    • Se você ou outras pessoas esquecerem de clicar em git checkout-symlinks antes de fazer algo com conseqüências potencialmente abrangentes, como git add -A , o repositório local pode acabar em um estado poluído.

      Usando nosso "exemplo de recompra" de antes:

      echo "I am nuthafile" > dir/foo/bar/nuthafile
      echo "Updating file" >> file
      git add -A
      git status
      # On branch master
      # Changes to be committed:
      #   (use "git reset HEAD <file>..." to unstage)
      #
      #       new file:   dir/foo/bar/nuthafile
      #       modified:   file
      #       deleted:    lnk_bar           # POLLUTION
      #       new file:   lnk_bar/baz       # POLLUTION
      #       new file:   lnk_bar/lnk_file  # POLLUTION
      #       new file:   lnk_bar/nuthafile # POLLUTION
      #
      

      Opa ...

      Por esse motivo, é bom incluir esses aliases como etapas a serem executadas para os usuários do Windows antes e depois da criação de um projeto, em vez de depois da finalização da compra ou antes do envio. Mas cada situação é diferente. Esses aliases têm sido úteis o suficiente para mim que uma verdadeira solução pós-checkout não foi necessária.

Espero que ajude!

Referências:

http://git-scm.com/book/en/Git-Internals-Git-Objects

http://technet.microsoft.com/en-us/library/cc753194

Última atualização: 2019-03-13

  • Conformidade POSIX (bem, exceto para as chamadas mklink , é claro) - não mais Bashisms !
  • Diretórios e arquivos com espaços neles são suportados.
  • Códigos de status de saída zero e diferente de zero (para comunicação de sucesso / falha do comando solicitado, respectivamente) agora são preservados / retornados adequadamente.
  • O alias add-symlink agora funciona mais como ln(1) e pode ser usado em qualquer diretório no repositório, não apenas no diretório-raiz do repositório.
  • O apelido rm-symlink (singular) foi substituído pelo alias rm-symlinks (plural), que agora aceita vários argumentos (ou nenhum argumento, que encontra todos os links simbólicos em todo o repositório, como antes) para transformar seletivamente o git links simbólicos para hardlinks NTFS + junções.
  • O alias checkout-symlinks também foi atualizado para aceitar vários argumentos (ou nenhum, == tudo) para reversão seletiva das transformações mencionadas anteriormente.

Nota Final: Embora eu tenha testado o carregamento e a execução desses aliases usando o Bash 3.2 (e até 3.1) para aqueles que ainda podem estar presos em versões antigas por qualquer número de razões, esteja ciente de que versões antigas são notórias para o analisador. insetos. Se você tiver problemas ao tentar instalar qualquer um desses aliases, a primeira coisa que você deve verificar é atualizar seu shell (para o Bash, verifique a versão com CTRL + X, CTRL + V). Como alternativa, se você estiver tentando instalá-los, colando-os no emulador de terminal, poderá ter mais sorte em colá-los em um arquivo e, em vez disso, fazer o sourcing, por exemplo,

. ./git-win-symlinks.sh

Boa sorte!


Eu sugiro que você não use links simbólicos no repo '. Armazene o conteúdo real dentro do repositório e coloque links simbólicos ao lado do repositório que aponta para o conteúdo.

Então, digamos que você está usando um repo 'para comparar a hospedagem do seu site no * nix com a hospedagem na vitória. Armazene o conteúdo em seu repo ', digamos /httpRepoContent e c:\httpRepoContent com esta sendo a pasta que é sincronizada via GIT, SVN etc.

Em seguida, substitua a pasta de conteúdo do seu servidor web ( /var/www e c:\program files\web server\www {nomes realmente não importam, edite se for necessário}) com um link simbólico para o conteúdo em seu repositório '. Os servidores da Web verão o conteúdo como realmente no lugar "certo", mas você poderá usar o controle de origem.

No entanto, se você precisar usar links simbólicos no repositório, precisará procurar algo como algum tipo de script de pré / pós-commit. Eu sei que você pode usá-los para fazer coisas, como os arquivos de código de análise através de um formatador, por exemplo, por isso deve ser possível converter os links simbólicos entre as plataformas.

Se alguém conhece um bom lugar para aprender como fazer estes scripts para os controles de fonte comuns, então por favor adicione um comentário.


Eu uso links sym o tempo todo entre meu diretório de documentos e o diretório git repo. Eu gosto de mantê-los separados. No windows eu uso a opção mklink / j. A junção parece deixar o git se comportar normalmente:

>mklink /j <location(path) of link> <source of link>

por exemplo:

>mklink /jc:\gitRepos\Posts C:\Bitnami\wamp\apache2\htdocs\Posts


Resposta curta: Agora eles são bem compatíveis, se você puder ativar o modo de desenvolvedor.

De https://blogs.windows.com/buildingapps/2016/12/02/symlinks-windows-10/

Agora, no Windows 10 Creators Update, um usuário (com direitos de administrador) pode ativar primeiro o Modo de Desenvolvedor e qualquer usuário na máquina pode executar o comando mklink sem elevar um console de linha de comando.

O que levou a essa mudança? A disponibilidade e o uso de links simbólicos é um grande negócio para os desenvolvedores modernos:

Muitas ferramentas de desenvolvimento populares como git e gerenciadores de pacotes, como o npm, reconhecem e persistem links simbólicos ao criar repos ou pacotes, respectivamente. Quando esses repos ou pacotes são restaurados em outro lugar, os links simbólicos também são restaurados, garantindo que o espaço em disco (e o tempo do usuário) não seja desperdiçado.

Fácil de ignorar com todos os outros anúncios da "Atualização do Criador", mas se você ativar o Modo de Desenvolvedor, poderá criar links simbólicos sem privilégios elevados. Pode ser necessário reinstalar e garantir que o suporte esteja ativado, pois não é por padrão.


Um truque simples que usamos é apenas chamar git add --all duas vezes seguidas.

Por exemplo, nosso script de confirmação do Windows 7 chama:

$ git add --all
$ git add --all

O primeiro add trata o link como texto e adiciona as pastas para exclusão.

O segundo add percorre o link corretamente e desfaz a exclusão restaurando os arquivos.

É menos elegante do que algumas das outras soluções propostas, mas é uma solução simples para alguns de nossos ambientes legados que receberam links simbólicos.





symlink