首页 > 解决方案 > git - 如何仅合并特定文件

问题描述

我有三个提交 ID commit1commit2commit3.

commit3是我们 repo 中的一个提交,而另外两个是上游的提交——一个commit1是旧的稳定版本,commit2一个是新的稳定版本。

我正在尝试获取自旧的稳定上游合并以来我们更改的文件列表,然后将这些文件合并到新的稳定版本(commit2)。

第一部分很简单,git diff --name-only不包括我们从未接触过的文件夹列表;假设我得到了 100 个文件的列表。

现在我将我们的提交合并到新版本 - 合并commit3commit2. 此时有 1000 个文件受此合并影响,但我知道它应该只有 100 个文件,因此,理论上,我可以还原不在我获得的 100 个文件的初始列表中的文件的更改git diff

重置它们的最佳方法是什么?我可以运行git ls-files以获取 repo 中的所有文件,然后遍历此列表,检查该文件是否不在先前检索到的 100 个受影响文件的列表中,并且git reset --hard -- <filespec>.

我也试过了git checkout commit2 <filespec>。在这两种情况下,我都以某种方式留下了 500 个受影响的文件,但我希望只有 100 个或更少的文件。我做错了什么?丢弃合并对除 100 之外的所有文件所做的任何更改的正确方法是什么?

EDIT001:添加一些代码来展示我如何尝试解决上述问题可能是个好主意

#!/usr/bin/env python3
import os
import subprocess
import sys


def execute_git_command(*args):
    arguments = ['git']
    for arg in args:
        arguments.append(arg)

    git_command = subprocess.Popen(arguments, stdout=PIPE, stderr=PIPE)
    git_stdout, git_stderr = git_command.communicate()
    if git_command.returncode != 0:
        sys.stderr.write(
            'Git command "{0}" failed with code {2}. Reason: {1}'.format(' '.join(arguments), git_stderr.decode(),
                                                                         git_command.returncode))
        exit()
    return git_stdout.decode()


def just_execute_git_command(*args):
    arguments = ['git']
    for arg in args:
        arguments.append(arg)

    git_command = subprocess.Popen(arguments, stdout=PIPE, stderr=PIPE)
    git_stdout, git_stderr = git_command.communicate()
    return git_stdout.decode()


if len(sys.argv) != 4:
    print("Usage: {0} previous_upstream_commit new_upstream_commit our_commit_for_merge".format(sys.argv[0]))
    exit()

old_upstream_commit = sys.argv[1]
new_upstream_commit = sys.argv[2]
our_commit_to_merge = sys.argv[3]
PIPE = subprocess.PIPE

print("Trying to merge commit id {0} to {1}".format(our_commit_to_merge, new_upstream_commit))

changed_by_us = execute_git_command('diff',
                                    '--name-only',
                                    old_upstream_commit + ".." + our_commit_to_merge,
                                    '--',
                                    './',
                                    ':(exclude)./some/folders/to/exclude/*')

affected_files = set()
for line in changed_by_us.splitlines():
    affected_files.add(line)
print("{0} files affected by our changes".format(len(affected_files)))

print("Checkingout commit id " + new_upstream_commit)
execute_git_command('checkout', new_upstream_commit)
execute_git_command('submodule', 'sync', '--recursive')
execute_git_command('submodule', 'update', '--init', '--recursive')

print("Creating new branch for it upstream_merge_" + new_upstream_commit)
just_execute_git_command('checkout', '-b', "upstream_merge_" + new_upstream_commit)
just_execute_git_command('merge', our_commit_to_merge)

gitBranch = execute_git_command('branch', '--show-current')
print("Commit {0} merged to branch {1}".format(our_commit_to_merge, gitBranch))

mergeAffectedFiles = execute_git_command('ls-files')

if len(mergeAffectedFiles.splitlines()) > 0:
    print("Will checkout files not affected by ours changes in upstream favor")

    for full_path in mergeAffectedFiles.splitlines():
        # print(conflictingFile)
        # full_path = conflictingFile
        # filename = os.path.basename(full_path)
        if full_path not in affected_files:
            just_execute_git_command('reset', '--hard', '--', full_path)

    mergeAffectedFiles = execute_git_command('ls-files', '--unmerged')

print("{0} conflicting files left for manual resolution.".format(len(mergeAffectedFiles.splitlines())))

免责声明,我不熟悉python :)

EDIT002:再想一想,如果我对所有这 100 个文件进行虚假提交,然后将其挑选到新的稳定版本 (commit2) 会怎样。技术上可行吗?

标签: git

解决方案


一种可能性是:从commit3,构建一个新分支,其中只包含您想要集成到的更改commit2

该建议与@LasseVKarlsen 的评论方向相同:

你没有合并文件,这是合并分支的副产品,你正在合并分支。

这是一种使用要合并的内容构建分支并合并该分支的方法。

该分支将commit2在同一点分叉commit3git merge-base commit2 commit3


以下是如何执行此操作的概述:

# 1. from commit3, create a branch 'wip3' :
git checkout -b wip3 commit3

# 2. reset that branch to the merge base :
git reset --soft $(git merge-base commit2 commit3)

# now all modifications are stored in the index :
# 3. discard all the modifications you do not want to keep

# by hand :
git reset that/directory
git reset that/other/directory
git reset that/file
...

# or using the list you got from `git diff commit1 commit3` :
git diff --cached --name-only | grep -v -f filelist.txt | xargs git reset

# 4. create a commit on branch wip3 :
git commit

# 5. you can now merge wip3 into commit2 :
git checkout commit2
git merge wip3

注意3. 4.:您可以多次运行步骤:在分支上wip3时,您始终可以恢复或重新应用对某些文件的修改和commit --amend.


推荐阅读