首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >3路合并前的Git交互式重基修补程序

3路合并前的Git交互式重基修补程序
EN

Stack Overflow用户
提问于 2021-04-29 23:56:11
回答 2查看 258关注 0票数 1

我有以下的吉特历史:

我想从提交1f63 (2提交之前)到feature/project-setup的交互重基如下:

代码语言:javascript
复制
git rebase -i HEAD~2

然后,git-rebase-todo文件有以下几行:

代码语言:javascript
复制
pick ff7abc8 Install initial project site packages
pick 1696181 Add `.bumpversion.cfg`

如果我将第一行更改为edit,应用我的更改,然后执行git commit --amendgit rebase --continue,我的提交历史记录现在如下所示:

据我所知,交互式git重基是精心挑选两个提交到重基的根(在本例中,提交1f63)。我的问题是,如何用1696覆盖e16f,使分支保持不变?(我希望我的最后历史记录看起来像原来的样子,但是1696将被新的提交e16f所取代,这个e16f包含了我的更改)

我最初的想法是,我可能首先需要选择那些提交,然后删除它们,添加一个中断,签出feature/project-setup,然后进行快速转发合并提交。有什么想法吗?

编辑:,如果有一种更简单的方法不需要重新设置开发、主和0cea8,然后重新合并,请告诉我。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-04-30 04:32:35

更新:06年5月-2021年5月

下面是一个(相对)简单的bash脚本,它将更新分支名称和标记。自担风险使用!将其放置在

代码语言:javascript
复制
/usr/bin/  # On Linux

代码语言:javascript
复制
C:\Program Files\Git\usr\bin  # On Windows

并给它命名

代码语言:javascript
复制
git-rebase-bti

(没有文件扩展名),并使其可执行

代码语言:javascript
复制
chmod +x path/to/git-rebase-bti

或在Windows上

代码语言:javascript
复制
icacls path/to/git-rebase-bti /grant your_usrnm:(rx)

虽然我认为Windows默认为文件提供可执行权限?(不要引用我的话)

代码语言:javascript
复制
#! /bin/sh -
#
# Git Rebase Branch Names, Tags, and Forks
#
# File:
#   git-rebase-bti
#
# Installation Instructions:
#   Place this file in the following folder:
#     - Linux: `/usr/bin/`
#     - Windows: `C:\Program Files\Git\usr\bin`
#
# Usage:
#   git rebase-bti <SHA> 
#
# Authors:
# Copyleft 2020 Adam Hendry. All rights reserved.
#
# Original Author:
# Copyleft 2020 Adam Hendry. All rights reserved.
#
# License:
# GNU GPL vers. 2.0
# 
# This script is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This script is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this script; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330,
# Boston, MA  02111-1307  USA

GIT_DIR='.git'
REBASE_DIR="${GIT_DIR}/rebase-merge"
TODO_FILE="${REBASE_DIR}/git-rebase-todo"
TODO_BACKUP="${TODO_FILE}.backup"

HEADS_FOLDER='refs/heads'
TAGS_FOLDER='refs/tags'
REWRITTEN_FOLDER='refs/rewritten'

# Initialize associative array (dictionary) variables
declare -A labels_by_sha  # Rebase label names indexed by original SHA
declare -A shas_by_label  # Original SHAs indexed by rebase label names

# Get heads (remove '.git/refs/heads' from beginning)
heads=($(find "${GIT_DIR}/${HEADS_FOLDER}" -type f | cut -d '/' -f 4-))

# Get tags (remove '.git/refs/tags' from beginning)
tags=($(find "${GIT_DIR}/${TAGS_FOLDER}" -type f | cut -d '/' -f 4-))

# Start the rebase operation in the background
git rebase -i --rebase-merges $1 &

# Capture the process ID
pid_main=$!

# Wait until the todo file is created
until [ -e "$TODO_FILE" ] && [ -e "$TODO_BACKUP" ]
do
  continue
done

# Store rebase message
rebase_message=$(tac $TODO_FILE | sed '/^$/q' | tac)

# Store todo list
rebase_message_length=$(echo "$rebase_message" | wc -l)
todo_list=$(cat $TODO_FILE | head -n -"$rebase_message_length")

# Prompt user
printf "Calculating todo file. Please wait..." > $TODO_FILE

