git仓库迁移(保留提交记录)

迁移项目需要用到一个开源的项目:https://github.com/samrocketman/gitlab-mirrors

同时上传了一个修改后的:git仓库————迁移脚本-Python文档类资源-CSDN下载

这里仅介绍如果把老Git库项目迁移到新Git库,不涉及更新库与删除库

一、原理介绍

主要用到开源项目中的三个文件:config.sh -> add_mirror.sh -> lib/manage_gitlab_project.py,之后会详细讲解这三个文件;

脚本主要是shell+python构成,运行方式比较简单,我这里使用的是Git Bash Here来运行shell命令的,首先进入到gitlab-mirrors目录下,右键即可,如下图:

 运行命令如下:

./add_mirror.sh --git --group-id 组的id --project-name abc --mirror http://账号:访问令牌@192.168.15.20/abc.git

运行原理:

        1. 在运行的电脑上,将老的Git镜像下来

        2. 在新的Git服务器上创建项目

        3. 将运行电脑上的Git remote设置为新的Git地址

        4. 将运行电脑上的Git同步到新的Git地址

二、配置文件介绍(config.sh)

#Environment file

#
# gitlab-mirrors settings
#

#The user git-mirrors will run as.(随便命名)
system_user="gitmirror"
#The home directory path of the $system_user
# 旧Git与新Git的同步中转站,保证此目录为空即可
user_home="d:/gitmirror/"
#The repository directory where gitlab-mirrors will contain copies of mirrored
#repositories before pushing them to gitlab.
# 具体的中转项目,会在此目录下创建对应的项目
repo_dir="${user_home}/repositories"
#colorize output of add_mirror.sh, update_mirror.sh, and git-mirrors.sh
#commands.(不用管)
enable_colors=true
#These are additional options which should be passed to git-svn.  On the command
#line type "git help svn" (不用管)
git_svn_additional_options="-s"
#Force gitlab-mirrors to not create the gitlab remote so a remote URL must be
#provided. (superceded by no_remote_set)(不用管)
no_create_set=false
#Force gitlab-mirrors to only allow local remotes only.(不用管)
no_remote_set=false
#Enable force fetching and pushing.  Will overwrite references if upstream
#forced pushed.  Applies to git projects only.(不用管)
force_update=false
#This option is for pruning mirrors.  If a branch is deleted upstream then that
#change will propagate into your GitLab mirror.  Aplies to git projects only.
prune_mirrors=false

#
# Gitlab settings
#

#This is the base web url of your Gitlab server.
#新Git服务地址
gitlab_url="http://192.168.15.20"
#Special user you created in Gitlab whose only purpose is to update mirror sites
#and admin the $gitlab_namespace group.
#新Git服务器上的账号
gitlab_user=git账号名字
#Generate a token for your $gitlab_user and set it here.
#新Git服务器上生成的访问令牌,后面会讲解如何生成
gitlab_user_token_secret=访问令牌
#Sets the Gitlab API version, either 3 or 4
# 不用管
gitlab_api_version=4
#Verify signed SSL certificates?
# 不用管
ssl_verify=true
#Push to GitLab over http?  Otherwise will push projects via SSH.
# 不用管
http_remote=false

#
# Gitlab new project default settings.  If a project needs to be created by
# gitlab-mirrors then it will assign the following values as defaults.
#

#values must be true or false
# 是否开启讨论功能
issues_enabled=false
wall_enabled=false
# 是否开启Wiki功能
wiki_enabled=true
snippets_enabled=false
#是否开启merge功能
merge_requests_enabled=true
public=false

具体每个字段的含义,在上面的脚本中已经注明

访问令牌的生成,如下图所示:

 

 三、同步脚本(add_mirror.sh)

#!/bin/bash
#Created by Sam Gleske
#MIT License
#Created Tue Sep 10 23:01:08 EDT 2013
#USAGE
#  ./add_mirror.sh --git --project-name someproject --mirror http://example.com/project.git

#bash option stop on first error
set -e

