吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1612|回复: 2
收起左侧

[其他转载] git rebase 修改commit时间

 关闭 [复制链接]
鴻渊 发表于 2022-4-2 16:06

废话

最近碰到一个需求,就是需要修改git项目最早commit时间,全部都往前推一段时间,身为渣渣的我,经历了几天几夜的奋斗,终于搞定了,特来分享一波,要是有发现哪里漏的,可以偷偷告诉我下。

步骤

git rebase 命令可以修改commit信息 包括时间、作者、描述等。特以此入手。下文A表示需要变基的分支

  • 翻阅资料后,准备了一个不成熟的 shell 脚本,用于实现自动处理,脚本存在不完善部分,主要是缺少测试数据,有想法的可以自己补充一波
  • 另 需注意 windows环境下 GIT_COMMITTER_DATE命令无法执行,mac的不确定,毕竟穷人买不起,window环境下需在 git bash 中执行
  • 通过 _git log > /C/Users/hong/Desktop/log.txt_ 将A的git日志导出到log.txt文件
  • 通过如下java代码 提取 log.txt 文件内容,同时自动生成待处理的 commit 脚本文件
    • 考虑到部分提交存在合并的情况,因而 rebase 时 需要处理的commit可能会跟log.txt顺序不一致,因而考虑生成多个commit 脚本文件 循环调用执行
  • 若想从首次提交开始变基 则使用 git rebase -i --root,若只是想修改某个区间(注:该区间指定的是一个前开后闭的区间)的可使用 git rebase -i [startpoint]  [endpoint],Emm 后面这种方式虽然可以变基,但是奈何能力太浅,变基后生成新分支之后我就不知道怎么覆盖到原分支了,有知道的大佬,有空可以介绍下
    • rebase 会进入可编辑页面,具体使用方式参考 vim
    • 每个commit 正常都是 pick 操作,可进行的修改如下
      • pick:保留该commit(缩写:p)
      • reword:保留该commit,但我需要修改该commit的注释(缩写:r)
      • edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
      • squash:将该commit和前一个commit合并(缩写:s)
      • fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
      • exec:执行shell命令(缩写:x)
      • drop:我要丢弃该commit(缩写:d)
    • edit即可实现我的需求,因而统一通过_%s#pick #e #g_修改为e,并保存, 其中 %s 表示全局替换, 若想按行替换 可改成 1,5s 表示1-5行,根据:set nu 看到的行号进行修改
  • 直接在 git bash 上执行通用脚本即可 不需要传入参数
  • 确定没问题 执行 git push --force-with-lease 强制更新, 建议强制更新之前备份原分支

    用到的java代码

// 要往前多少天
int minusDays = 360;
// commit time 往前推操作截止时间
String cutOffTime = "2021-04-21 00:00:00";

@SneakyThrows
@Test
public void readLogFile() {
  // 日志文件路径   commit 脚本文件要生成到哪个文件夹中
  String filePath = "D:\\log.txt", dirPath = "D:\\gitsh\\";
  // 清空文件夹
  File file = new File(dirPath);
  if (file.exists()) {// 判断是否待删除目录是否存在
    String[] content = file.list();// 取得当前目录下所有文件和文件夹
    assert content != null;
    for(String name : content) {
      File temp = new File(dirPath, name);
      temp.delete();
    }
  } else {
    file.mkdir(); // 创建文件夹
  }
  BufferedReader reader = new BufferedReader(new FileReader(filePath));
  String s = null;
  List<String> list = new ArrayList<>();
  /**
   * log.txt 文件内容格式如下
   * commit c26785080088ff3f146ab438d02736fc90ebb903  当前commit hash 一般处理时取前7位
   * Merge: b39b38a 9747c96  合并的commit hash  需注意 rebase 是无法变更merge的
   * Author: name <email>  本次提交的author信息
   * Date:   Tue Dec 14 11:54:41 2021 +0800  提交时间
   *      本次提交描述
   *      
   * 提取日志信息
   */
  while ((s = reader.readLine()) != null) {
    if (s.startsWith("Author:")) {
      list.add(s.replaceFirst("Author:", "").trim());
    } else if (s.startsWith("Date:")) {
      list.add(dateConvert(s.replaceFirst("Date:", "").trim()));
    } else if (s.startsWith("Merge:")) {
      list.add(s);
    } else if (s.startsWith("commit ")) {
      list.add(s.substring(0, 14));
    }
  }
  reader.close();

  String jb = "GIT_COMMITTER_DATE='%s' GIT_AUTHOR_DATE='%s' git commit --amend --no-edit --date '%s' --author=\"%s\"";

  for (int i = 0, len = list.size(); i < len; i += 3) {
    // 因 rebase 无法变基到merge commit,但导出的log.txt中会包含 merge commit 所以需要剔除  剔除后生成commit 脚本文件即可
    if (list.get(i + 1).startsWith("Merge:")) {
      i += 1;
      continue;
    }
    if (i + 2 >= len) {
      continue;
    }
    String dateStr = list.get(i + 2), authorStr = list.get(i + 1);

    String[] authors = authorStr.split(" <");

    // 创建文件
    File temp = new File(dirPath, list.get(i).replaceFirst("commit ", "") + ".sh");
    BufferedWriter bw = new BufferedWriter(new FileWriter(temp));
    bw.write("#!/bin/bash");
    bw.newLine();
    // /C/Users/hong/Desktop/rebasejs.sh 上面提到的通用脚本
    bw.write("/C/Users/hong/Desktop/rebasejs.sh c '" + authors[0] + "' '" + authors[1].substring(0, authors[1].length() - 1) + "' '" + dateStr + "'");
    bw.newLine();  
    // 关闭流
    bw.close();
  }
}

