问题
I have three commit ids, commit1, commit2 and commit3.
commit3 is a commit in our repo, while the other two are commits in upstream - the commit1 is the old stable release, and the commit2 is a new stable.
I'm trying to get a list of files that we changed since the old stable upstream merge and then merge these files to the new stable release (commit2).
The first part is easy, git diff --name-only excluding a list of folders we never touch; let's say I get a list of 100 files.
Now I'm merging our commit to the new release - merge commit3 to commit2. At this point there are 1000 files affected by this merge, but I know it should be only 100 files, so, theoretically, I can revert the changes on the files that are not in the initial list of 100 files I got with git diff.
What is the best way to reset them? I can run git ls-files to get all files in repo and then I iterate over this list, checking that the file NOT in the list of 100 affected files retrieved previously, and git reset --hard -- <filespec>.
I also tried git checkout commit2 <filespec>. In both cases I somehow left with, lets say, 500 affected files, but I expect to be with only 100 files or less. What I'm doing wrong? What is the right way to discard any change made by the merge to all files excluding the 100?
EDIT001: It may be a good idea to add some code to show how I'm trying to solve the aforementioned problem
#!/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())))
Disclaimer, I'm not familiar with python :)
EDIT002: Second thoughts, what if I compose a fake commit of all these 100 files and then cherry-pich it to the new stable release (commit2). Is it technically possible?
回答1:
One possibility is : from commit3, build a new branch, which contains only the changes you want to integrate to commit2.
This suggestion goes in the same direction as @LasseVKarlsen's comment :
you're not merging files, that is a byproduct of merging branches, you're merging branches.
It is a way to build a branch with the content you want to merge, and merge that branch.
This branch would fork off commit2 at the same point as commit3 : git merge-base commit2 commit3
Here is an outline of how to do this :
# 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
note : you can run steps 3. 4. several times : while on branch wip3, you can always revert or reapply modifications on some files and commit --amend.
来源:https://stackoverflow.com/questions/65701154/git-how-to-merge-only-specific-files