#Include all user options and dependencies
# 添加所有的依赖库
git_mirrors_dir="${0%/*}"
source ${git_mirrors_dir}/includes.sh

#check if api version is set
[ -z $gitlab_api_version ] && gitlab_api_version=4

#export env vars for python script
# 导出config.sh中配置的环境变量,python中也可以用
export gitlab_user_token_secret gitlab_url gitlab_user ssl_verify gitlab_api_version

PROGNAME="${0##*/}"
PROGVERSION="${VERSION}"

#Default script options
git=false
project_name=""
mirror=""
group_id=0 # 从命令行读取的组ID
desc=""
force=false
no_create_set="${no_create_set:-false}"
no_remote_set="${no_remote_set:-false}"
http_remote="${http_remote:-false}"

# 自定义变量
# 通过组ID获取到的组的路径名字
group_name="" # 组对应的路径名字

#
# ARGUMENT HANDLING
#

usage()
{
  cat <<EOF
${PROGNAME} ${PROGVERSION} - MIT License by Sam Gleske

USAGE:
  ${PROGNAME} TYPE --project NAME --mirror URL [--authors-file FILE]

DESCRIPTION:
  This will add a git repository to be mirrored by GitLab.  It
  first checks to see if the project exists in gitlab.  If it does
  not exist then it creates it.  It will then clone and check in the
  first copy into GitLab.  From there you must use the update_mirror.sh
  script or git git-mirrors.sh script.

  -h,--help          Show help

  -v,--version       Show program version

  -f,--force         Force add project even if it already exists.
                     Any program errors will automatically continue.

  -m,--mirror URL    Repository URL to be mirrored.

  -n,--no-create URL Set a static remote without attempting to resolve
                     the remote in GitLab.  This allows more generic
                     mirroring without needing to be specifically for
                     GitLab.

  -l,--no-remote     This is a local only mirror.  There is no remote
                     to push to.  This is meant for mirroring remote
                     projects on a developer machine.

  -g,--group-id     目标git的组Id

  --desc            git的描述

  -p,--project-name NAME
                     Set a GitLab project name to NAME.

REPOSITORY TYPES:
  At least one repository TYPE is required.

  --git              Mirror a git repository (must be explicitly set)

EOF
}
#Short options are one letter.  If an argument follows a short opt then put a colon (:) after it
SHORTOPTS="hvflm:n:p:"
# 如果要在命令行里加参数,需要在下方添加,用于读取命令行中的数据
LONGOPTS="help,version,force,git,mirror:,no-create:,desc:,group-id:,no-remote,project-name:,authors-file:"
ARGS=$(getopt -s bash --options "${SHORTOPTS}" --longoptions "${LONGOPTS}" --name "${PROGNAME}" -- "$@")
eval set -- "$ARGS"
while true; do
  case $1 in
    -h|--help)
        usage
        exit 1
      ;;
    -v|--version)
        echo "${PROGNAME} ${PROGVERSION}"
        exit 1
      ;;
    --git)
        git=true
        shift
      ;;
    -f|--force)
        force=true
        set +e
        shift
      ;;
    -p|--project-name)
        project_name="${2}"
        shift 2
      ;;
    -m|--mirror)
        mirror="${2}"
        shift 2
      ;;
    -n|--no-create)
        no_create_set=true
        no_create="${2}"
        shift 2
      ;;
    -l|--no-remote)
        no_remote_set=true
        shift 1
      ;;
    --group-id)
        group_id="${2}"
        shift 2
      ;;
    --desc)
        desc="${2}"
        shift 2
      ;;
    --authors-file)
        authors_file="${2}"
        shift 2
      ;;
    --)
        shift
        break
      ;;
    *)
        shift
      ;;
    esac
done

#
# Program functions
#

