ChatGPT解决这个技术问题 Extra ChatGPT

Pushing an existing Git repository to SVN

I've been doing all my work in Git and pushing to GitHub. I've been very happy with both the software and the site, and I have no wish to change my working practices at this point.

My PhD adviser is asking all students to keep their work in an SVN repository that's hosted at the university. I've found tons of documentation and tutorials about to pull down an existing SVN repository into Git, but nothing about pushing a Git repository to a fresh SVN repository. I expect there must be some way to do this with a combination of git-svn and a fresh branch and rebasing and all those wonderful terms, but I'm a Git newbie and don't feel confident with any of them.

I then want to just run a couple of commands to push commits to that SVN repository when I choose. I wish to keep using Git and just have the SVN repository mirror what's in Git.

I'll be the only person ever committing to SVN, if this makes any difference.

Of note: You will probably lose your original date-stamps when you do this. The new dates will be based on the time of the import to Subversion.

P
Peter Mortensen

I needed this as well, and with the help of Bombe's answer + some fiddling around, I got it working. Here's the recipe:

Import Git -> Subversion

1. cd /path/to/git/localrepo
2. svn mkdir --parents protocol:///path/to/repo/PROJECT/trunk -m "Importing git repo"
3. git svn init protocol:///path/to/repo/PROJECT -s
4. git svn fetch
5. git rebase origin/trunk
5.1.  git status
5.2.  git add (conflicted-files)
5.3.  git rebase --continue
5.4.  (repeat 5.1.)
6. git svn dcommit

After #3 you'll get a cryptic message like this:

Using higher level of URL: protocol:///path/to/repo/PROJECT => protocol:///path/to/repo

Just ignore that.

When you run #5, you might get conflicts. Resolve these by adding files with state "unmerged" and resuming rebase. Eventually, you'll be done; then sync back to the SVN repository, using dcommit. That's all.

Keeping repositories in sync

You can now synchronise from SVN to Git, using the following commands:

git svn fetch
git rebase trunk

And to synchronise from Git to SVN, use:

git svn dcommit

Final note

You might want to try this out on a local copy, before applying to a live repository. You can make a copy of your Git repository to a temporary place; simply use cp -r, as all data is in the repository itself. You can then set up a file-based testing repository, using:

svnadmin create /home/name/tmp/test-repo

And check a working copy out, using:

svn co file:///home/name/tmp/test-repo svn-working-copy

That'll allow you to play around with things before making any lasting changes.

Addendum: If you mess up git svn init

If you accidentally run git svn init with the wrong URL, and you weren't smart enough to take a backup of your work (don't ask ...), you can't just run the same command again. You can however undo the changes by issuing:

rm -rf .git/svn
edit .git/config

And remove the section [svn-remote "svn"] section.

You can then run git svn init anew.


Nice answer. Does this also mess up the commit dates?
Good questions - Unfortunately, I don't know the answer to either. This is more a practical guide of what I found to work. I don't fully understand all the details. Regarding the commit-dates, I guess you could make a test and find out. Remember that you can init a local (fs-based) svn repo, for testing things out.
I've followed these same steps using "git rebase --onto trunk --root" in place of step 5 and had much more success. Only a handful of merge conflicts to resolve, instead of tons.
In my case this sequence didn't worked. Always a message "Unable to determine upstream SVN information from HEAD history" was shown. So, no dcommit possible.
Nevermind, I tried to get smart and omit the -s as I didn't want to follow the SVN standard setup (trunk/ branches/ tags/). Couldn't get it to work at all without that.
B
BuZZ-dEE

Here's how we made it work:

Clone your Git repository somewhere on your machine.

Open .git/config and add the following (from Maintaining a read-only SVN mirror of a Git repository):

[svn-remote "svn"]
    url = https://your.svn.repo
    fetch = :refs/remotes/git-svn

Now, from a console window, type these:

git svn fetch svn
git checkout -b svn git-svn
git merge master

Now, if it breaks here for whatever reason, type these three lines:

git checkout --theirs .
git add .
git commit -m "some message"

And finally, you can commit to SVN:

git svn dcommit

Note: I always scrap that folder afterwards.