@SneakyThrows
public String dateConvert(String dateStr) {
  // commit 时间格式处理
  SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy z", Locale.US);
  Date d = sdf.parse(dateStr);
  Instant instant = d.toInstant();
  ZoneId zone = ZoneId.systemDefault();
  LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
  // 在某一些时间之前 提交时间都往前推
  if (localDateTime.isBefore(DateUtil.parseDefault(cutOffTime))) {
    localDateTime = localDateTime.minusDays(minusDays);
  }
  return DateUtil.formatDefault(localDateTime);
}

通用脚本

#!/bin/bash
# git rebase 变基使用的脚本 首次调用可不传入参数
# 参数1 当值为 c 时 会自动判断当前变基执行到哪次commit
# 参数2 姓名
# 参数3 邮箱
# 参数4 时间

# 生成的每次commit 脚本文件夹
commit_sh_path=/C/Users/hong/Desktop/gitsh/
# 原本地环境的 author 信息
my_email=xxxx@xxx.com
my_name=xxxx

# 通用处理方法
rebaseCommit() {
    # 临时设置 author 信息为原本次commit提交的author信息
    git config user.name "$1"
    git config user.email "$2"
    # 修改日志信息以及作者信息
    GIT_COMMITTER_DATE="$3" GIT_AUTHOR_DATE="$3" git commit --amend --no-edit --date "$3" --author="$1 <$2>"
    git rebase --continue
    if [ $? -ne 0 ]
    then 
        echo '执行失败,开始自动合并'
        conflict
        exit 1
    fi
}
# 冲突自动处理
conflict() {
  # 通过 git status 命令可以获得当前 冲突情况
  status_res=`git status`
  status_res=`echo $status_res | grep -Eo "Unmerged path[s]?:.*[\\.][a-z0-9A-Z]+" | grep -Eo "(both (modified|deleted|added)|(deleted|added) by (us|them)):\s+[a-z0-9A-Z\\/\\.]+" | tail -n 1`
  if [ -z "$status_res" ];
  then
    echo "合并结束,继续下一个commit"
    checkOver
    exit 1
  fi
  # 获取冲突类型  冲突文件名  them表示正要合并的  us表示目前的
  type_res=`echo $status_res | grep -Eo "(modified|deleted|added)"`
  file_name=`echo $status_res | grep -Eo "[a-z0-9A-Z\\/\\.]+" | tail -n 1`
  obj_type=`echo $status_res | grep -Eo "(us|them)" | head -n 1`

  # 冲突类型分为 both (modified|deleted|added) 和 (modified|deleted|added) by (us|them) 
  # 目前这几种情况 (modified|deleted|added) by us没碰到过  
  # 要是各位大佬有碰到的话 可以把 git status 信息发给我参考完善下
  if [ "$type_res" == "modified" ];
  then
    if [ "$obj_type" == "us" ];
    then
      obj_type="ours"
      # 目前还没碰到当前情况 因而不确定合并方式 碰到会自动提前结束脚本
      echo "发现us: $status_res"
      exit 1
    else
      obj_type="theirs"
    fi
    # 冲突合并方式  一般合并 theirs
    git checkout --${obj_type} $file_name
    git add $file_name
  elif [ "$type_res" == "deleted" ];
  then
    # 冲突是删除文件时使用
    git rm $file_name
  elif [ "$type_res" == "added" ];
  then
    git add $file_name
  else
    echo "无匹配操作: $status_res"
    exit 1
  fi
    # 遍历 查看是否还有冲突
    conflict
}
# 检查是否还有需要处理的提交记录
checkOver() {
    # 查看是否存在需要执行的 commit 
    status_res=`git status`
    status_res=`echo $status_res | grep -Eo "Last command[s]? done.*(Next|No) command" | grep -Eo "edit [a-z0-9]{7}" | grep -Eo "[a-z0-9]{7}$" | tail -n 1`
    if [ ! $status_res ];
    then
        echo "执行完成"
        exit 1
    fi
    echo "执行$status_res"
    $commit_sh_path$status_res.sh
    # 删除已执行文件,方便后续执行完之后查看是否有漏的commit
    rm -rf $commit_sh_path$status_res.sh
}
if [ "$1" == "c" ];
then
  # 修改的commit是别人提交时,rebase 会导致出现两个author信息,因而需先取消全局设置的git author信息,等处理完了之后再重新设置
    git config --global --unset user.name
    git config --global --unset user.email
    rebaseCommit "$2" "$3" "$4"
    git config --global user.email "$my_email"
    git config --global user.name "$my_name"
fi

checkOver

生成的脚本文件

#!/bin/bash
# 通用脚本 参数1固定  参数2表示author名 3表示author邮箱 4表示要改为哪个时间
/D/rebasejs.sh 'c' 'xx' 'xxx@xx.com' '2019-06-30 15:32:04'

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

coolinmind 发表于 2022-4-2 22:00
感谢分享,这个实用
who4who 发表于 2024-3-14 16:39
感谢,其中commit可以使用--date和--author指定信息,这正是我需要的知识点。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-27 20:28

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表