如何将普通的 Git 存储库转换为裸存储库?

发布于 2024-08-20 10:02:34 字数 169 浏览 10 评论 0原文

如何将“普通”Git 存储库转换为裸存储库?

主要区别似乎是:

  • 包含所有相关数据和构成工作副本的所有其他文件

  • 在裸 Git 存储库中,没有工作副本,并且文件夹(我们称之为 repo.git)包含

How can I convert a 'normal' Git repository to a bare one?

The main difference seems to be:

  • in the normal Git repository, you have a .git folder inside the repository containing all relevant data and all other files making up your working copy

  • in a bare Git repository, there is no working copy and the folder (let's call it repo.git) contains the actual repository data

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(19

半枫 2024-08-27 10:02:34

简而言之:将 repo 的内容替换为 repo/.git 的内容,然后告诉存储库它现在是一个裸存储库。

为此,请执行以下命令:

cd repo
mv .git ../repo.git # renaming just for clarity
cd ..
rm -fr repo
cd repo.git
git config --bool core.bare true

请注意,这与将 git clone --bare /path/to/repo 执行到新位置(如此处所述)。

考虑这种情况

  • 您的源有 100 个分支
    • 您仅在本地签出了其中 10 个
    • 您的裸存储库将只有 10
  • 您将裸存储库发送到某处
    • 它将缺少 90 个分支

如果这是您的意图,那很好。如果您需要远程/源的镜像,这不是方法。

In short: replace the contents of repo with the contents of repo/.git, then tell the repository that it is now a bare repository.

To do this, execute the following commands:

cd repo
mv .git ../repo.git # renaming just for clarity
cd ..
rm -fr repo
cd repo.git
git config --bool core.bare true

Note that this is different from doing a git clone --bare /path/to/repo to a new location (as described here).

Consider this scenario

  • Your origin had 100 branches
    • You have only checked out 10 of them locally
    • Your bare repo will only have the 10
  • You send the bare repo somewhere
    • It would be missing 90 branches

If that's your intention, that's fine. If you needed a mirror of the remote/origin, this is not the way.

淤浪 2024-08-27 10:02:34

你的方法看起来可行;裸存储库的文件结构就是 .git 目录中的内容。但我不知道是否有任何文件实际上被更改,所以如果失败,您可以这样做

git clone --bare /path/to/repo

您可能需要在不同的目录中执行它以避免名称冲突,然后您可以将其移回到你想要的地方。您可能需要更改配置文件以指向您的原始存储库所在的位置。

Your method looks like it would work; the file structure of a bare repository is just what is inside the .git directory. But I don't know if any of the files are actually changed, so if that fails, you can just do

git clone --bare /path/to/repo

You'll probably need to do it in a different directory to avoid a name conflict, and then you can just move it back to where you want. And you may need to change the config file to point to wherever your origin repo is.

风吹雨成花 2024-08-27 10:02:34

我认为以下链接会有所帮助

GitFaq:如何使现有非裸存储库裸?

$ mv repo/.git repo.git
$ git --git-dir=repo.git config core.bare true
$ rm -rf repo

I think the following link would be helpful

GitFaq: How do I make existing non-bare repository bare?

$ mv repo/.git repo.git
$ git --git-dir=repo.git config core.bare true
$ rm -rf repo
末が日狂欢 2024-08-27 10:02:34

除非您特别想要或需要在文件系统上进行调整,否则创建非裸存储库的裸版本确实非常简单(在其他几篇文章中提到过)。它是 git 核心功能的一部分:

git clone --bare existing_repo_path bare_repo_path

Unless you specifically want or need to twiddle bits on the filesystem, it really is dead simple to create a bare version of a non-bare repository (mentioned in several other posts here). It’s part of git’s core functionality:

git clone --bare existing_repo_path bare_repo_path

清风不识月 2024-08-27 10:02:34

另请考虑使用

git clone --mirror path_to_source_repository path_to_bare_repository

文档

设置源存储库的镜像。这意味着--裸露。与 --bare 相比,--mirror 不仅将源的本地分支映射到目标的本地分支,它还映射所有引用(包括远程跟踪分支、注释等)并设置 refspec 配置,以便所有这些引用被目标存储库中的 git 远程更新覆盖。

Please also consider to use

git clone --mirror path_to_source_repository path_to_bare_repository

From the documentation:

Set up a mirror of the source repository. This implies --bare. Compared to --bare, --mirror not only maps local branches of the source to local branches of the target, it maps all refs (including remote-tracking branches, notes etc.) and sets up a refspec configuration such that all these refs are overwritten by a git remote update in the target repository.

梦晓ヶ微光ヅ倾城 2024-08-27 10:02:34

我只是想推送到网络路径上的存储库,但 git 不会让我这样做,除非该存储库被标记为裸。
我所需要的只是改变它的配置:

git config --bool core.bare true

不需要摆弄文件,除非你想保持它干净。

I just wanted to push to a repository on a network path but git would not let me do that unless that repository was marked as bare.
All I needed was to change its config:

git config --bool core.bare true

No need to fiddle with the files unless you want to keep it clean.

紅太極 2024-08-27 10:02:34

我已经阅读了答案,并且已经这样做了:

cd repos
mv .git repos.git
cd repos.git
git config --bool core.bare true # from another answer
cd ../
mv repos.git ../
cd ../
rm -rf repos/ # or delete using a file manager if you like

这会将 repos/.git 的内容保留为裸露的 repos.git

i've read the answers and i have done this:

cd repos
mv .git repos.git
cd repos.git
git config --bool core.bare true # from another answer
cd ../
mv repos.git ../
cd ../
rm -rf repos/ # or delete using a file manager if you like

this will leave the contents of repos/.git as the bare repos.git

心房敞 2024-08-27 10:02:34

这是我认为最安全和最简单的。这里没有什么是上面没有提到的。我只是想看到一个显示安全分步过程的答案。您可以从要裸露的存储库 (repo) 开始一个文件夹。我采用了上面暗示的约定,即裸存储库文件夹具有 .git 扩展名。

(1) Backup, just in case.
    (a) > mkdir backup
    (b) > cd backup
    (c) > git clone ../repo
(2) Make it bare, then move it
    (a) > cd ../repo
    (b) > git config --bool core.bare true
    (c) > mv .git ../repo.git
(3) Confirm the bare repository works (optional, since we have a backup)
    (a) > cd ..
    (b) > mkdir test
    (c) > cd test
    (d) > git clone ../repo.git
(4) Clean up
    (a) > rm -Rf repo
    (b) (optional) > rm -Rf backup/repo
    (c) (optional) > rm -Rf test/repo

Here's what I think is safest and simplest. There is nothing here not stated above. I just want to see an answer that shows a safe step-by-step procedure. You start one folder up from the repository (repo) you want to make bare. I've adopted the convention implied above that bare repository folders have a .git extension.

(1) Backup, just in case.
    (a) > mkdir backup
    (b) > cd backup
    (c) > git clone ../repo
(2) Make it bare, then move it
    (a) > cd ../repo
    (b) > git config --bool core.bare true
    (c) > mv .git ../repo.git
(3) Confirm the bare repository works (optional, since we have a backup)
    (a) > cd ..
    (b) > mkdir test
    (c) > cd test
    (d) > git clone ../repo.git
(4) Clean up
    (a) > rm -Rf repo
    (b) (optional) > rm -Rf backup/repo
    (c) (optional) > rm -Rf test/repo
归途 2024-08-27 10:02:34

只需阅读

Pro Git Book: 4.2 Git on the Server - Getting Git on a Server

归结为 然后

$ git clone --bare my_project my_project.git
Cloning into bare repository 'my_project.git'...
done.

my_project.git 到服务器

主要是,答案是什么 #42试图指出。确实,人们可以重新发明轮子;-)

Simply read

Pro Git Book: 4.2 Git on the Server - Getting Git on a Server

which boild down to

$ git clone --bare my_project my_project.git
Cloning into bare repository 'my_project.git'...
done.

Then put my_project.git to the server

Which mainly is, what answer #42 tried to point out. Shurely one could reinvent the wheel ;-)

浅浅淡淡 2024-08-27 10:02:34

这是一个小 BASH 函数,您可以将其添加到基于 UNIX 的系统上的 .bashrc 或 .profile 中。添加后,shell 将重新启动,或者通过调用 source ~/.profilesource ~/.bashrc 重新加载文件。

function gitToBare() {
  if [ -d ".git" ]; then
    DIR="`pwd`"
    mv .git ..
    rm -fr *
    mv ../.git .
    mv .git/* .
    rmdir .git

    git config --bool core.bare true
    cd ..
    mv "${DIR}" "${DIR}.git"

    printf "[\x1b[32mSUCCESS\x1b[0m] Git repository converted to "
    printf "bare and renamed to\n  ${DIR}.git\n"
    cd "${DIR}.git"
  else
    printf "[\x1b[31mFAILURE\x1b[0m] Cannot find a .git directory\n"
  fi
}

一旦在包含 .git 目录的目录中调用,它将进行适当的更改以转换存储库。如果调用时不存在 .git 目录,则会出现 FAILURE 消息,并且不会发生文件系统更改。

Here is a little BASH function you can add to your .bashrc or .profile on a UNIX based system. Once added and the shell is either restarted or the file is reloaded via a call to source ~/.profile or source ~/.bashrc.

function gitToBare() {
  if [ -d ".git" ]; then
    DIR="`pwd`"
    mv .git ..
    rm -fr *
    mv ../.git .
    mv .git/* .
    rmdir .git

    git config --bool core.bare true
    cd ..
    mv "${DIR}" "${DIR}.git"

    printf "[\x1b[32mSUCCESS\x1b[0m] Git repository converted to "
    printf "bare and renamed to\n  ${DIR}.git\n"
    cd "${DIR}.git"
  else
    printf "[\x1b[31mFAILURE\x1b[0m] Cannot find a .git directory\n"
  fi
}

Once called within a directory containing a .git directory, it will make the appropriate changes to convert the repository. If there is no .git directory present when called, a FAILURE message will appear and no file system changes will happen.

当梦初醒 2024-08-27 10:02:34

那些说删除文件并移动 .git 目录的方法并不干净,并且没有使用“git”方法来做一些应该很简单的事情。这是我发现的将普通存储库转换为裸存储库的最干净的方法。

首先将 /path/to/normal/repo 克隆到名为 repo.git 的裸存储库中,

git clone --bare /path/to/normal/repo

然后删除指向 /path/to/normal/repo 的源,

cd repo.git
git remote rm origin

最后您可以删除原始存储库。此时您可以将 repo.git 重命名为 repo,但表示 git 存储库的标准约定是 some.git,所以我个人会保留这种方式。

完成所有这些后,您可以克隆新的裸存储库(这实际上创建了一个普通存储库,也是您将其从裸存储转换为普通存储库的方式)

当然,如果您有其他上游,您会想要记下它们,并更新您的裸存储库以包含它。但同样,这一切都可以通过 git 命令完成。请记住,手册页是您的朋友。

The methods that say to remove files and muck about with moving the .git directory are not clean and not using the "git" method of doing something that's should be simple. This is the cleanest method I have found to convert a normal repo into a bare repo.

First clone /path/to/normal/repo into a bare repo called repo.git

git clone --bare /path/to/normal/repo

Next remove the origin that points to /path/to/normal/repo

cd repo.git
git remote rm origin

Finally you can remove your original repo. You could rename repo.git to repo at that point, but the standard convention to signify a git repository is something.git, so I'd personally leave it that way.

Once you've done all that, you can clone your new bare repo (which in effect creates a normal repo, and is also how you would convert it from bare to normal)

Of course if you have other upstreams, you'll want to make a note of them, and update your bare repo to include it. But again, it can all be done with the git command. Remember the man pages are your friend.

爱的那么颓废 2024-08-27 10:02:34

如果您的存储库只有很少的本地签出分支 /refs/heads/* 和很少的远程分支分支 remotes/origin/* 并且如果您想将其转换为 BARE 存储库,所有分支都在 /refs/heads/* 中,

您可以执行以下操作来保存历史记录。

  1. 创建一个裸存储库
  2. cd 到本地存储库,该存储库具有本地签出分支和远程分支
  3. git push /path/to/bare/repo +refs/remotes/origin/:refs/heads/

In case you have a repository with few local checkedout branches /refs/heads/* and few remote branch branches remotes/origin/* AND if you want to convert this into a BARE repository with all branches in /refs/heads/*

you can do the following to save the history.

  1. create a bare repository
  2. cd into the local repository which has local checkedout branches and remote branches
  3. git push /path/to/bare/repo +refs/remotes/origin/:refs/heads/
榆西 2024-08-27 10:02:34

以下是来自 gitglossary裸存储库的定义>:

裸存储库通常是一个适当命名的目录,带有 .git 后缀,没有受修订控制的任何文件的本地检出副本。也就是说,通常存在于隐藏的 .git 子目录中的所有 Git 管理和控制文件都直接存在于repository.git 目录中,并且没有其他文件存在和签出。通常公共存储库的发布者会提供裸存储库。

我来到这里是因为我正在使用“本地存储库”,并且希望能够做任何我想做的事情,就好像它是远程存储库一样。我只是玩玩,试图了解 git。我假设这就是任何想阅读此答案的人的情况。

我很想得到专家的意见或一些具体的反例,但是似乎(在翻阅我找到的一些 git 源代码之后)只需转到文件 .git/config 并设置 <将strong>core属性bare设置为true,git将允许你远程对存储库做任何你想做的事情。即以下几行应该存在于 .git/config 中:(

[core]
    ...
    bare = true
...

这大致是命令 git config --bool core.bare true 的作用,这可能是推荐的处理更复杂的情况)

我对这种说法的理由是,在 git 源代码中,似乎有两种不同的方法来测试存储库是否是裸露的。一种是检查全局变量 is_bare_repository_cfg。这是在执行的某些设置阶段设置的,并反映在 .git/config 文件中找到的值。另一个是函数is_bare_repository()。以下是该函数的定义:

int is_bare_repository(void)
{
    /* if core.bare is not 'false', let's see if there is a work tree */
    return is_bare_repository_cfg && !get_git_work_tree();
} 

我没有时间也没有专业知识来绝对自信地说出这一点,但据我所知,您是否将 bare 属性设置为 true< /code> 在 .git/config 中,这应该始终返回 1。函数的其余部分可能是针对以下情况:

  1. core.bare 未定义(即既不是 true 也不是 false)
  2. 没有工作树(即 .git 子目录是主目录)

稍后我会尝试一下,但这似乎表明设置 core.bare = true 相当于从配置文件中删除 core.bare 并正确设置目录。

无论如何,设置 core.bare = true 肯定会让你推动它,但我不确定项目文件的存在是否会导致其他一些操作出错。这很有趣,我认为推送到存储库并查看本地发生的情况(即运行 git status 并理解结果)很有启发性。

Here is the definition of a bare repository from gitglossary:

A bare repository is normally an appropriately named directory with a .git suffix that does not have a locally checked-out copy of any of the files under revision control. That is, all of the Git administrative and control files that would normally be present in the hidden .git sub-directory are directly present in the repository.git directory instead, and no other files are present and checked out. Usually publishers of public repositories make bare repositories available.

I arrived here because I was playing around with a "local repository" and wanted to be able to do whatever I wanted as if it were a remote repository. I was just playing around, trying to learn about git. I'll assume that this is the situation for whoever wants to read this answer.

I would love for an expert opinion or some specific counter-examples, however it seems that (after rummaging through some git source code that I found) simply going to the file .git/config and setting the core attribute bare to true, git will let you do whatever you want to do to the repository remotely. I.e. the following lines should exist in .git/config:

[core]
    ...
    bare = true
...

(This is roughly what the command git config --bool core.bare true will do, which is probably recommended to deal with more complicated situations)

My justification for this claim is that, in the git source code, there seems to be two different ways of testing if a repo is bare or not. One is by checking a global variable is_bare_repository_cfg. This is set during some setup phase of execution, and reflects the value found in the .git/config file. The other is a function is_bare_repository(). Here is the definition of this function:

int is_bare_repository(void)
{
    /* if core.bare is not 'false', let's see if there is a work tree */
    return is_bare_repository_cfg && !get_git_work_tree();
} 

I've not the time nor expertise to say this with absolute confidence, but as far as I could tell if you have the bare attribute set to true in .git/config, this should always return 1. The rest of the function probably is for the following situation:

  1. core.bare is undefined (i.e. neither true nor false)
  2. There is no worktree (i.e. the .git subdirectory is the main directory)

I'll experiment with it when I can later, but this would seem to indicate that setting core.bare = true is equivalent to removeing core.bare from the config file and setting up the directories properly.

At any rate, setting core.bare = true certainly will let you push to it, but I'm not sure if the presence of project files will cause some other operations to go awry. It's interesting and I suppose instructive to push to the repository and see what happened locally (i.e. run git status and make sense of the results).

怪我闹别瞎闹 2024-08-27 10:02:34

我使用以下脚本读取包含所有 SVN 存储库列表的文本文件并将其转换为 GIT,然后使用 git clone --bare 转换为裸 git 存储库

#!/bin/bash
file="list.txt"
while IFS= read -r repo_name
do
 printf '%s\n' "$repo_name"
 sudo git svn clone --shared --preserve-empty-dirs --authors-file=users.txt file:///programs/svn/$repo_name 
 sudo git clone --bare /programs/git/$repo_name $repo_name.git
 sudo chown -R www-data:www-data $repo_name.git
 sudo rm -rf $repo_name
done <"$file"

list.txt 具有格式

repo1_name
repo2_name

和 users.txt有格式

(无作者)= Prince Rogers [电子邮件受保护]< /a>>


www-data 是 Apache Web 服务器用户,需要权限才能通过 HTTP 推送更改

I used the following script to read a text file that has a list of all my SVN repos and convert them to GIT, and later use git clone --bare to convert to a bare git repo

#!/bin/bash
file="list.txt"
while IFS= read -r repo_name
do
 printf '%s\n' "$repo_name"
 sudo git svn clone --shared --preserve-empty-dirs --authors-file=users.txt file:///programs/svn/$repo_name 
 sudo git clone --bare /programs/git/$repo_name $repo_name.git
 sudo chown -R www-data:www-data $repo_name.git
 sudo rm -rf $repo_name
done <"$file"

list.txt has the format

repo1_name
repo2_name

and users.txt has the format

(no author) = Prince Rogers <[email protected]>

www-data is the Apache web server user, permission is needed to push changes over HTTP

何以畏孤独 2024-08-27 10:02:34

大多数解决方案只是删除主工作树
但这也会删除

此处未跟踪的文件,我们将未跟踪的文件移动到单独的目录中,

最复杂的问题来自于
rsync 在同一文件系统上移动文件

#!/usr/bin/env bash

# git-repo-to-bare-repo.sh

# example use:
#   git-repo-to-bare-repo.sh repo/
# this will move repo/.git/ to repo.git/
# and move untracked files to repo.untracked.*/

# https://stackoverflow.com/questions/2199897
# How to convert a normal Git repository to a bare one?

# most solutions simply delete the main worktree
# but that also deletes untracked files
# here, we move untracked files to a separate directory

set -e
set -u
#set -x # debug

main_worktree_path="$1"

main_worktree_path="$(readlink -f "$main_worktree_path")"

workdir_path"$(readlink -f .)"

if [ "$main_worktree_path" = "$workdir_path" ]; then
  echo "error: main_worktree_path cannot be the current workdir, because it will be removed"
  exit 1
fi

cd "$main_worktree_path"

if ! [ -d .git ]; then
  echo "error: missing .git/"
  exit 1
fi

version=$(date --utc +%Y%m%dT%H%M%SZ).$(mktemp -u XXXXXXXX)

basename="${PWD##*/}"

# move .git to this path
git_path="../$basename.git"

# move untracked files to this directory
tmp_path="../$basename.untracked.$version"

if [ -e "$git_path" ]; then echo "error: path exists: ${git_path@Q}"; exit 1; fi
if [ -e "$tmp_path" ]; then echo "error: path exists: ${tmp_path@Q}"; exit 1; fi

# no. rsync does not move files
# remove-source-files means copy and delete files, which is slow/wasteful
if false; then
args=(
  rsync
    #--verbose # debug
    --archive
    --recursive # not implied by --archive when --files-from is used
    --remove-source-files # copy and delete files
    --from0
    .
    "$tmp_path"
)
"${args[@]}" \
    --files-from=<(
      git ls-files -dmoz --directory
    )
fi

echo "checking for untracked files"

# actually "move" untracked files
# git ls-files -dmoz | xargs -r -0 mv -v -t "$tmp_path" # wrong: mv does not preserve path
has_untracked=0
while IFS= read -r -d '' src; do
  if [ $has_untracked = 0 ]; then
    echo "moving untracked files to $tmp_path"
    mkdir -p "$tmp_path"
    has_untracked=1
  fi
  stack=("$src")
  dirstack=()
  while [ ${#stack[@]} != 0 ]; do
    echo "stack ${stack@Q}"
    src="${stack[0]}"; stack=("${stack[@]:1}") # shift src from stack
    #[ -z "$src" ] && continue
    echo "src ${src@Q}"
    isdir=0
    if [ "${src: -1}" = "/" ]; then # git shows directories with "/" suffix
      src="${src:0: -1}"
      isdir=1
    elif [ -d "$src" ]; then
      isdir=1
    fi
    srcdir="${src%/*}"
    dst="$tmp_path/$src"
    dstdir="${dst%/*}"
    #echo "dstdir ${dstdir@Q}"
    # mkdir -p "$(dirname "$dst")" # more generic
    if [ -e "$dstdir" ] && ! [ -d "$dstdir" ]; then
      dstdir2="$dstdir.$version"
      echo "note: file exists: ${dstdir@Q}. moving ${src@Q} to ${dstdir2@Q}"
      dstdir="$dstdir2"
      dst="$dstdir2/${src##*/}"
    fi
    if [ -e "$dst" ]; then # dst path exists
      if [ $isdir = 0 ]; then # src path is file
        if [ -d "$dst" ]; then # dst path is dir
          dst2="$dst.$version"
          echo "note: dir exists: ${dst@Q}. moving ${src@Q} to ${dst2@Q}"
          dst="$dst2"
        else # dst path is file
          # compare files: permissions, size, content
          if
            [ $(stat -c%a_%s "$src") = $(stat -c%a_%s "$dst") ] &&
            [ $(sha256sum "$src" | head -c64) = $(sha256sum "$dst" | head -c64) ]
          then
            echo "note: same file exists: ${dst@Q}. deleting ${src@Q}"
            rm "$src"
            continue
          else
            dst2="$dst.$version"
            echo "note: different file exists: ${dst@Q}. moving ${src@Q} to ${dst2@Q}"
            dst="$dst2"
          fi
        fi
      else # src path is dir
        if ! [ -d "$dst" ]; then # dst path is file
          dst2="$dst.$version"
          echo "note: file exists: ${dst@Q}. moving ${src@Q} to ${dst2@Q}"
          dst="$dst2"
        fi
        # dst path is dir
        echo "note: dir exists: ${dst@Q}. moving contents of ${src@Q}"
        # recurse: move contents of src dir
        while IFS= read -r -d '' src2; do
          if [ -d "$src2" ]; then
            stack+=("$src2/")
          else
            stack+=("$src2")
          fi
        done < <(
          find "$src" -mindepth 1 -maxdepth 1 -print0
        )
        #echo "dirstack+=(${src@Q})"
        dirstack+=("$src") # later: remove empty src dir
        #echo "dirstack ${dirstack@Q}"
        continue
      fi
      dstdir2="$dstdir.$version"
      echo "error: file exists: ${dstdir@Q}. moving to ${dstdir2@Q}"
      dstdir="$dstdir2"
      dst="$dstdir2/${src##*/}"
    fi
    mkdir -p "$dstdir"
    echo mv "${src@Q}" "${dst@Q}"
    mv "$src" "$dst"
    # no. still walking down here
    #rmdir --ignore-fail-on-non-empty "$srcdir" # remove srcdir if empty
  done
  # walk up
  # todo? try this more often to keep dirstack small
  while [ ${#dirstack[@]} != 0 ]; do
    #echo "dirstack ${dirstack@Q}"
    srcdir="${dirstack[-1]}"; dirstack=("${dirstack[@]:0:$((${#dirstack[@]} - 1))}") # pop srcdir from stack
    #echo "srcdir ${srcdir@Q}"
    rmdir --ignore-fail-on-non-empty "$srcdir" # remove srcdir if empty
  done
done < <(
  git ls-files -dmoz --directory
)

if [ $has_untracked = 0 ]; then
  echo "ok. found no untracked files"
else
  echo "ok. done moving untracked files"
fi



echo "moving .git/ to ${git_path@Q}"
mv .git "$git_path"

echo "setting git config core.bare=true in ${git_path@Q}"
git -C "$git_path" config --bool core.bare true

echo "removing main worktree ${main_worktree_path@Q}"
#cd "$workdir_path"
cd ..
rm -rf "$main_worktree_path"

most solutions simply delete the main worktree
but that also deletes untracked files

here, we move untracked files to a separate directory

most complexity comes from the workaround for
rsync move files on the same filesystem

#!/usr/bin/env bash

# git-repo-to-bare-repo.sh

# example use:
#   git-repo-to-bare-repo.sh repo/
# this will move repo/.git/ to repo.git/
# and move untracked files to repo.untracked.*/

# https://stackoverflow.com/questions/2199897
# How to convert a normal Git repository to a bare one?

# most solutions simply delete the main worktree
# but that also deletes untracked files
# here, we move untracked files to a separate directory

set -e
set -u
#set -x # debug

main_worktree_path="$1"

main_worktree_path="$(readlink -f "$main_worktree_path")"

workdir_path"$(readlink -f .)"

if [ "$main_worktree_path" = "$workdir_path" ]; then
  echo "error: main_worktree_path cannot be the current workdir, because it will be removed"
  exit 1
fi

cd "$main_worktree_path"

if ! [ -d .git ]; then
  echo "error: missing .git/"
  exit 1
fi

version=$(date --utc +%Y%m%dT%H%M%SZ).$(mktemp -u XXXXXXXX)

basename="${PWD##*/}"

# move .git to this path
git_path="../$basename.git"

# move untracked files to this directory
tmp_path="../$basename.untracked.$version"

if [ -e "$git_path" ]; then echo "error: path exists: ${git_path@Q}"; exit 1; fi
if [ -e "$tmp_path" ]; then echo "error: path exists: ${tmp_path@Q}"; exit 1; fi

# no. rsync does not move files
# remove-source-files means copy and delete files, which is slow/wasteful
if false; then
args=(
  rsync
    #--verbose # debug
    --archive
    --recursive # not implied by --archive when --files-from is used
    --remove-source-files # copy and delete files
    --from0
    .
    "$tmp_path"
)
"${args[@]}" \
    --files-from=<(
      git ls-files -dmoz --directory
    )
fi

echo "checking for untracked files"

# actually "move" untracked files
# git ls-files -dmoz | xargs -r -0 mv -v -t "$tmp_path" # wrong: mv does not preserve path
has_untracked=0
while IFS= read -r -d '' src; do
  if [ $has_untracked = 0 ]; then
    echo "moving untracked files to $tmp_path"
    mkdir -p "$tmp_path"
    has_untracked=1
  fi
  stack=("$src")
  dirstack=()
  while [ ${#stack[@]} != 0 ]; do
    echo "stack ${stack@Q}"
    src="${stack[0]}"; stack=("${stack[@]:1}") # shift src from stack
    #[ -z "$src" ] && continue
    echo "src ${src@Q}"
    isdir=0
    if [ "${src: -1}" = "/" ]; then # git shows directories with "/" suffix
      src="${src:0: -1}"
      isdir=1
    elif [ -d "$src" ]; then
      isdir=1
    fi
    srcdir="${src%/*}"
    dst="$tmp_path/$src"
    dstdir="${dst%/*}"
    #echo "dstdir ${dstdir@Q}"
    # mkdir -p "$(dirname "$dst")" # more generic
    if [ -e "$dstdir" ] && ! [ -d "$dstdir" ]; then
      dstdir2="$dstdir.$version"
      echo "note: file exists: ${dstdir@Q}. moving ${src@Q} to ${dstdir2@Q}"
      dstdir="$dstdir2"
      dst="$dstdir2/${src##*/}"
    fi
    if [ -e "$dst" ]; then # dst path exists
      if [ $isdir = 0 ]; then # src path is file
        if [ -d "$dst" ]; then # dst path is dir
          dst2="$dst.$version"
          echo "note: dir exists: ${dst@Q}. moving ${src@Q} to ${dst2@Q}"
          dst="$dst2"
        else # dst path is file
          # compare files: permissions, size, content
          if
            [ $(stat -c%a_%s "$src") = $(stat -c%a_%s "$dst") ] &&
            [ $(sha256sum "$src" | head -c64) = $(sha256sum "$dst" | head -c64) ]
          then
            echo "note: same file exists: ${dst@Q}. deleting ${src@Q}"
            rm "$src"
            continue
          else
            dst2="$dst.$version"
            echo "note: different file exists: ${dst@Q}. moving ${src@Q} to ${dst2@Q}"
            dst="$dst2"
          fi
        fi
      else # src path is dir
        if ! [ -d "$dst" ]; then # dst path is file
          dst2="$dst.$version"
          echo "note: file exists: ${dst@Q}. moving ${src@Q} to ${dst2@Q}"
          dst="$dst2"
        fi
        # dst path is dir
        echo "note: dir exists: ${dst@Q}. moving contents of ${src@Q}"
        # recurse: move contents of src dir
        while IFS= read -r -d '' src2; do
          if [ -d "$src2" ]; then
            stack+=("$src2/")
          else
            stack+=("$src2")
          fi
        done < <(
          find "$src" -mindepth 1 -maxdepth 1 -print0
        )
        #echo "dirstack+=(${src@Q})"
        dirstack+=("$src") # later: remove empty src dir
        #echo "dirstack ${dirstack@Q}"
        continue
      fi
      dstdir2="$dstdir.$version"
      echo "error: file exists: ${dstdir@Q}. moving to ${dstdir2@Q}"
      dstdir="$dstdir2"
      dst="$dstdir2/${src##*/}"
    fi
    mkdir -p "$dstdir"
    echo mv "${src@Q}" "${dst@Q}"
    mv "$src" "$dst"
    # no. still walking down here
    #rmdir --ignore-fail-on-non-empty "$srcdir" # remove srcdir if empty
  done
  # walk up
  # todo? try this more often to keep dirstack small
  while [ ${#dirstack[@]} != 0 ]; do
    #echo "dirstack ${dirstack@Q}"
    srcdir="${dirstack[-1]}"; dirstack=("${dirstack[@]:0:$((${#dirstack[@]} - 1))}") # pop srcdir from stack
    #echo "srcdir ${srcdir@Q}"
    rmdir --ignore-fail-on-non-empty "$srcdir" # remove srcdir if empty
  done
done < <(
  git ls-files -dmoz --directory
)

if [ $has_untracked = 0 ]; then
  echo "ok. found no untracked files"
else
  echo "ok. done moving untracked files"
fi



echo "moving .git/ to ${git_path@Q}"
mv .git "$git_path"

echo "setting git config core.bare=true in ${git_path@Q}"
git -C "$git_path" config --bool core.bare true

echo "removing main worktree ${main_worktree_path@Q}"
#cd "$workdir_path"
cd ..
rm -rf "$main_worktree_path"
离线来电— 2024-08-27 10:02:34

首先,备份您现有的存储库:

(a)  mkdir backup

(b)  cd backup

(c)  git clone non_bare_repo

其次,运行以下命令:

git clone --bare -l non_bare_repo new_bare_repo

First, backup your existing repo:

(a)  mkdir backup

(b)  cd backup

(c)  git clone non_bare_repo

Second, run the following:

git clone --bare -l non_bare_repo new_bare_repo
勿挽旧人 2024-08-27 10:02:34

添加了 2 个:
写完答案后意识到,如果后面跟着 git add *,接受的答案可能会在我的电脑上产生相同的结果。

文件从我的工作文件夹中消失(只有 . git 左),它又漂亮又紧凑:

git switch --orphan some_new_branch_name 

然后如果愿意的话转换为裸:

git config --bool core.bare true

这样保留包括远程链接的配置:

$ git config --list
core.repositoryformatversion=0
core.filemode=true
core.bare=true
remote.origin.url=https://github.com/vmatare/thinkfan.git
remote.origin.fetch=+refs/*:refs/*
remote.origin.mirror=true

添加:
在评论中提到它不会删除“任何 git-ignored 文件”,在这种情况下,需要手动另外删除它们(或者将存储库本身,即 .git 子文件夹移动到其他地方)。

备注:
core.bare true 之前,某些操作会导致错误:

$ git fetch --all
Fetching origin
fatal: Refusing to fetch into current branch refs/heads/devel of non-bare repository
error: Could not fetch origin

此后 some_new_branch_name 尚未在 gitbranch 的输出中列出。为了进一步测试,我做了git checkout master,我得到了文件,并且git分支的输出中再次没有some_new_branch_name,所以我认为新的除非在那里完成了一些工作(和/或执行了提交),否则孤儿分支不会被添加到存储库中。

Added 2:
After writing the answer realized that accepted answer would likely result in the same result on my PC if followed by git add *.

I got files disappear from my working folder (only .git left), it is again nice and compact by:

git switch --orphan some_new_branch_name 

Then convert to bare if one wants to:

git config --bool core.bare true

That way config including remote link is kept:

$ git config --list
core.repositoryformatversion=0
core.filemode=true
core.bare=true
remote.origin.url=https://github.com/vmatare/thinkfan.git
remote.origin.fetch=+refs/*:refs/*
remote.origin.mirror=true

Added:
In the comments it is mentioned that it would not remove "any git-ignored files", it such case they need to be additionally deleted manually (or repository itself that is .git subfolder be moved elsewhere).

Notes:
Before core.bare true some actions resulted in errors:

$ git fetch --all
Fetching origin
fatal: Refusing to fetch into current branch refs/heads/devel of non-bare repository
error: Could not fetch origin

some_new_branch_name have not been listed in output of git branch after that. To test further I did git checkout master I got files back and again no some_new_branch_name in output of git branch, so I think new orphan branch would not be added to repository unless some work is done there (and / or commits performed).

表情可笑 2024-08-27 10:02:34

Oneliner 用于执行上述所有操作:(

for i in `ls -A .`; do if [ $i != ".git" ]; then rm -rf $i; fi; done; mv .git/* .; rm -rf .git; git config --bool core.bare true

如果出现问题并且您没有备份,请不要怪我 :P)

Oneliner for doing all of the above operations:

for i in `ls -A .`; do if [ $i != ".git" ]; then rm -rf $i; fi; done; mv .git/* .; rm -rf .git; git config --bool core.bare true

(don't blame me if something blows up and you didn't have backups :P)

冬天的雪花 2024-08-27 10:02:34

哇,令人惊讶的是有这么多人参与其中,尤其是考虑到似乎没有人停下来问为什么这个人正在做他正在做的事情。

裸露和非裸露 git 存储库之间的唯一区别是非裸露版本有一个工作副本。您需要一个裸存储库的主要原因是,如果您想将其提供给第三方,您实际上无法直接对其进行操作,因此在某些时候您将不得不克隆它,此时您将需要克隆它。回到常规工作副本版本。

话虽如此,要转换为裸存储库,您所要做的就是确保没有待处理的提交,然后:

rm -R * && mv .git/* . && rm -R .git

好了,裸存储库。

Wow, it's simply amazing how many people chimed in on this, especially considering it doesn't seem that not a single on stopped to ask why this person is doing what he's doing.

The ONLY difference between a bare and non-bare git repository is that the non-bare version has a working copy. The main reason you would need a bare repo is if you wanted to make it available to a third party, you can't actually work on it directly so at some point you're going to have to clone it at which point you're right back to a regular working copy version.

That being said, to convert to a bare repo all you have to do is make sure you have no commits pending and then just :

rm -R * && mv .git/* . && rm -R .git

There ya go, bare repo.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文