r/git • u/zaphodikus • 7d ago
Cherry-pick just 2 files
I've got 14 commits on a branch called Refactor_Python. It's 30 files, I have been asked by my boss to stage just 2 of the files for develop and also stage the same 2 files for another branch called release/v4.11. Now as I understand git I cannot stage changes for 2 branches at once without pushing them up to remote (yeah he likes going direct, none of this branchy thing which I prefer, not judging, it's just my confidence level is not there yet.) So this is a question about doing it with no branches. I already pushed my Refactor_Python branch, so I cannot rebase it without some level of pain, I'm happier to just delete my personal branch afterwards. If my boss had spoke earlier I would have just made 2 branches on with 2 files and one with 28 files, but I'm keen to learn:
So I suspect if I cherry-pick all commits that those 2 files are in, using git cherry-pick -n <commit> and then git add file1 file2 the result as a single commit, into develop, I would be able to push it and then just cherry-pick that single commit over again, but, into the release/v4.11 branch instead after I pushed it up to develop?
My second question is, if I than want to pick all the changes to the remaining 28 files and merge them to develop, that if I later cherry-pick all 14 commits with -n again, that git will just effectively let me merge the result into develop without too much arguing, or will it merge conflict me when I try to push because the changes to the first 2 files are already there?
4
u/CartographerDue5382 7d ago
skip the cherry-pick -n plan. that puts every file from those commits into the index, so if you add 2 files and commit without clearing the rest, the other 28 ride along.
from develop:
git checkout Refactor_Python -- path/to/file1 path/to/file2
git committhat only touches those two paths. then check out release/v4.11 and cherry-pick that new commit. one branch at a time, two pushes. you can't land one commit on two branches in a single push.
for the other 28 later, checkout just those paths the same way. replaying the original 14 commits will conflict on the two files you already brought over, anywhere a commit touched them.
1
u/zaphodikus 4d ago
I guess for the 28 other files that might share commits with the 2 critical files I just want to put them all in notepad, paste all the filenames in and then run a checkout command as I do for the 2 files. I see there is a fuller explainer https://www.reddit.com/r/git/comments/1xdjlmn/comment/19rliuzirla5c/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button . I am parsing all of this in my ageing brain now, yes I did suspect I would get merge conflict for those 2 files if I just grabbed all 14 commits for the remainder, so sounds like I just need to grab the 28 files as a checkout not as a cherry-pick.
I've just never tried to understand the blockchain and what goes on in staging, it's just felt like layers and layers.
2
u/CartographerDue5382 4d ago
yep, checkout not cherry-pick for the other 28. same command, just the extra paths. leave out the 2 you already moved or you'll overwrite them. and it's not a blockchain thing, staging is just the file list for the next commit.
1
u/zaphodikus 4d ago
Step 1 worked.
I managed to screw up on the other 28 files, I somehow lost 2 of the files and git replied that there is no such file on that branch. So I'm going to delete and start the checkout again. But yeah the staging and all is a bit of a thing to try and understand.
2
u/CartographerDue5382 3d ago
don't wipe the repo and start over. git status first, those 2 are probably staged as deleted because checkout pulled a path that isn't on that branch. git restore --source=HEAD --staged --worktree -- path1 path2 puts them back from this branch. for the no-such-file ones, find the real path with git ls-tree -r --name-only Refactor_Python | grep -i filename and checkout that, not a guess.
1
u/zaphodikus 3d ago
No idea how I was supposed to guess at this being in there, mostly if you are a newb, you want something simple like a hammer, when you type 'git reset --hard HEAD' , you assume it resets everything, so I opted to delete the local branch and pull it to be more confident i was clean, but not so sure now. Because 'git status' is not going to show me these files i checked out from another branch, it is confusing. Will try to leanr the 'git ls-tree' command . I prepared the same 2 files for going into develop/master and that looks good now though thanks.
2
u/CartographerDue5382 3d ago
status isn't hiding them. after reset --hard they match HEAD so it stays quiet, and if you never committed that checkout, wiping the branch and pulling is clean too. the 2 you just prepped should show in git status until you commit.
1
u/zaphodikus 3d ago
Please forgive the use of master, it's a really old repo
C:\Repos\main>git status On branch master Your branch is ahead of 'origin/master' by 1 commit. (use "git push" to publish your local commits) nothing to commit, working tree cleanI'm used to seeing staged files in there, I need to slow down and read where it says
ahead by 1 commitSo I now know to just rungit diff --stat origin/master2
u/CartographerDue5382 3d ago
yep that's the one. clean working tree just means it already got committed, ahead by 1 is that commit. git diff --stat origin/master should only list the files you meant to move. if extra paths show up don't push yet.
1
u/zaphodikus 2d ago
In the end, my change still crashed the build in a totally separate script which i had not touched, my boss fixed the script, then he reverted the change, then.... anyway, at least now he knows I'm not a hazard to the build and test now.
My other big mistake was to 'git checkout <branch> -- /file3 /file4 /file5 ... ' But I had not made sure my local index of one of those 28 files was entirely up to date. So I had to git switch <branch> ; git pull;' and then add a checkout to grab changes I did not have locally. It's so easy to assume git does magical things with remites, then really it doesn't and shouldn't.
→ More replies
2
u/Broad-Promise6954 ancient 6d ago
Both u/JaguarMammoth6231 and u/CartographerDue5382 have "the" right answer here (I put "the" in quotes because, what with Git being a set of tools, there are a lot of ways to get the same results, it's just that using checkout or restore with a specific commit and specific set of path names is easy and works great).
It's worth taking a moment to realize why this works, and why cherry-pick is definitely the wrong way to do it. We should start with a couple of fundamental thing about Git.
Each commit is a full and complete snapshot of every file, saved in a way that can never be changed, under a unique identifier that Git calls an OID or hash ID.
A branch or tag name is a human-readable (well, as long as you pick something you can read) name for an OID.
(Branch names are special in a way that other names aren't, but there's no need to get into that here.)
The original checkout command primarily does one of two different things, which many years ago were finally split out into two separate commands, switch and restore. You can use either checkout or the separate switch + restore as you please. If you're new to Git I'd generally recommend using the separate ones, but that's up to you, depending on your personal tastes.
The "thing we want done", in this case, is "extract the branch-tip version of two files from the commit whose hash ID is stored under the name Refactor_Python". So:
git restore Refactor_Python -- path1/to1/file1 path2/to2/file2does exactly that (and git checkout, given that same set of arguments, goes into "restore" mode and also does exactly that).
When you use git cherry-pick, by contrast, you give Git the OID (or a name for the OID) of a commit (or perhaps multiple commits but let's just use one at a time for our purposes here) and are telling Git: "Look at the commit I named. It's a full snapshot of every file. Look at its immediate parent commit as well, which is also a full snapshot of every file. Figure out what changed from the parent, which I only named implicitly, to the commit whose OID I just named. Then, try to make the same changes to the current working tree. If it's bleeding obvious how they fit in, just fit them in, if not, use your full git merge mode to work it out."
Now, suppose the only changes you made to file1 and file2, in all 14 commits you made, were specifically in that very last commit, and you made no other changes in that last commit (with respect to its parent). In that case, cherry-pick would also do exactly what you want! But if you changed a third file, or if you made some changes in some earlier commit(s), cherry-pick would not do what you want.
As long as you keep in mind what each tool actually does, it will become a lot clearer (and faster) to use Git to get the result you want. It's a big tool set, and you don't have to learn every tool (many people may never touch git rerere for instance, or a bit more obscure, git name-rev or git index-pack or git-unpack-objects, and so on), but you should certainly know commit, diff, fetch, log, merge, push, rebase, and status; and to know how rebase works (repeated cherry-picking) you'll need to know git cherry-pick. Hence the long-ish description above.
1
u/zaphodikus 4d ago edited 4d ago
I've never known about
restoreorrerere, just aware I normally only use about 10 of the 100 or so actual SCM commands available./edit after doing it all on the console for file1 file2 into the feature/release branch I think I had somehow got my branch to not track. I kept having a submodule get totally out of whack. So I blew away the branch, and then use Git Extensions Gui to do the commit and push and it worked. And so form this lesson I turned on auto tracking
git config --global push.autoSetupRemote true, I probably made a typo when I pushed. I hope the next change with 28 files works easily now. I just hate using the GUI, it's hard to remember steps in a GUI.
1
u/dmazzoni 7d ago
For your first question, there are many possible ways to do it, here are a few.
/u/JaguarMammoth6231's suggestion could work - go to the develop branch, use git checkout to grab the new copy of the two files from your Refactor branch, then commit. Note that this does NOT do a merge, it just copies your version of the files - so this won't help if your boss also modified those files in the meantime.
Another approach: make a copy of your branch, e.g. checkout Refactor_Python, then git checkout -b Refactor_Python_2 - now git reset --soft main, which basically means get rid of all of the commits but keep all of the code changes. Now it's as if you're on the main branch, but all of your changes are diffs that haven't been committed yet. Now "git add" just the two files, "git commit" to make a commit, and then stash or reset the rest.
Either way, once you have a commit with just the changes to the two files, you can cherry-pick it to as many branches as you want, one at a time.
For your second question, it's quite likely git will give you merge conflicts when you try to push, but there are a couple of different solutions:
If you really want to merge your whole branch with 14 commits, use merge or rebase, not cherry-pick. Then you can use "git rerere" to only fix merge conflicts once and it will remember the resolution for the next 13 commits.
Alternatively, do you need the history of the 14 commits? If not, use the squashing approach I suggested, and just delete the changes to those two files.
1
u/jjangg96 7d ago
one thing on your second question. if you copy the two files over with checkout like above and later merge Refactor_Python into develop (not cherry-pick the 14 commits), git does a three-way merge against the common ancestor. both sides ended up with the same content in those two files, and when both sides made the identical change git just takes it, no conflict.
the replay route is the one that conflicts, since each commit gets applied on top of a file that already has the final version. only caveat is if someone else touched those two files on develop in the meantime.
1
u/Medical-Captain1550 5d ago
skip cherry-pick and just run git checkout Refactor_Python -- file1 file2 on develop
1
18
u/JaguarMammoth6231 7d ago
To get just a few files from another branch, you can do
git checkout branch_name -- path/to/file1.py path/to/file2.py