Search code examples
gitgithubversion-controlgit-workflow

Creating a new commits out of picking and excluding files from existing commits?


At a workplace, the team does their work in their own feature branch, merge the features in develop branch and then finally do a cherry-pick of the commits into production branch. The server serves the files from production branch. This is to say, whatever in the production branch is what is shown on the public website. In other words, instead of uploading the files through FTP, the team cherry picks the commits from their feature branch containing the new files to the production branch and subsequently do a git pull origin production on the web server to update the files.

I'm not familiar with a workflow like this because I usually upload through FTP and I've all the freedom to upload only the files needed. I was wondering, in a workflow like this, is it possible to recreate new commits containing selected files based on existing commits?

As an example, let's say I've made 2 commits like so (Commit A comes before Commit B) in MyFeatureBranch:

Commit B:
Removed 1 line from FileA.
Added 2 lines to FileB.
Deleted FileC
Added 1 line to package.json

Commit A:
Added 2 lines to FileA.
Removed 5 lines from FileB.
Created package.json.

How can I create a Commit C on top of Commit B containing the combined changes in Commit A and Commit B of the files FileA and FileB but excludes the file package.json (or even more files to exclude)?

I cannot use gitignore because I still need to push that updated package.json into develop branch.

My intention to have a Commit C like this is so that I can easily do a git cherry-pick CommitC from MyFeatureBranch into production branch, and have all my updates (wanted and unwanted files) now pushed into production. I might be able to cherry-pick the commits one by one, but package.json will still be copied into production branch because it is inside Commit A.

So, is it possible to create a new commit out of selected files and exclude files from existing commits?


Solution

  • First of all: Using cherry-picking the way you described it does not seem to be a good workflow. This will make work with git a lot harder than required, as there are multiple branches containing mostly the same code but missing a mutual history. It might be a good idea to revise that workflow and solve the problems worked around by the cherry-picks in an easier way.

    You should also check with the team already following this workflow how they solve the described problem. Maybe some workflow exists which avoids to do this workarounds and you just do not know it?


    This is the way I would recommend to you to produce Commit C if you really want it, as this process is fairly easy to follow while still keeping a clean history for the files. Below you can find hints for some alternatives which might work too.

    Checkout a new branch from the parent commit of Commit A. You can find that commit either using git log or just use A^ (where A is the hash of Commit A), which will always reference the parent commit of A (assuming that A is not a merge commit which also wouldn't work with the cherry-picking process).

    git checkout -b cherry-pick-preparation A^
    

    cherry-pick-preparation is the name of a new branch used to prepare for the cherry-pick. You need to either delete this after cherry-picking the commit or use another name the next time. It is also possible to do all this without creating a new branch, however I would avoid that to make sure you can come back to your work if you make errors.

    Now pick all the changes from your branch without committing them. B can be either the hash of the last commit or simply the name of your branch.

    git checkout B -- .
    git reset HEAD .
    

    This will set all your files to the same content as they are in B. You can have a look at that using git status and git diff. You can now undo everything you don't want to cherry-pick later on. If you need to reset complete files you might want to do git checkout -- file1 file2 file3 to reset those files. When you are done commit the changes. The resulting commit can be cherry-picked anywhere else.


    You also could have a look at Interactive Rebasing which would allow you to change the history of your branch without creating a new branch. You might want to "edit" to change your commits and remove the changes you do not want to cherry-pick and "squash" to unite your commits into a single one.


    You could also add Commit C on top of A and B which just undoes the changes you do not want in production and afterwards cherry-pick all three commits. The resulting files would be the same as before - but in the history changes to package.json are visible. Most probably this is not a good idea as it is very hard to follow the history of the files afterwards.