# Get label names
label_names=($(grep -oP '^(l|label) \K[^ ]*$' -- $TODO_BACKUP))

for label_name in "${label_names[@]}"
do
  if [ $label_name = 'onto' ]
  then
    continue
  fi
  
  command_line=$(grep -B 1 -P '^(l|label) '"$label_name"'$' $TODO_BACKUP | head -n 1 | sed 's/\n//g')
  command_name=$(echo "$command_line" | grep -oP '^(p|pick|m|merge)(?= )')
  
  label_sha=
  
  if [ "$command_name" = 'p' ] || [ "$command_name" = 'pick' ]
  then
    label_sha=$(echo $command_line | grep -oP '^(p|pick) \K[[:alnum:]]*' | cut -c1-7)
  elif [ "$command_name" = 'm' ] || [ "$command_name" = 'merge' ]
  then
    label_sha=$(echo $command_line | grep -oP '^(m|merge) -[cC] \K[[:alnum:]]*' | cut -c1-7)
  fi
  
  shas_by_label["$label_name"]="$label_sha"
  labels_by_sha["$label_sha"]="$label_name"
done

# Restore Branch Names
todo_list+="\n\n# Restore Branch Names\n"

for head in "${heads[@]}"
do
  sha=$(cat "${GIT_DIR}/${HEADS_FOLDER}/${head}" | cut -c1-7)
  
  if [ -n "${labels_by_sha[$sha]}" ]
  then
    todo_list+='exec git update-ref '"${HEADS_FOLDER}/${head}"' '"${REWRITTEN_FOLDER}/${labels_by_sha[$sha]}\n"
  fi
  
  # elif in `git rev-list`, pick sha and label it, then `git update-ref` here`
  
done

todo_list+='\n# Restore Tag Names\n'

for tag in "${tags[@]}"
do
  sha=$(cat "${GIT_DIR}/${TAGS_FOLDER}/${tag}" | cut -c1-7)
  
  if [ -n "${labels_by_sha[$sha]}" ]
  then
    todo_list+='exec git update-ref '"${TAGS_FOLDER}/${tag}"' '"${REWRITTEN_FOLDER}/${labels_by_sha[$sha]}\n"
  fi
done

todo_list+="$rebase_message"

# Update todo file
printf "$todo_list" > $TODO_FILE

# Wait until the rebase operation is completed
wait $pid_main

# Exit the script
exit 0

答案:

交互式重基可以用来实现这些更改,但是HEAD和重基根之间存在的分支和标记名称(否则会阻止Git的垃圾收集移除这些旧的提交)必须首先删除,然后在重基之后重新应用。不幸的是,要正确工作,必须从历史的尖端(即develop分支)开始重新建立基础。

develop重基

代码语言:javascript
复制
git checkout develop
git branch -D feature/project-setup
git branch -D master
git tag -d 0.1.0
git rebase -i --rebase-merges

edit添加到您希望更改的提交中,然后进行更改(git add -A)、修改提交(git commit --amend)和完成重基(git rebase --continue)。

之后,一个接一个地添加分支和标签名。

代码语言:javascript
复制
git branch master cfa8
git branch feature/project-setup 1696
git checkout master
git tag 0.1.0

虽然这里开发的git-rebasetag脚本是一个很好的开端,但它只在Linux机器上工作,只对标记进行重基而不对分支名称,在标记提交消息上进行匹配(对于不带注释的标记不起作用),并且使用python而不是shell脚本,后者的可移植性稍差一些。

或者,可以按以下方式更新rebase-todo

代码语言:javascript
复制
label onto

# Branch feature-project-setup
reset onto
pick ff7abc83 Install initial project site packages
pick 1696181f Add `.bumpversion.cfg`
label feature-project-setup

# Branch release-0-1-0
reset 8e2d63e # Initial commit
merge -C c598c3bf feature-project-setup # Merge branch 'feature/project-setup' into develop
label branch-point
pick 0cea85a3 Bump version: 0.0.0 → 0.1.0
label release-0-1-0

# Branch 0-1-0
reset 8e2d63e # Initial commit
merge -C cfa8ed17 release-0-1-0 # Merge branch 'release/0.1.0' into master
label 0-1-0

reset branch-point # Merge branch 'feature/project-setup' into develop
merge -C a22db135 0-1-0 # Merge tag '0.1.0' into develop
label develop