+1 this actually worked for me (don't have trunk/base whatever), in contrast to other answers that kept on giving Unable to determine upstream SVN information from HEAD history.
I tried this technique, but it didn't import history. Btw, "git merge master" is now "git merge --allow-unrelated-histories master"
If you absolutely want to overwrite what's in SVN with what's in git, use git merge -s recursive -Xtheirs --allow-unrelated-histories master.
B
Benjol

Using git rebase directly will lose the first commit. Git treats it different and can't rebase it.

There is a procedure that will preserve full history: http://kerneltrap.org/mailarchive/git/2008/10/26/3815034

I will transcribe the solution here, but credits are for Björn.

Initialize git-svn:

git svn init -s --prefix=svn/ https://svn/svn/SANDBOX/warren/test2

The --prefix gives you remote tracking branches like "svn/trunk" which is nice because you don't get ambiguous names if you call your local branch just "trunk" then. And -s is a shortcut for the standard trunk/tags/branches layout.

Fetch the initial stuff from SVN:

git svn fetch

Now look up the hash of your root commit (should show a single commit):

git rev-list --parents master | grep '^.\{40\}$'

Then get the hash of the empty trunk commit:

git rev-parse svn/trunk

Create the graft:

git replace --graft <root-commit-hash> <svn-trunk-commit-hash>

Now, "gitk" should show svn/trunk as the first commit on which your master branch is based.

Make the graft permanent:

git filter-branch -- ^svn/trunk --all

Drop the graft:

git replace -d <root-commit-hash>

gitk should still show svn/trunk in the ancestry of master.

Linearize your history on top of trunk:

git svn rebase

And now "git svn dcommit -n" should tell you that it is going to commit to trunk.

git svn dcommit

Can you explain how this technique is different from above more clearly.
When I try "git rev-parse svn/trunk" it reports unknown revision or path not in the working tree.
This is the only answer that worked for me, except the the steps git filter-branch and drop graft weren't needed: I did a rebase after creating the graft, and then did git svn dcommit.
I think rebase can be used with --root to also manage the first commit so the warning that starts the answer might not be true anymore.
P
Peter Mortensen

Create a new directory in the Subversion repository for your project.

# svn mkdir --parents svn://ip/path/project/trunk

Change to your Git-managed project and initialize git-svn.

# git svn init svn://ip/path/project -s
# git svn fetch

This will create a single commit because your SVN project directory is still empty. Now rebase everything on that commit, git svn dcommit and you should be done. It will seriously mess up your commit dates, though.


I used this answer with the instructions at hassox.blogspot.com/2007/12/using-git-with-svn.html I followed these commands, then "#git branch -a" to see the trunk name. Then: # git checkout -b local-svn trunk # git merge master # git svn dcommit Remember to .gitignore the .svn directory!
As I just did the same operation, I wanted to make it explicit that for some time now (January '09), git can perform rebase operation on the root commit. This makes the process much simpler than a lot of the old articles indicate, see comments on its.arubything.com/2009/1/4/…
What does the "-s" git svn init option do? I can't see this in the man pages for git svn.
Read the man page again, maybe search for “-s” because it is in there. It’s an alias for “--stdlayout”.
P
Peter Mortensen

Git -> SVN with complete commit history

I had a Git project and had to move it to SVN. This is how I made it, keeping the whole commit history. The only thing that gets lost is the original commit time since libSVN will set the local time when we do git svn dcommit.

Howto:

Have a SVN repository where we want to import our stuff to and clone it with git-svn: git svn clone https://path.to/svn/repository repo.git-svn` Go there: cd repo.git-svn Add the remote of the Git repository (in this example I'm using C:/Projects/repo.git). You want to push to SVN and give it the name old-git: git remote add old-git file:///C/Projects/repo.git/ Fetch the information from the master branch from the old-git repository to the current repository: git fetch old-git master Checkout the master branch of the old-git remote into a new branch called old in the current repository: git checkout -b old old-git/master` Rebase to put the HEAD on top of old-git/master. This will maintain all your commits. What this does basically is to take all of your work done in Git and put it on top of the work you are accessing from SVN. git rebase master Now go back to your master branch: git checkout master And you can see that you have a clean commit history. This is what you want to push to SVN. Push your work to SVN: git svn dcommit

That's all. It is very clean, no hacking, and everything works perfectly out of the box. Enjoy.


Similar process also detailed at chani.wordpress.com/2012/01/25/…
Looks like. However, I find her way pretty confusing and mine is much shorter (8 steps than 19/23). Maybe I am not getting her correctly but I think she mixes svn and git commands at her second bullet.
Ah, yes, the difference is that your process imports git to the root of the SVN repo, hers imports it to a subfolder, that's why the few git svn preps are needed.
In step 7, shouldn't there be a git merge old? For me it seems that you are making a dcommit of master that you haven't changed?!
@Alexander using 'git rebase master' we yield a fast-forward merge, that is a linear merge without a merge commit having two parents. We want to have a linear history here.
C
Community

I would propose a very short instruction in 4 commands using SubGit. See this post for details.


P
Peter Mortensen

I needed to commit my existing Git repository to an empty SVN repository.

This is how I managed to do this:

$ git checkout master
$ git branch svn
$ git svn init -s --prefix=svn/ --username <user> https://path.to.repo.com/svn/project/
$ git checkout svn
$ git svn fetch
$ git reset --hard remotes/svn/trunk
$ git merge master
$ git svn dcommit

It worked without problems. I hope this helps someone.

Since I had to authorize myself with a different username to the SVN repository (my origin uses private/public key authentication), I had to use the --username property.


you may need to install git-svn before this is possible, see stackoverflow.com/questions/527037/git-svn-not-a-git-command
P
Peter Mortensen

If you want to keep on working with Git as your main repository and just need to "export" the revisions to SVN from time to time, you could use Tailor to keep the SVN repository in sync. It can copy revisions between different source control systems and would update the SVN with the changes you make in Git.

I haven't tried a Git-to-SVN conversion, but for a SVN -> SVN example see this answer.


P
Peter Mortensen

Yet another sequence that worked (with some comments on each step):

Install git-svn and subversion toolkits: sudo apt-get install git-svn subversion Switch inside the PROJECT_FOLDER cd PROJECT_FOLDER Create the project path on the Subversion server (unfortunately the current git-svn plugin has a defect in comparison with TortoiseSVN). It is unable to store source code directly into the PROJECT_FOLDER. Instead, by default, it will upload all the code into PROJECT_FOLDER/trunk. svn mkdir --parents protocol:///path/to/repo/PROJECT_FOLDER/trunk -m "creating git repo placeholder"

This is the place where trunk at the end of the path is mandatory

Initialize the git-svn plugin context inside the .git folder git svn init -s protocol:///path/to/repo/PROJECT_FOLDER This is the place where trunk at the end of the path is unnecessary Fetch an empty Subversion repository information git svn fetch This step is helping to synchronize the Subversion server with the git-svn plugin. This is the moment when git-svn plugin establishes remotes/origin path and associates it with the trunk subfolder on the server side. Rebase old Git commits happened before the git-svn plugin became involved in the process (this step is optional) git rebase origin/trunk Add new/modified files to commit (this step is regular for Git activities and is optional) git add . Commit freshly added files into the local Git repository (this step is optional and is only applicable if step 7 has been used): git commit -m "Importing Git repository" Pushing all the project changes history into the Subversion server: git svn dcommit


P
Peter Mortensen

You can make a new SVN repository. Export your Git project (fleshing out the .git files). Add it to the SVN repository (initializing the repository with what you had so far in Git). Then use the instructions for importing SVN repositories in a fresh Git project.

But this will lose your previous Git history.


P
Peter Mortensen

If you don't have to use any specific SVN and you are using GitHub you can use their SVN connector.

More information is here: Collaborating on GitHub with Subversion


SVN what? In ("... any specific SVN and ..."). SVN version? SVN client? Or something else?
I
Indrek

I recently had to migrate several Git repositories to SVN, and after trying all solutions I could find, what finally worked for me was Mercurial (yes, using a third VCS). Using this guide, I came up with the following process (on Linux, but the basic idea should work on Windows as well).

The necessary packages: $ sudo apt-get install git subversion mercurial python-subversion Mercurial needs to be configured by adding the following to ~/.hgrc: [extensions] hgext.convert= Create some temporary working directories (I had several repositories to migrate so I created directories for the SVN and Git versions, to keep them separate): $ mkdir svn $ mkdir git Make an empty local SVN repository: $ svnadmin create svn/project Clone the existing Git repository: $ git clone server/path/project.git git/project Let Mercurial do its thing: $ hg convert --dest-type svn git/project svn/project Now the SVN repository should contain the full commit history, but not with original timestamps. If this is not an issue, skip over the next part to step 11. With a little work, the date and time of each commit can be changed. Since my repositories are fairly small, it was feasible for me to do it manually. First, create a pre-revprop-change hook in the SVN repository with the following contents, to allow the necessary property to be modified: #!/bin/bash exit 0; This script has to be made executable: $ chmod +x svn/project/hooks/pre-revprop-change Mercurial created a working copy of the SVN repository, named project-wc, so switch to it and edit the commit times: $ cd project-wc $ svn propedit svn:date --revprop -r 1 Enter the correct date and time (pay attention to timezones!) and save. You should get a message saying "Set new value for property svn:date on revision 1". Now rinse and repeat for every other revision. Optionally check the commit history to make sure everything looks OK: $ svn log -r 1:HEAD Then go back up one level: $ cd .. Dump the repository: $ svnadmin dump svn/project > project.dump And load the dump on your Subversion server. Done!

This process would probably also work directly between remote repositories, but I found it easier to work with local ones. Fixing the commit times was a lot of work, but overall the process was much more straightforward than any other method I found.


张耀文

there are three methods:

rebase: as the other answers commit id: find svn first commit id and git first commit id , echo their into .git/info/grafts:echo "git_id svn_id}" > .git/info/grafts then git svn dcommit checkout every git commit,copy files into svn_repo, svn commit

bash demo: github demo

v1.x: use rebase and commit id

v2.x: use copy files,then svn commit


d
dangt85

In my case, I had to initiate a clean project from SVN

$ Project> git svn init protocol://path/to/repo -s
$ Project> git svn fetch

add all your project sources...

$ Project> git add .
$ Project> git commit -m "Importing project sources"
$ Project> git svn dcommit

P
Peter Mortensen

I would like to share a great tool being used in the WordPress community called Scatter

Git WordPress plugins and a bit of sanity scatter

This enables users to be able to send their Git repository to wordpress.org SVN automatically. In theory, this code can be applied to any SVN repository.


The link seems to be effectively broken ("The server at evansolomon.me is taking too long to respond.").
P
Peter Mortensen

I just want to share some of my experience with the accepted answer. I did all steps and all was fine before I ran the last step:

git svn dcommit

$ git svn dcommit Use of uninitialized value $u in substitution (s///) at /usr/lib/perl5/vendor_perl/5.22/Git/SVN.pm line 101. Use of uninitialized value $u in concatenation (.) or string at /usr/lib/perl5/vendor_perl/5.22/Git/SVN.pm line 101. refs/remotes/origin/HEAD: 'https://192.168.2.101/svn/PROJECT_NAME' not found in ''

I found the thread https://github.com/nirvdrum/svn2git/issues/50 and finally the solution which I applied in the following file in line 101 /usr/lib/perl5/vendor_perl/5.22/Git/SVN.pm

I replaced

$u =~ s!^\Q$url\E(/|$)!! or die

with

if (!$u) {
    $u = $pathname;
} 
else {
       $u =~ s!^\Q$url\E(/|$)!! or die
      "$refname: '$url' not found in '$u'\n";
}

This fixed my issue.


e
eftshift0

This is what I would do. Assuming my local branch is called main. Fill in the gaps, cause I do not remember the exact git svn commands, as I have not used it in a while.

create in svn the branch that will be used for the project.

use git svn to clone the svn repo you want (at least, the branch you want to use so that you do not have to fetch the other millions of revisions that we do not care about for this endeavor).

checkout the svn branch in the new git repo.

Add to this repository a remote that points to the original git repo you are using for the project. Let's say the remote is called the-real-stuff.

git fetch the-real-stuff # so that we get to see what's in the real repo

Given that this is the first time we will be pulling our code into the svn one, we have to trick git so that it can "merge" that code: git merge --allow-unrelated-histories the-real-stuff/main -m "Whatever comment I want in the svn revision". This will "merge" both branches locally.

git svn dcommit so that you can push the content of the project as it is right now.

Continue working on the original git repo. Then, when you want to push into svn, go to the git svn clone and do:

git fetch the-real-stuff # get visibility to the changes in the original git repo
git merge the-real-stuff/main -m "Whatever comment I want to show on this svn revision"
git svn dcommit

And you are done.

I like the idea of using 2 repos so that we do not fill out the original repo with svn stuff, though it's possible to keep the whole thing in a single git repo.


P
Peter Mortensen

What if you don't want to commit every commit that you make in Git, to the SVN repository? What if you just want to selectively send commits up the pipe? Well, I have a better solution.

I keep one local Git repository where all I ever do is fetch and merge from SVN. That way I can make sure I'm including all the same changes as SVN, but I keep my commit history separate from the SVN entirely.

Then I keep a separate SVN local working copy that is in a separate folder. That's the one I make commits back to SVN from, and I simply use the SVN command line utility for that.

When I'm ready to commit my local Git repository's state to SVN, I simply copy the whole mess of files over into the local SVN working copy and commit it from there using SVN rather than Git.

This way I never have to do any rebasing, because rebasing is like freebasing.