function preflight() {
  STATUS=0
  #test for multiple repository types
  types=0
  selected_types=()
  if ${git};then
    ((types += 1))
    selected_types+=('--git')
  fi
  if [ "${types}" -eq "0" ];then
    red_echo -n "Must select at least one repository type.  e.g. "
    yellow_echo "--git"
    STATUS=1
  elif [ "${types}" -gt "1" ];then
    red_echo -n "Multiple repository types not allowed.  Found:"
    for x in ${selected_types[@]};do
      yellow_echo -n " $x"
    done
    echo ""
    STATUS=1
  fi
  #test required project_name option
  if [ -z "${project_name}" ];then
    red_echo -n "Missing "
    yellow_echo -n "--project-name"
    red_echo " option."
    STATUS=1
  fi
  #test required mirror option
  if [ -z "${mirror}" ];then
    red_echo -n "Missing "
    yellow_echo -n "--mirror"
    red_echo " option."
    STATUS=1
  fi
  #test no_create_set environment variable (must be bool)
  if [ ! "${no_create_set}" = "true" ] && [ ! "${no_create_set}" = "false" ];then
    red_echo -n "no_create_set="
    yellow_echo -n "${no_create_set}"
    red_echo -n " is not a valid option for no_create_set!  Must be "
    yellow_echo -n "true"
    red_echo -n " or "
    yellow_echo -n "false"
    red_echo "."
    STATUS=1
  elif ${no_create_set} && [ -z "${no_create}" ];then
    yellow_echo -n "--no-create"
    red_echo " option must have a git remote to push."
    STATUS=1
  fi
  #test no_remote_set environment variable (must be bool)
  if [ ! "${no_remote_set}" = "true" ] && [ ! "${no_remote_set}" = "false" ];then
    red_echo -n "no_remote_set="
    yellow_echo -n "${no_remote_set}"
    red_echo -n " is not a valid option for no_remote_set!  Must be "
    yellow_echo -n "true"
    red_echo -n " or "
    yellow_echo -n "false"
    red_echo "."
    STATUS=1
  fi
  #test authors_file path for existence
  if [ ! -z "${authors_file}" -a ! -f "${authors_file}" ];then
    red_echo -n "Specified "
    yellow_echo -n "--authors-file"
    red_echo " does not exist!"
    STATUS=1
  fi
  #test ssl_verify environment variable (must be bool)
  if [ ! "${ssl_verify}" = "true" ] && [ ! "${ssl_verify}" = "false" ];then
    red_echo -n "ssl_verify="
    yellow_echo -n "${ssl_verify}"
    red_echo -n " is not a valid option for ssl_verify!  Must be "
    yellow_echo -n "true"
    red_echo -n " or "
    yellow_echo -n "false"
    red_echo "."
    STATUS=1
  fi
  #test http_remote environment variable (must be bool)
  if [ ! "${http_remote}" = "true" ] && [ ! "${http_remote}" = "false" ];then
    red_echo -n "http_remote="
    yellow_echo -n "${http_remote}"
    red_echo -n " is not a valid option for http_remote!  Must be "
    yellow_echo -n "true"
    red_echo -n " or "
    yellow_echo -n "false"
    red_echo "."
    STATUS=1
  fi
  #test enable_colors environment variable (must be bool)
  if [ ! "${enable_colors}" = "true" ] && [ ! "${enable_colors}" = "false" ];then
    red_echo -n "enable_colors="
    yellow_echo -n "${enable_colors}"
    red_echo -n " is not a valid option for enable_colors!  Must be "
    yellow_echo -n "true"
    red_echo -n " or "
    yellow_echo -n "false"
    red_echo "."
    STATUS=1
  fi
  #test issues_enabled environment variable (must be bool)
  if [ ! "${issues_enabled}" = "true" ] && [ ! "${issues_enabled}" = "false" ];then
    red_echo -n "issues_enabled="
    yellow_echo -n "${issues_enabled}"
    red_echo -n " is not a valid option for issues_enabled!  Must be "
    yellow_echo -n "true"
    red_echo -n " or "
    yellow_echo -n "false"
    red_echo "."
    STATUS=1
  fi
  #test wall_enabled environment variable (must be bool)
  if [ ! "${wall_enabled}" = "true" ] && [ ! "${wall_enabled}" = "false" ];then
    red_echo -n "wall_enabled="
    yellow_echo -n "${wall_enabled}"
    red_echo -n " is not a valid option for wall_enabled!  Must be "
    yellow_echo -n "true"
    red_echo -n " or "
    yellow_echo -n "false"
    red_echo "."
    STATUS=1
  fi
  #test wiki_enabled environment variable (must be bool)
  if [ ! "${wiki_enabled}" = "true" ] && [ ! "${wiki_enabled}" = "false" ];then
    red_echo -n "wiki_enabled="
    yellow_echo -n "${wiki_enabled}"
    red_echo -n " is not a valid option for wiki_enabled!  Must be "
    yellow_echo -n "true"
    red_echo -n " or "
    yellow_echo -n "false"
    red_echo "."
    STATUS=1
  fi
  #test snippets_enabled environment variable (must be bool)
  if [ ! "${snippets_enabled}" = "true" ] && [ ! "${snippets_enabled}" = "false" ];then
    red_echo -n "snippets_enabled="
    yellow_echo -n "${snippets_enabled}"
    red_echo -n " is not a valid option for snippets_enabled!  Must be "
    yellow_echo -n "true"
    red_echo -n " or "
    yellow_echo -n "false"
    red_echo "."
    STATUS=1
  fi
  #test public environment variable (must be bool)
  if [ ! "${public}" = "true" ] && [ ! "${public}" = "false" ];then
    red_echo -n "public="
    yellow_echo -n "${public}"
    red_echo -n " is not a valid option for public!  Must be "
    yellow_echo -n "true"
    red_echo -n " or "
    yellow_echo -n "false"
    red_echo "."
    STATUS=1
  fi
  #test merge_requests_enabled environment variable (must be bool)
  if [ ! "${merge_requests_enabled}" = "true" ] && [ ! "${merge_requests_enabled}" = "false" ];then
    red_echo -n "merge_requests_enabled="
    yellow_echo -n "${merge_requests_enabled}"
    red_echo -n " is not a valid option for merge_requests_enabled!  Must be "
    yellow_echo -n "true"
    red_echo -n " or "
    yellow_echo -n "false"
    red_echo "."
    STATUS=1
  fi
  return ${STATUS}
}

