Search code examples
gitmergetfstfvccherry-pick

TFS/TFVC merge selected changeset vs git cherry-pick between branches


We will be migrating our TFS/TFVC repository to Git. In TFVC we used to have a trunk based development with long lasting release maintenance branches. Bug fixes on release branches have to be merged back to the trunk. Sometimes smaller features have to be carried over from trunk to a release branch. In TFVC we did this by "merging" individual (or small groups of) changesets from one branch to the other. The resulting changesets were marked as "merge", although I don't exactly know what that implies for TFVC, especially considering future merge operations.

So I imagine the branch graph to look something like this: (Although note that TFVC never displays any graphs)

-A--B--C---D--E--F---    trunk
  \       /    \
   G--H--I--J---K--L-    release-x

(Here the release branch has been created from A, I->D is a bugfix merge, E->K is a feature-forward) But maybe I'm wrong. In this case could someone explain what a TFVC merge changeset really does?

I've been told that an equivalent way of doing in Git is cherry-picking individual commits. However, on the resulting branch graphs, I don't see any link between the branches following a cherry-pick commit. I am aware that cherry-picking is not technically a merge operation and thus history relation between the branches is not carried over. Is there something I am missing? Is there a better way of carrying over such small commits from one branch to another, yet still retain some merge information? I DO NOT want to merge the entire branch. As for example changesets B,C or H must remain isolated from each other branch.


Solution

  • When Cherry picking you can add the -x option to have git add a message to the commit-message of each cherry-picked commit:

    -x When recording the commit, append a line that says "(cherry picked from commit …​)" to the original commit message in order to indicate which commit this change was cherry-picked from. This is done only for cherry picks without conflicts. Do not use this option if you are cherry-picking from your private branch because the information is useless to the recipient. If on the other hand you are cherry-picking between two publicly visible branches (e.g. backporting a fix to a maintenance branch for an older release from a development branch), adding this information can be useful.

    This is the closest you'll get. A cherry picked commit it recorded as a new commit on the target branch, not as a reverse or forward integration.

    Git doesn't do "partial merges" since git stores the status of the worktree as a "version". TFVC stores the workspace versions of individual files instead.

    What you're seeing are 2 very different things.

    In TFVC you see a branch graph that is clearly defined through branch objects that have a defined parent branch. Each integration between those branches is visualized, since the merge stores which files were merged, which changesets were part of that merge and over which branch relationship these were merged. A changeset in TFVC (closest thing to a commit in git) is also a very different thing. For one, a changeset can span multiple branches, even repositories, and a changeset contains 1 or more changes to files.

    In Git a branch is just a special pointer to a commit somewhere in the commit graph. Parentage is calculated (based on the commit graph) and branches can freely merge without respecting a preconfigured hierarchy. The visualization shows the relationships between commits, not branches. A special type of commit, the merge, stores when a new commit was created from the basis of 2 or more other commits. The relation to these commits is stored, not the names of the branches that happened to point to these commits. A Commit stores the changes to the worktree. One or more branches may point to that commit.

    Since a cherry-pick replays the changes on one branch in the commit tree onto another, it's not recorded as a merge where multiple sets of commits are linked together. Instead, it's recorded as a new commit that happens to have the same diff (and potentially not even that if you needed to do conflict resolution).

    By adding the -x flag the relationship is stored in the comments.