# Reset branch and tag names
reset feature-project-setup
exec git branch -D feature/project-setup
exec git branch feature/project-setup

reset 0-1-0
exec git tag -d 0.1.0
exec git tag 0.1.0
exec git branch -D master
exec git branch master
reset develop

或者,由于git重基将标签写入refs/rewritten,可以使用一些管道命令以更少的行完成这一任务:

代码语言:javascript
复制
exec git update-ref refs/heads/feature/project-setup refs/rewritten/feature-project-setup
exec git update-ref refs/heads/master refs/rewritten/0-1-0
exec git update-ref refs/tags/0.1.0 refs/rewritten/0-1-0

在上面的标签中,0-1-0同时适用于这个特定实例中的master和标记0.1.0

如果能把它变成额外的重基选项,比如--rebase-tagsrebase-branch-names,那就太好了。不幸的是,pre-rebase钩子无法工作,因为这发生在rebase-todo生成之前。此外,也没有post-rebase钩子。因此,一个单独的shell脚本似乎是谨慎的。

最后,上面的分叉点不需要重基,如果重基路径修订列表中有一个未合并的分叉点,也需要重新设置分叉点。如果发生这种情况,可以尝试在待办事项列表的末尾添加以下内容:

代码语言:javascript
复制
reset new_base_branch  # Be sure to `label new_base_branch` before here at right spot
exec git branch temp_name  # Give new base a temp name
exec git checkout branch_to_rebase
exec git rebase temp_name
exec git branch -D temp_name

这也是一个很好的附加选项(如--rebase-forks),但代码还需要检查branch_to_rebase实际上没有一个子节点可以合并到重基路径之外的onto分支中。为了获得最好的安全性,我将始终从您的存储库的提交技巧中提取rebase -i --rebase-merges

票数 1
EN

Stack Overflow用户

发布于 2021-04-30 02:29:14

如果有一种更简单的方法,不需要重新设置开发、主和0cea8,然后重新合并,请告诉我。

没有,但是重置0cea8的概念也是无稽之谈。我们只能“重置”分支名称(嗯,Git的索引和工作树,因为Git将太多东西塞进git reset命令中)。请看下面关于分支名称的一件大事。

关于提交,有几件事需要实现:

  • 它们完全,完全是只读的。
  • 它们的真实名称是它们的散列ID,它是根据提交的内容计算的(包括元数据-从技术上讲,它是根据元数据计算的,因为保存的快照是元数据)。
  • 任何给定提交的元数据都会给出它的前任提交(如果是常规的单亲提交)或其所有父级(如果是合并提交)的原始散列ID。

综合起来,这意味着您始终可以将提交复制到一个新的和改进的版本,但是这样做之后,您现在必须复制每个“下游”(后续)提交。那是因为,考虑到这样的情况:

代码语言:javascript
复制
... <-F <-G <-H

如果H是序列中的“最后”提交,如果我们将F复制到一个新的和改进的F'中,那么我们需要一个新的和改进的G,其中的改进,或者至少是其中的一个,是G'的父级是F'。然后,我们还需要将H复制到一个新的和改进的H'中,以便它的父级可以是G'

关于masterdevelop-almost这样的分支名称,真正需要了解的只有一件大事--其他的一切都是从这件大事中得到的--那就是每个分支只保存一个提交哈希ID。无论哪个哈希ID存储在分支名称中,提交都是该分支的最后一个提交:

代码语言:javascript
复制
...--F--G--H   <-- branch

分支branch的最后一次提交是提交H。是提交H本身导致提交G成为分支branch的一部分,然后是提交G本身导致提交F成为分支的一部分,以此类推。历史是一组提交:不多也不少。

因此,如果您有一些您不喜欢的历史记录,您可以构建新的历史记录--新的提交--这是您所喜欢的,但是即使对过去的提交做了一次简单的更改,新的和改进的提交都有一个不同的散列ID,并且这种更改会影响到提交的其余部分。

GIT2.18学习了一个新的--rebase-merges选项,它允许您不仅自动复制单个提交,而且还可以重复合并操作(不能复制合并提交,因此必须重新执行合并)。这使您获得了大部分的方式,但仍然需要手动调整分支名称,以便指向新的和改进的提交。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67326512

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档