#
# Main execution
#

#Run a preflight check on options for compatibility.
if ! preflight 1>&2;then
  echo "Command aborted due to previous errors." 1>&2
  exit 1
fi

#Set up project creation options based on config.sh to be passed to create manage_gitlab_project.py
# 构建参数,传递到manage_gitlab_project.py,也就是将shell数据传递到python中
CREATE_OPTS=""
if ${issues_enabled};then
  CREATE_OPTS="--issues ${CREATE_OPTS}"
fi
if ${wall_enabled};then
  CREATE_OPTS="--wall ${CREATE_OPTS}"
fi
if ${merge_requests_enabled};then
  CREATE_OPTS="--merge ${CREATE_OPTS}"
fi
if ${wiki_enabled};then
  CREATE_OPTS="--wiki ${CREATE_OPTS}"
fi
if ${snippets_enabled};then
  CREATE_OPTS="--snippets ${CREATE_OPTS}"
fi
if ${public};then
  CREATE_OPTS="--public ${CREATE_OPTS}"
fi
if ${http_remote};then
  CREATE_OPTS="--http ${CREATE_OPTS}"
fi

# 如果远端不存在,则调用python相应的命令,进行项目的创建
#Get the remote gitlab url for the specified project.
#If the project doesn't already exist in gitlab then create it.
if ! ${no_remote_set} && [ -z "${no_create}" ];then
  green_echo "Resolving gitlab remote." 1>&2
  # 调用python方法,去创建Git仓库
  gitlab_remote=$(python lib/manage_gitlab_project.py --create --groupid ${group_id} --desc "${desc}" ${CREATE_OPTS} "${project_name}")
  # 接收python的返回值,解析为对应的数据
  # retstr=$?
  green_echo ${gitlab_remote}
  # 以&进行字符串拆分,python传出的数据
  array=(${gitlab_remote//,/ })
  # 构建咋们新Git的地址
  gitlab_remote="http://admin_jt:${gitlab_user_token_secret}@192.168.15.20/${array[0]}/${project_name}.git"
  # 同步某个仓库的wiki时,用下面的地址
  # gitlab_remote="http://admin_jt:${gitlab_user_token_secret}@192.168.15.20/${array[0]}/${project_name}.wiki.git"
  # 设置项目的路径名称
  group_name=${array[1]}
  # for var in ${array[@]}
  # do
  #   echo $var
  # done
else
  if ! ${no_remote_set};then
    green_echo -n "Using remote: " 1>&2
    echo "${no_create}" 1>&2
    gitlab_remote="${no_create}"
  else
    echo "Local only mirror." 1>&2
  fi
fi

#Check for namespace directory existence
# 创建项目的Git目录
if [ ! -e "${repo_dir}/${group_name}" ];then
  mkdir -p "${repo_dir}/${group_name}"
elif [ ! -d "${repo_dir}/${group_name}" ];then
  red_echo "Error: \"${repo_dir}/${group_name}\" exists but is not a directory." 1>&2
  exit 1
elif [ -d "${repo_dir}/${group_name}/${project_name}" ] && ! ${force};then
  red_echo "Error: \"${repo_dir}/${group_name}/${project_name}\" exists already.  Aborting command." 1>&2
  exit 1
fi
#Resolve the $authors_file path because of changing working directories
if [ ! -z "${authors_file}" ];then
  if ! echo "${authors_file}" | grep '^/' &> /dev/null;then
    authors_file="${PWD}/${authors_file}"
    authors_file="$(echo ${authors_file} | sed 's#/./#/#g')"
  fi
fi
cd "${git_mirrors_dir}"

if ${git};then
  green_echo "Creating mirror from ${mirror} ${group_name}" 1>&2
  # 进入Git目录
  cd "${repo_dir}/${group_name}"
  # 将老的Git项目克隆到本地
  git clone --mirror "${mirror}" "${project_name}"
  cd "${project_name}"
  if ! ${no_remote_set};then
    green_echo "Adding gitlab remote to project." 1>&2
    # 在本地添加新Git地址为新的远端
    git remote add gitlab "${gitlab_remote}"
    # 配置同步内容
    git config --add remote.gitlab.push '+refs/heads/*:refs/heads/*'
    git config --add remote.gitlab.push '+refs/tags/*:refs/tags/*'
    git config remote.gitlab.mirror true
    #Check the initial repository into gitlab
    green_echo "Checking the mirror into gitlab." 1>&2
    # 从老Git地址拉取最新的内容
    git fetch
    if ${http_remote};then
      git config credential.helper store
    fi
    # 将Git内容,推送到新的远端(新Git地址)
    git push gitlab
    if [ ! -z "${no_create}" ];then
      git config gitlabmirrors.nocreate true
    fi
  else
      git config gitlabmirrors.noremote true
  fi
  green_echo "All done!" 1>&2
fi

四、同步脚本(manage_gitlab_project.py)

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Created by Sam Gleske
#MIT License
#Created Tue Sep 10 23:01:08 EDT 2013

from sys import argv,exit,stderr
from optparse import OptionParser
import os

try:
  import gitlab
except ImportError:
  raise ImportError("python-gitlab module is not installed.  You probably didn't read the install instructions closely enough.  See docs/prerequisites.md.")

def printErr(message):
  stderr.write(message + "\n")
  stderr.flush()

# 通过组的ID,查找到对应的组位置
def find_group(gid):
  groups = git.groups.list(all=True)

  for obj in groups:
    # printErr(str(obj.id) +" asdasdasd "+ str(gid))
    # printErr(str(obj.web_url))
    if getattr(obj,"id") == int(gid):
      return obj
  return None

# 通过组的名字,和项目名字,确定项目对象
def find_project(gid,pname):
  projects = git.projects.list(as_list=True)
  for obj in projects:
    # printErr(str(getattr(obj,"name")) +"-----"+ str(pname))
    # printErr(str(getattr(obj,"namespace")) +"-----"+ str(gid))
    nspace = getattr(obj,"namespace")
    if getattr(obj,"name") == pname and nspace["id"] == int(gid):
      return obj
  return None

# 创建项目
def createproject(pname):
  if options.public:
     visibility_level="public"
  else:
     visibility_level="private"
  if len(options.desc) == 0:
    if options.public:
      description="Public mirror of %s." % project_name
    else:
      description="Git mirror of %s." % project_name
  else:
    description=options.desc
  project_options={
    'issues_enabled': options.issues,
    'wall_enabled': options.wall,
    'merge_requests_enabled': options.merge,
    'wiki_enabled': options.wiki,
    'snippets_enabled': options.snippets,
    'visibility': visibility_level,
    'namespace_id': options.groupid,
  }
  #make all project options lowercase boolean strings i.e. true instead of True
  for x in project_options.keys():
    project_options[x] = str(project_options[x]).lower()
  printErr("Creating new project %s" % pname)
  project_options['name'] = pname
  project_options['description'] = description
  git.projects.create(project_options)
  found_project = find_project(options.groupid,pname)
  return found_project

# 开始进入游戏主体
try:
  token_secret=os.environ['gitlab_user_token_secret']
  gitlab_url=os.environ['gitlab_url']
  gitlab_user=os.environ['gitlab_user']
  ssl_verify=os.environ['ssl_verify']
  gitlab_api_version=os.environ['gitlab_api_version']
except KeyError:
  printErr("Environment config missing.  Do not run this script standalone.")
  exit(1)
parser = OptionParser()
# 解析路径中的参数
parser.add_option("--issues",dest="issues",action="store_true",default=False)
parser.add_option("--wall",dest="wall",action="store_true",default=False)
parser.add_option("--merge",dest="merge",action="store_true",default=False)
parser.add_option("--wiki",dest="wiki",action="store_true",default=False)
parser.add_option("--snippets",dest="snippets",action="store_true",default=False)
parser.add_option("--public",dest="public",action="store_true",default=False)
parser.add_option("--create",dest="create",action="store_true",default=False)
parser.add_option("--delete",dest="delete",action="store_true",default=False)
parser.add_option("--desc",dest="desc",metavar="DESC",default=False)
parser.add_option("--groupid",dest="groupid",metavar="GROUPID",default=False)
parser.add_option("--http",dest="http",action="store_true",default=False)
(options,args) = parser.parse_args()
# printErr(str(options)+"   ajskldkljajklf")

if len(args) == 0:
  printErr("No project name specified.  Do not run this script standalone.")
  exit(1)
elif len(args) > 1:
  printErr("Too many arguments.  Do not run this script standalone.")
  exit(1)

project_name=args[0]

# 链接到git仓库
if not eval(ssl_verify.capitalize()):
  git=gitlab.Gitlab(gitlab_url,token_secret,ssl_verify=False,api_version=gitlab_api_version)
else:
  git=gitlab.Gitlab(gitlab_url,token_secret,ssl_verify=True,api_version=gitlab_api_version)

if options.create:
  found_group = find_group(options.groupid)
  # 打印git组信息
  # printErr(str(found_group))

  found_project = None
  # 查找项目是否存在
  found_project= find_project(options.groupid,project_name)
  if not found_project:
    # 项目不存在的情况,则创建项目
    found_project=createproject(project_name)
    if not found_project:
      printErr("There was a problem creating {group}/{project}.  Did you give {user} user Admin rights in gitlab?".format(group=found_group.name,project=project_name,user=gitlab_user))
      exit(1)
  # 打印项目的结构体
  # printErr(str(found_project))
  # 下方的print是为了让shell能接收到返回的数据
  print(found_group.full_path+","+found_project.name)
  if options.http:
    print(found_project.http_url_to_repo)
  else:
    print(found_project.ssh_url_to_repo)
elif options.delete:
  # 执行项目的删除
  try:
    deleted_project=find_project(options.groupid,project_name).delete()
  except Exception as e:
    printErr(e)
    exit(1)
else:
  printErr("No --create or --delete option added.")
  exit(1)

五、运行命令

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值