Is git-svn dcommit after merging in git dangerous?

36,795

Solution 1

Actually, I found an even better way with the --no-ff option on git merge. All this squash technic I used before is no longer required.

My new workflow is now as follows:

  • I have a "master" branch that is the only branch that I dcommit from and that clone the SVN repository (-s assume you have a standard SVN layout in the repository trunk/, branches/, and tags/):

    git svn clone [-s] <svn-url>
    
  • I work on a local branch "work" (-b creates the branch "work")

    git checkout -b work
    
  • commit locally into the "work" branch (-s to sign-off your commit message). In the sequel, I assume you made 3 local commits

    ...
    (work)$> git commit -s -m "msg 1"
    ...
    (work)$> git commit -s -m "msg 2"
    ...
    (work)$> git commit -s -m "msg 3"
    

Now you want to commit onto the SVN server

  • [Eventually] stash the modifications you don't want to see committed on the SVN server (often you commented some code in the main file just because you want to accelerate the compilation and focus on a given feature)

    (work)$> git stash
    
  • rebase the master branch with the SVN repository (to update from the SVN server)

    (work)$> git checkout master
    (master)$> git svn rebase
    
  • go back to the work branch and rebase with master

    (master)$> git checkout work
    (work)$> git rebase master
    
  • Ensure everything is fine using, for instance:

    (work)$> git log --graph --oneline --decorate
    
  • Now it's time to merge all three commits from the "work" branch into "master" using this wonderful --no-ff option

    (work)$> git checkout master
    (master)$> git merge --no-ff work
    
  • You can notice the status of the logs:

    (master)$> git log --graph --oneline --decorate
    * 56a779b (work, master) Merge branch 'work'
    |\  
    | * af6f7ae msg 3
    | * 8750643 msg 2
    | * 08464ae msg 1
    |/  
    * 21e20fa (git-svn) last svn commit
    
  • Now you probably want to edit (amend) the last commit for your SVN dudes (otherwise they will only see a single commit with the message "Merge branch 'work'"

    (master)$> git commit --amend
    
  • Finally commit on the SVN server

    (master)$> git svn dcommit
    
  • Go back to work and eventually recover your stashed files:

    (master)$> git checkout work
    (work)$> git stash pop
    

Solution 2

Creating local branches is definitely possible with git-svn. As long as you're just using local branches for yourself, and not trying to use git to merge between upstream svn branches, you should be fine.

I have a "master" branch that I use to track the svn server. This is the only branch that I dcommit from. If I'm doing some work, I create a topic branch and work away on it. When I want to commit it, I do the following:

  1. Commit everything to the topic branch
  2. git svn rebase (resolve any conflicts between your work and svn)
  3. git checkout master
  4. git svn rebase (this makes the next step a fast-forward merge, see Aaron's comments below)
  5. git merge topic_branch
  6. resolve any merge conflicts (there shouldn't be any at this point)
  7. git svn dcommit

I also have another situation where I need to maintain some local changes (for debugging) that should never be pushed up to svn. For that, I have the above master branch but also a branch called "work" where I normally do work. Topic branches are branched off work. When I want to commit work there, I checkout master and use cherry-pick to pick the commits from the work branch that I want to commit to svn. This is because I want to avoid committing the three local change commits. Then, I dcommit from the master branch and rebase everything.

It is worthwhile running git svn dcommit -n first to make sure that you are about to commit exactly what you intend to commit. Unlike git, rewriting history in svn is hard!

I feel that there must be a better way to merge the change on a topic branch while skipping those local change commits than using cherry-pick, so if anybody has any ideas they would be welcome.

Solution 3

Simple solution: Remove 'work' branch after merging

Short answer: You can use git however you like (see below for a simple workflow), including merge. Just make sure follow each 'git merge work' with 'git branch -d work' to delete the temporary work branch.

Background explanation: The merge/dcommit problem is that whenever you 'git svn dcommit' a branch, the merge history of that branch is 'flattened': git forgets about all merge operations that went into this branch: Just the file contents is preserved, but the fact that this content (partially) came from a specific other branch is lost. See: Why does git svn dcommit lose the history of merge commits for local branches?

(Note: There is not much that git-svn could do about it: svn simply doesn't understand the much more powerful git merges. So, inside the svn repository this merge information cannot be represented in any way.)

But this is the whole problem. If you delete the 'work' branch after it has been merged into the 'master branch' then your git repository is 100% clean and looks exactly like your svn repository.

My workflow: Of course, I first cloned the remote svn repository into a local git repository (this may take some time):

$> git svn clone <svn-repository-url> <local-directory>

All work then happens inside the "local-directory". Whenever I need to get updates from the server (like 'svn update'), I do:

$> git checkout master
$> git svn rebase

I do all my development work in a separate branch 'work' that is created like this:

$> git checkout -b work

Of course, you can create as many branches for your work as you like and merge and rebase between them as you like (just delete them when you are done with them --- as discussed below). In my normal work, I commit very frequently:

$> git commit -am '-- finished a little piece of work'

The next step (git rebase -i) is optional --- it's just cleaning up the history before archiving it on svn: Once I reached a stable mile stone that I want to share with others, I rewrite the history of this 'work' branch and clean up the commit messages (other developers don't need to see all the little steps and mistakes that I made on the way --- just the result). For this, I do

$> git log

and copy the sha-1 hash of the last commit that is live in the svn repository (as indicated by a git-svn-id). Then I call

$> git rebase -i 74e4068360e34b2ccf0c5869703af458cde0cdcb

Just paste sha-1 hash of our last svn commit instead of mine. You may want to read the documentation with 'git help rebase' for the details. In short: this command first opens an editor presenting your commits ---- just change 'pick' to 'squash' for all those commits that you want to squash with previous commits. Of course, the first line should stay as a 'pick'. In this way, you can condense your many little commits into one or more meaningful units. Save and exit the editor. You will get another editor asking you to rewrite the commit log messages.

In short: After I finish 'code hacking', I massage my 'work' branch until it looks how I want to present it to the other programmers (or how I want to see the work in a few weeks time when I browse history).

In order to push the changes to the svn repository, I do:

$> git checkout master
$> git svn rebase

Now we are back at the old 'master' branch updated with all changes that happened in the mean time in the svn repository (your new changes are hidden in the 'work' branch).

If there are changes that may clash with your new 'work' changes, you have to resolve them locally before you may push your new work (see details further below). Then, we can push our changes to svn:

$> git checkout master
$> git merge work        # (1) merge your 'work' into 'master'
$> git branch -d work    # (2) remove the work branch immediately after merging
$> git svn dcommit       # (3) push your changes to the svn repository

Note 1: The command 'git branch -d work' is quite safe: It only allows you to delete branches that you don't need anymore (because they are already merged into your current branch). If you execute this command by mistake before merging your work with the 'master' branch, you get an error message.

Note 2: Make sure to delete your branch with 'git branch -d work' between merging and dcommit: If you try to delete the branch after dcommit, you get an error message: When you do 'git svn dcommit', git forgets that your branch has been merged with 'master'. You have to remove it with 'git branch -D work' which doesn't do the safety check.

Now, I immediately create a new 'work' branch to avoid accidentally hacking on the 'master' branch:

$> git checkout -b work
$> git branch            # show my branches:
  master
* work

Integrating your 'work' with changes on svn: Here is what I do when 'git svn rebase' reveals that others changed the svn repository while I was working on my 'work' branch:

$> git checkout master
$> git svn rebase              # 'svn pull' changes
$> git checkout work           # go to my work
$> git checkout -b integration # make a copy of the branch
$> git merge master            # integrate my changes with theirs
$> ... check/fix/debug ...
$> ... rewrite history with rebase -i if needed

$> git checkout master         # try again to push my changes
$> git svn rebase              # hopefully no further changes to merge
$> git merge integration       # (1) merge your work with theirs
$> git branch -d work          # (2) remove branches that are merged
$> git branch -d integration   # (2) remove branches that are merged
$> git svn dcommit             # (3) push your changes to the svn repository

More powerful solutions exist: The presented workflow is simplistic: It uses the powers of git only within each round of 'update/hack/dcommit' --- but leaves the long-term project history just as linear as the svn repository. This is ok if you just want to start using git merges in small first steps in a legacy svn project.

When you become more familiar with git merging, feel free to explore other workflows: If you know what you are doing, you can mix git merges with svn merges (Using git-svn (or similar) just to help out with svn merge?)

Solution 4

Greg Hewgill answer on top is not safe! If any new commits appeared on trunk between the two "git svn rebase", the merge will not be fast forward.

It can be ensured by using "--ff-only" flag to the git-merge, but I usually do not run "git svn rebase" in the branch, only "git rebase master" on it (assuming it is only a local branch). Then afterwards a "git merge thebranch" is guaranteed to be fast forward.

Solution 5

A safe way to merge svn branches in git is to use git merge --squash. This will create a single commit and stop for you to add a message.

Let's say you have a topic svn branch, called svn-branch.

git svn fetch
git checkout remotes/trunk -b big-merge
git merge --squash svn-branch

at this point you have all the changes from the svn-branch squashed into one commit waiting in the index

git commit
Share:
36,795
Knut Eldhuset
Author by

Knut Eldhuset

Updated on July 08, 2022

Comments

  • Knut Eldhuset
    Knut Eldhuset almost 2 years

    My motivation for trying out git-svn is the effortless merging and branching. Then I noticed that man git-svn(1) says:

    Running git-merge or git-pull is NOT recommended on a branch you plan to dcommit from. Subversion does not represent merges in any reasonable or useful fashion; so users using Subversion cannot see any merges you've made. Furthermore, if you merge or pull from a git branch that is a mirror of an SVN branch, dcommit may commit to the wrong branch.

    Does this mean I cannot create a local branch from svn/trunk (or a branch), hack away, merge back into svn/trunk, then dcommit? I understand that svn users will see the same mess that merges in svn pre 1.5.x have always been, but are there any other drawbacks? That last sentence worries me, too. Do people routinely do these kinds of things?

  • Knut Eldhuset
    Knut Eldhuset over 15 years
    If I understand this correctly, in git, merging git with svn is OK, but not svn with svn? So I can't merge my svn/branch with svn/trunk in git?
  • Aristotle Pagaltzis
    Aristotle Pagaltzis over 15 years
    Rewriting history in SVN is hard if you want to do something trivial. In non-trivial cases it’s practically impossible.
  • Greg Hewgill
    Greg Hewgill over 15 years
    Jegern: That's correct. For merging svn-to-svn, I would recommend using svn itself to do that.
  • Kzqai
    Kzqai over 14 years
    As others have pointed out, though, this does lose the granularity of the commits.
  • Aaron
    Aaron almost 14 years
    Greg Hewgill's answer has a lot of votes, but I believe it's wrong. Merging from topic_branch into master is only safe if it's just a fast-forward merge. If it's a real merge requiring a merge commit, then the merge commit will be lost when you dcommit. The next time you try to merge topic_branch into master, git thinks the first merge never happened, and all hell breaks loose. See the question Why does git svn dcommit lose the history of merge commits for local branches?
  • Aaron
    Aaron almost 14 years
    @Greg Hewgill, the edit isn't clear. You wrote that the rebase "makes the next commit a fast-forward merge." I don't know what that means, because a fast-forward merge doesn't involve a commit, but perhaps you meant "makes the next merge a fast-forward merge". But if that's what you meant, it's not correct. Whether a merge from topic_branch into master is a fast-forward merge or not depends on whether any commits have been made on master since the branch point. When you rebase master, that creates new commits on master, so a subsequent merge is necessarily not a fast-forward merge.
  • Greg Hewgill
    Greg Hewgill almost 14 years
    @Aaron: Yeah I don't know what I was thinking there. I've fixed it again, hopefully that makes sense. One of the troubles is that there's so many ways to do things in Git, that it takes quite some explanation to describe a specific sequence of actions.
  • inger
    inger over 13 years
    I think this statement "as long as you're just using local branches for yourself, and not trying to use git to merge between upstream svn branches, you should be fine" , to me misleadingly suggests that it's not possible and/or useful to use GIT for real SVN merges. IMHO, the opposite is true, see luntain's answer below and mine @ stackoverflow.com/questions/2945842/…
  • João Bragança
    João Bragança over 13 years
    This is EXACTLY the work flow this git n00b was looking for. Create a local branch to fix a bug, do whatever, then send it up to svn with a SINGLE commit. Using the highest rated answer here messed up what I was doing - couldn't git svn rebase without errors.
  • Srikumar
    Srikumar over 13 years
    I too think the "answer" ought to be changed to this one.
  • dpb
    dpb over 13 years
    Would be nice if you could delete your old answer on this question since this way is better. Thanks for adding this!
  • schoetbi
    schoetbi over 13 years
    @Tchalvak: Sometimes you want just that. I have often commits in a feature branch ala "fixed mess from previous commit" and these dead ends i like to hide because of a good reputation:-)
  • Kzqai
    Kzqai over 13 years
    Yeah, though I consider that to be a tightrope walk, since whenever you squash, you also lose the ability to pull apart individual commits later. Just a matter of different choices of committing standards.
  • jemmons
    jemmons about 13 years
    @Greg Hewgil You say "this makes the next step a fast-forward merge", but I'm not sure how that can be if development has continued on master. When you git svn rebase master, it could pull new changes that your topic branch doesn't know about. Thus insuring your merge will be anything BUT a fast-forward. Maybe use --ff-only to be safe? But really you should be --squashing
  • Greg Hewgill
    Greg Hewgill about 13 years
    @jemmons: if there are no new Subversion commits between steps 2 and 4, then step 5 will indeed be a fast-forward merge. If somebody else commits to Subversion between step 2 and 4, then switch back to the topic branch and repeat from step 2.
  • jemmons
    jemmons about 13 years
    Isn't this exactly what the git-svn docs quoted in the op warn against? By running merge with the --no-ff option, you are explicitly creating a merge commit (a commit with two parents) instead of a fast-forward. To ensure all commits in the svn-tracking branch are single-parent commits, all merges must either be fast-forwards (--ff-only can help with this) or, if trunk has changed behind your back, a --squash, right?
  • rescdsk
    rescdsk over 12 years
    This works for a one-off branch that you immediately delete, but git svn dcommit rewrites the git commit that you give it. This means that you lose its other parent, and now your git repo has no record that you ever merged that branch into master. This can also leave your working directory in an inconsistent state.
  • inger
    inger about 12 years
    @schoetbi yes, you sometimes need to cleanup messy history before publishing. This is where 'git rebase -i <trunk>' is a great help: you can join/reorder/remove and even split(!) commits, fixup msg selectively.
  • tekumara
    tekumara almost 12 years
    use git merge --no-ff work -m "commit message" instead of having an extra git commit --amend step
  • tatlar
    tatlar almost 12 years
    Excellent summary of how to make Git and SVN play nicely in the sandbox together without making Git go home crying with sand in its eyes...
  • Moataz Elmasry
    Moataz Elmasry over 10 years
    for me I'd prefer to use "git merge --ff-only work" since I want to preserve all my commits and not just the last one
  • void.pointer
    void.pointer over 10 years
    This seems unnecessarily complex. I've heard of people doing git merge --squash work, why not do this? I can see doing squash commits in the branch before merging if you are creating more than one 'pick' (say you have 8 commits and you are turning each of 4 commits into 1, and merging 2 commits to master). Also when updating my 'work' branch, I do rebase, it's simpler than creating another branch for my branch and doing merges...
  • Dror
    Dror over 9 years
    Is it just me or there's a reason to delete the work branch after the merge and re-branch at the end of the process? stackoverflow.com/q/26511177/671013