2015-10-26 17:07:03 -04:00
|
|
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
|
|
# contributor license agreements. See the NOTICE file distributed with
|
|
|
|
# this work for additional information regarding copyright ownership.
|
|
|
|
# The ASF licenses this file to You under the Apache License, Version 2.0
|
|
|
|
# (the "License"); you may not use this file except in compliance with
|
|
|
|
# the License. You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
""" Workaround for slow updates from an svn branch to git.
|
2015-11-01 10:27:43 -05:00
|
|
|
See also jira issue INFRA-9182
|
2015-10-26 17:07:03 -04:00
|
|
|
|
|
|
|
Situation:
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
Remote svn repo ---> (slow) git-svn fetch ---> Remote git repo (upstream)
|
2015-10-26 17:07:03 -04:00
|
|
|
| |
|
|
|
|
| |
|
|
|
|
v v
|
2015-11-23 16:03:46 -05:00
|
|
|
Local svn working copy ---> this workaround ---> Local git repo
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
When the remote git-svn fetch is slow, the remote git repo is behind
|
2015-10-26 17:07:03 -04:00
|
|
|
the remote svn repo.
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
When this script is run it will first check that the local working copy and repository are clean.
|
|
|
|
Then it switches the svn working copy to the branch, which updates from the remote.
|
|
|
|
Then it the fetches branch from the git upstream repo, and merges the branch locally.
|
2015-11-01 10:27:43 -05:00
|
|
|
Normally the local svn and git will then be at the same svn revision, and the script will exit.
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
Otherwise the remote git repo is out of date, and the following happens.
|
|
|
|
|
|
|
|
For the branch branchname in a local git repository following an upstream git-svn git repository,
|
2015-10-26 17:07:03 -04:00
|
|
|
this maintains commits on a temporary git branch branchname.svn in the local git repository.
|
|
|
|
These commits contain a message ending like this:
|
2015-11-01 10:27:43 -05:00
|
|
|
"SvnRepoUrl diff -r EarlierSvnRevisionNumber:NextSvnRevisionNumber".
|
2015-11-23 16:03:46 -05:00
|
|
|
Otherwise the messages of the added commits are the same as their counterparts from git svn.
|
|
|
|
|
|
|
|
Normally the added git commits and their git-svn counterparts have no differences between their working trees.
|
|
|
|
However such differences can occur, see also the documentation of git-svn reset and the limitations below.
|
|
|
|
In order not to interfere with git-svn this script only adds commits to a temporary branch
|
|
|
|
branchname.svn, and the commit messages are chosen differently, they do not contain git-svn-id.
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
In case an earlier branchname.svn exists, it will first be deleted if necessary,
|
|
|
|
and restarted at the later branch.
|
|
|
|
Therefore branchname.svn is temporary and should only be used locally.
|
|
|
|
|
|
|
|
By default, no more than 20 commits will be added to branchname.svn in a single run.
|
2015-10-26 17:07:03 -04:00
|
|
|
|
|
|
|
The earlier revision number is taken from the git-svn-id message of git svn,
|
2015-11-23 16:03:46 -05:00
|
|
|
or from the latest revision number in the commit message on branchname.svn,
|
2015-10-26 17:07:03 -04:00
|
|
|
whichever is later.
|
|
|
|
|
|
|
|
This allows branchname.svn to be used as a local git branch instead of branchname
|
2015-11-23 16:03:46 -05:00
|
|
|
to develop new features locally, for example by merging branchname.svn into a feature branch.
|
2015-11-01 10:27:43 -05:00
|
|
|
"""
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
""" Limitations:
|
|
|
|
|
|
|
|
This currently works by patching text, and therefore this does not work on binary files.
|
|
|
|
An example commit in lucene-solr that adds a binary file, on which this currently does not work correctly:
|
|
|
|
svn revision 1707457
|
|
|
|
git commit 3c0390f71e1f08a17f32bc207b4003362f8b6ac2
|
|
|
|
|
|
|
|
When the local svn working copy contains file after updating to the latest available revision,
|
|
|
|
and there is an interim commit that deletes this file, this file is left as an empty file in the working directory
|
|
|
|
of the local git repository.
|
|
|
|
|
|
|
|
All svn properties are ignored here.
|
2015-11-01 10:27:43 -05:00
|
|
|
"""
|
2015-11-23 16:03:46 -05:00
|
|
|
|
|
|
|
""" To be done:
|
|
|
|
Take binary files from the patch, and check out binary files directly from the remote svn repo directly into the local git repo.
|
|
|
|
|
|
|
|
Going really far: checkout each interim svn revision, and use all (changed) files from there instead of the text diff.
|
|
|
|
Determining all files under version control with svn (svn ls) is far too slow for this (esp. when compared to git ls-tree),
|
|
|
|
so this is probably better done by using svnsync to setup a local mirror repository following the remote,
|
|
|
|
and then using svnlook on the local mirror repository.
|
|
|
|
Doing that only to avoid the limitations of this workaround does not appear to be worthwhile.
|
|
|
|
"""
|
|
|
|
|
|
|
|
""" This was developed on Linux using the following program versions:
|
2015-11-01 10:27:43 -05:00
|
|
|
python 2.7.6
|
|
|
|
git 1.9.1
|
|
|
|
svn 1.8.8
|
|
|
|
grep (GNU grep) 2.16
|
|
|
|
|
|
|
|
gitk (part of git) was used for manual testing:
|
|
|
|
- reset branch to an earlier commit to simulate a non working update from svn to git,
|
|
|
|
- delete branchname.svn, reset branchname.svn to earlier,
|
2015-11-23 16:03:46 -05:00
|
|
|
- diff a commit generated here to a commit from git svn,
|
|
|
|
- update, reload, show commits by commit date, ...
|
2015-10-26 17:07:03 -04:00
|
|
|
"""
|
|
|
|
|
|
|
|
import os
|
|
|
|
import subprocess
|
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
from xml import sax
|
|
|
|
from xml.sax.handler import ContentHandler
|
|
|
|
|
|
|
|
import types
|
|
|
|
|
|
|
|
class SvnInfoHandler(ContentHandler):
|
|
|
|
revisionAttr = "revision"
|
|
|
|
|
|
|
|
def __init__(self):
|
2015-11-23 16:03:46 -05:00
|
|
|
self.lastChangeRev = None
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
def startElement(self, name, attrs):
|
|
|
|
if name == "commit":
|
|
|
|
self.lastChangeRev = int(attrs.getValue(self.revisionAttr))
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
def getLastChangeRevision(self):
|
2015-11-01 10:27:43 -05:00
|
|
|
return self.lastChangeRev
|
|
|
|
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
class SvnLogEntry(object):
|
2015-11-01 10:27:43 -05:00
|
|
|
pass # attributes set in SvnLogHandler: revision, author, date, msg
|
2015-10-26 17:07:03 -04:00
|
|
|
|
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
class SvnLogHandler(ContentHandler): # collect list of SvnLogEntry's
|
2015-11-23 16:03:46 -05:00
|
|
|
logEntryTag = "logentry"
|
|
|
|
revisionAttr = "revision" # also used as SvnLogEntry attribute
|
|
|
|
authorTag = "author"
|
|
|
|
dateTag = "date"
|
|
|
|
msgTag = "msg"
|
|
|
|
charCollectTags = (authorTag, dateTag, msgTag) # also used as SvnLogEntry attributes
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
def __init__(self):
|
|
|
|
self.logEntries = []
|
|
|
|
self.chars = None
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
def startElement(self, name, attrs):
|
2015-11-23 16:03:46 -05:00
|
|
|
if name == self.logEntryTag:
|
2015-11-01 10:27:43 -05:00
|
|
|
self.lastLogEntry = SvnLogEntry()
|
|
|
|
setattr(self.lastLogEntry, self.revisionAttr, int(attrs.getValue(self.revisionAttr)))
|
|
|
|
for tag in self.charCollectTags:
|
|
|
|
setattr(self.lastLogEntry, tag, None)
|
|
|
|
return
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
if name in self.charCollectTags:
|
|
|
|
self.chars = ""
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
def characters(self, content):
|
|
|
|
if self.chars is not None:
|
|
|
|
self.chars += content
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
def endElement(self, name):
|
|
|
|
if name in self.charCollectTags:
|
|
|
|
setattr(self.lastLogEntry, name, self.chars)
|
|
|
|
self.chars = None
|
|
|
|
return
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
if name == self.logEntryTag:
|
2015-11-01 10:27:43 -05:00
|
|
|
self.logEntries.append(self.lastLogEntry)
|
|
|
|
self.lastLogEntry = None
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
def getLogEntries(self):
|
|
|
|
return self.logEntries
|
2015-10-26 17:07:03 -04:00
|
|
|
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
class SubProcessAtPath(object):
|
|
|
|
def __init__(self, pathName, verbose=True):
|
|
|
|
assert pathName != ""
|
2015-11-01 10:27:43 -05:00
|
|
|
self.pathName = pathName
|
2015-11-23 16:03:46 -05:00
|
|
|
self.verbose = verbose
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
def getPathName(self):
|
|
|
|
return self.pathName
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
def chDirToPath(self):
|
|
|
|
if self.pathName != os.getcwd():
|
|
|
|
os.chdir(self.pathName)
|
|
|
|
assert self.pathName == os.getcwd()
|
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
def __str__(self):
|
|
|
|
return self.__class__.__name__ + "(" + self.pathName + ")"
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
def checkCall(self, *args, **kwArgs):
|
2015-11-01 10:27:43 -05:00
|
|
|
assert type(*args) != types.StringType
|
2015-11-23 16:03:46 -05:00
|
|
|
self.chDirToPath()
|
2015-11-01 10:27:43 -05:00
|
|
|
if self.verbose:
|
|
|
|
print "check_call args:", " ".join(*args)
|
|
|
|
subprocess.check_call(*args, **kwArgs)
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
def checkOutput(self, *args, **kwArgs):
|
2015-11-01 10:27:43 -05:00
|
|
|
assert type(*args) != types.StringType
|
2015-11-23 16:03:46 -05:00
|
|
|
self.chDirToPath()
|
2015-11-01 10:27:43 -05:00
|
|
|
if self.verbose:
|
|
|
|
print "check_output args:", " ".join(*args)
|
|
|
|
result = subprocess.check_output(*args, **kwArgs)
|
|
|
|
if self.verbose:
|
|
|
|
print "check_output result:", result
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
class SvnWorkingCopy(SubProcessAtPath):
|
2015-11-01 10:27:43 -05:00
|
|
|
def __init__(self, pathName):
|
2015-11-23 16:03:46 -05:00
|
|
|
SubProcessAtPath.__init__(self, pathName, verbose=False)
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
svnCmd = "svn"
|
|
|
|
|
|
|
|
def ensureNoLocalModifications(self):
|
2015-11-23 16:03:46 -05:00
|
|
|
localMods = self.checkOutput((self.svnCmd, "status"))
|
2015-11-01 10:27:43 -05:00
|
|
|
if localMods:
|
|
|
|
errorExit(self, "should not have local modifications:\n", localMods)
|
|
|
|
|
|
|
|
def update(self):
|
2015-11-23 16:03:46 -05:00
|
|
|
self.checkCall((self.svnCmd, "update"))
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
def switch(self, repoBranchName):
|
2015-11-23 16:03:46 -05:00
|
|
|
self.checkCall((self.svnCmd, "switch", ("^/" + repoBranchName)))
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
def lastChangedRevision(self):
|
2015-11-23 16:03:46 -05:00
|
|
|
infoXml = self.checkOutput((self.svnCmd, "info", "--xml"))
|
2015-11-01 10:27:43 -05:00
|
|
|
infoHandler = SvnInfoHandler()
|
|
|
|
sax.parseString(infoXml, infoHandler)
|
2015-11-23 16:03:46 -05:00
|
|
|
return infoHandler.getLastChangeRevision()
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
def getLogEntries(self, fromRevision, toRevision, maxNumLogEntries):
|
|
|
|
revRange = self.revisionsRange(fromRevision, toRevision)
|
2015-11-23 16:03:46 -05:00
|
|
|
logXml = self.checkOutput((self.svnCmd, "log", "-r", revRange, "--xml", "-l", str(maxNumLogEntries)))
|
2015-11-01 10:27:43 -05:00
|
|
|
logHandler = SvnLogHandler()
|
|
|
|
sax.parseString(logXml, logHandler)
|
|
|
|
return logHandler.getLogEntries()
|
|
|
|
|
|
|
|
def revisionsRange(self, fromRevision, toRevision):
|
|
|
|
return str(fromRevision) + ":" + str(toRevision)
|
|
|
|
|
|
|
|
def createPatchFile(self, fromRevision, toRevision, patchFileName):
|
|
|
|
revRange = self.revisionsRange(fromRevision, toRevision)
|
|
|
|
patchFile = open(patchFileName, 'w')
|
|
|
|
try:
|
|
|
|
print "Creating patch from", self.pathName, "between revisions", revRange
|
2015-11-23 16:03:46 -05:00
|
|
|
self.checkCall((self.svnCmd, "diff", "-r", revRange,
|
|
|
|
"--ignore-properties"), # git apply can fail on svn properties.
|
|
|
|
stdout=patchFile)
|
2015-11-01 10:27:43 -05:00
|
|
|
finally:
|
|
|
|
patchFile.close()
|
|
|
|
print "Created patch file", patchFileName
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
def patchedFileNames(self, patchFileName): # return a sequence of the patched file names
|
|
|
|
if os.path.getsize(patchFileName) == 0: # changed only svn properties, no files changed.
|
|
|
|
return []
|
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
indexPrefix = "Index: "
|
|
|
|
regExp = "^" + indexPrefix # at beginning of line
|
2015-11-23 16:03:46 -05:00
|
|
|
patchedFileNamesLines = self.checkOutput(("grep", regExp, patchFileName)) # grep exits 1 whithout any match.
|
2015-11-01 10:27:43 -05:00
|
|
|
indexPrefixLength = len(indexPrefix)
|
|
|
|
return [line[indexPrefixLength:]
|
|
|
|
for line in patchedFileNamesLines.split("\n")
|
|
|
|
if len(line) > 0]
|
|
|
|
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
class GitRepository(SubProcessAtPath):
|
2015-11-01 10:27:43 -05:00
|
|
|
def __init__(self, pathName):
|
2015-11-23 16:03:46 -05:00
|
|
|
SubProcessAtPath.__init__(self, pathName, verbose=False)
|
2015-11-01 10:27:43 -05:00
|
|
|
self.currentBranch = None
|
|
|
|
|
|
|
|
gitCmd = "git"
|
|
|
|
|
|
|
|
def checkOutBranch(self, branchName):
|
2015-11-23 16:03:46 -05:00
|
|
|
self.checkCall((self.gitCmd, "checkout", branchName))
|
2015-11-01 10:27:43 -05:00
|
|
|
self.currentBranch = branchName
|
|
|
|
|
|
|
|
def getCurrentBranch(self):
|
|
|
|
if self.currentBranch is None:
|
2015-11-23 16:03:46 -05:00
|
|
|
gitStatusOut = self.checkOutput((self.gitCmd, "status"))
|
2015-11-01 10:27:43 -05:00
|
|
|
if gitStatusOut.startswith("On branch "):
|
|
|
|
self.currentBranch = gitStatusOut.split[2]
|
|
|
|
else:
|
|
|
|
errorExit(self, "not on a branch:", gitStatusOut)
|
|
|
|
return self.currentBranch
|
|
|
|
|
|
|
|
def workingDirectoryClean(self):
|
2015-11-23 16:03:46 -05:00
|
|
|
gitStatusOut = self.checkOutput((self.gitCmd, "status"))
|
2015-11-01 10:27:43 -05:00
|
|
|
expSubString = "nothing to commit, working directory clean"
|
|
|
|
return gitStatusOut.find(expSubString) >= 0
|
|
|
|
|
|
|
|
def listBranches(self, pattern):
|
2015-11-23 16:03:46 -05:00
|
|
|
return self.checkOutput((self.gitCmd, "branch", "--list", pattern))
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
def branchExists(self, branchName):
|
2015-11-23 16:03:46 -05:00
|
|
|
listOut = self.listBranches(branchName) # CHECKME: using branchName as pattern may not always be ok.
|
2015-11-01 10:27:43 -05:00
|
|
|
return len(listOut) > 0
|
|
|
|
|
|
|
|
def deleteBranch(self, branchName):
|
2015-11-23 16:03:46 -05:00
|
|
|
self.checkCall((self.gitCmd, "branch", "-D", branchName))
|
2015-11-01 10:27:43 -05:00
|
|
|
if branchName == self.currentBranch:
|
|
|
|
self.currentBranch = None
|
|
|
|
|
|
|
|
def createBranch(self, branchName):
|
2015-11-23 16:03:46 -05:00
|
|
|
self.checkCall((self.gitCmd, "branch", branchName))
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
def fetch(self, upStream):
|
2015-11-23 16:03:46 -05:00
|
|
|
self.checkCall((self.gitCmd, "fetch", upStream))
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
def merge(self, branch, fromBranch):
|
2015-11-23 16:03:46 -05:00
|
|
|
self.checkCall((self.gitCmd, "merge", branch, fromBranch))
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
def getCommitMessage(self, commitRef):
|
2015-11-23 16:03:46 -05:00
|
|
|
return self.checkOutput((self.gitCmd, "log", "--format=%B", "-n", "1", commitRef))
|
|
|
|
|
|
|
|
def getCommitAuthorName(self, commitRef):
|
|
|
|
return self.checkOutput((self.gitCmd, "log", "--format=%aN", "-n", "1", commitRef))
|
|
|
|
|
|
|
|
def getCommitAuthorEmail(self, commitRef):
|
|
|
|
return self.checkOutput((self.gitCmd, "log", "--format=%aE", "-n", "1", commitRef))
|
|
|
|
|
|
|
|
def getLatestCommitForAuthor(self, svnAuthor):
|
|
|
|
authorCommit = self.checkOutput(
|
|
|
|
" ".join((self.gitCmd,
|
|
|
|
"rev-list", "--all", "-i", ("--author=" + svnAuthor), # see git commit documentation on --author
|
|
|
|
"|", # pipe should have a buffer for at most a few commit ids.
|
|
|
|
"head", "-1")),
|
|
|
|
shell=True) # use shell pipe
|
|
|
|
authorCommit = authorCommit.rstrip("\n")
|
|
|
|
return authorCommit
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
def getSvnRemoteAndRevision(self, gitSvnCommitRef):
|
|
|
|
gitSvnCommitMessage = self.getCommitMessage(gitSvnCommitRef)
|
|
|
|
words = gitSvnCommitMessage.split();
|
|
|
|
svnIdMarker = "git-svn-id:"
|
|
|
|
assert words.index(svnIdMarker) >= 0
|
|
|
|
svnId = words[words.index(svnIdMarker) + 1]
|
|
|
|
splitSvnId = svnId.split("@")
|
|
|
|
svnRemote = splitSvnId[0]
|
|
|
|
svnRevision = int(splitSvnId[1])
|
|
|
|
return (svnRemote, svnRevision)
|
|
|
|
|
|
|
|
def lastTempGitSvnRevision(self, branchName): # at a commit generated here on the temp branch.
|
|
|
|
gitCommitMessage = self.getCommitMessage(branchName)
|
|
|
|
parts = gitCommitMessage.split(":")
|
|
|
|
lastPart = parts[-1].split()[0] # remove appended newlines
|
|
|
|
try:
|
|
|
|
return int(lastPart)
|
|
|
|
except: # not generated here, ignore.
|
|
|
|
print "Warning: svn revision range not found at end of commit message:\n", gitCommitMessage
|
|
|
|
return None
|
|
|
|
|
|
|
|
def applyPatch(self, patchFileName, stripDepth):
|
2015-11-23 16:03:46 -05:00
|
|
|
self.checkCall((self.gitCmd, "apply",
|
|
|
|
("-p" + str(stripDepth)),
|
|
|
|
"--whitespace=nowarn",
|
|
|
|
patchFileName))
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
def addAllToIndex(self):
|
2015-11-23 16:03:46 -05:00
|
|
|
self.checkCall((self.gitCmd, "add", "-A"))
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
def deleteForced(self, fileName):
|
2015-11-23 16:03:46 -05:00
|
|
|
self.checkCall((self.gitCmd, "rm", "-f", fileName))
|
|
|
|
|
|
|
|
def commit(self, message,
|
|
|
|
authorName, authorEmail, authorDate,
|
|
|
|
committerName, committerEmail, committerDate):
|
|
|
|
author = ''.join((authorName, " <", authorEmail, ">"))
|
|
|
|
os.environ["GIT_COMMITTER_NAME"] = committerName # no need to save/restore earlier environment state.
|
|
|
|
os.environ["GIT_COMMITTER_EMAIL"] = committerEmail
|
|
|
|
os.environ["GIT_COMMITTER_DATE"] = committerDate
|
|
|
|
self.checkCall((self.gitCmd, "commit",
|
|
|
|
"--allow-empty", # only svn poperties changed.
|
|
|
|
("--message=" + message),
|
|
|
|
("--author=" + author),
|
|
|
|
("--date=" + authorDate) ))
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
def cleanDirsForced(self):
|
2015-11-23 16:03:46 -05:00
|
|
|
self.checkCall((self.gitCmd, "clean", "-fd"))
|
2015-10-26 17:07:03 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def errorExit(*messageParts):
|
2015-11-01 10:27:43 -05:00
|
|
|
raise RuntimeError(" ".join(map(str, messageParts)))
|
|
|
|
|
|
|
|
|
|
|
|
def allSuccessivePairs(lst):
|
|
|
|
return [lst[i:i+2] for i in range(len(lst)-1)]
|
|
|
|
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
def maintainTempGitSvnBranch(branchName, tempGitBranchName,
|
|
|
|
svnWorkingCopyOfBranchPath, svnRepoBranchName,
|
|
|
|
gitRepoPath, gitUpstream,
|
|
|
|
patchFileName,
|
|
|
|
maxCommits=20, # generate at most this number of commits on tempGitBranchName, rerun to add more.
|
|
|
|
testMode=False):
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
assert maxCommits >= 1
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
gitRepo = GitRepository(gitRepoPath)
|
|
|
|
gitRepo.checkOutBranch(branchName) # fails with git message when working directory is not clean
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
svnWorkingCopy = SvnWorkingCopy(svnWorkingCopyOfBranchPath)
|
|
|
|
svnWorkingCopy.ensureNoLocalModifications()
|
|
|
|
svnWorkingCopy.switch(svnRepoBranchName) # switch to repo branch, update to latest revision
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
lastSvnRevision = svnWorkingCopy.lastChangedRevision()
|
|
|
|
# print svnWorkingCopy, "lastSvnRevision:", lastSvnRevision
|
|
|
|
|
|
|
|
gitRepo.fetch(gitUpstream)
|
|
|
|
if testMode:
|
|
|
|
pass # leave branch where it is, as if the last commits from upstream did not arrive
|
|
|
|
else:
|
|
|
|
gitRepo.merge(branchName, gitUpstream + "/" + branchName)
|
|
|
|
|
|
|
|
(svnRemote, lastSvnRevisionOnGitSvnBranch) = gitRepo.getSvnRemoteAndRevision(branchName)
|
2015-10-26 17:07:03 -04:00
|
|
|
print "svnRemote:", svnRemote
|
2015-11-01 10:27:43 -05:00
|
|
|
#print gitRepo, branchName, "lastSvnRevisionOnGitSvnBranch:", lastSvnRevisionOnGitSvnBranch
|
2015-10-26 17:07:03 -04:00
|
|
|
|
|
|
|
# check whether tempGitBranchName exists:
|
|
|
|
diffBaseRevision = lastSvnRevisionOnGitSvnBranch
|
|
|
|
svnTempRevision = None
|
|
|
|
doCommitOnExistingTempBranch = False
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
if gitRepo.branchExists(tempGitBranchName):
|
2015-10-26 17:07:03 -04:00
|
|
|
print tempGitBranchName, "exists"
|
|
|
|
# update lastSvnRevisionOnGitSvnBranch from there.
|
2015-11-01 10:27:43 -05:00
|
|
|
svnTempRevision = gitRepo.lastTempGitSvnRevision(tempGitBranchName)
|
|
|
|
if svnTempRevision is None:
|
|
|
|
print "Warning: no svn revision found on branch:", tempGitBranchName
|
|
|
|
else:
|
2015-10-26 17:07:03 -04:00
|
|
|
if svnTempRevision > lastSvnRevisionOnGitSvnBranch:
|
|
|
|
diffBaseRevision = svnTempRevision
|
2015-11-01 10:27:43 -05:00
|
|
|
doCommitOnExistingTempBranch = True
|
|
|
|
gitRepo.checkOutBranch(tempGitBranchName)
|
2015-10-26 17:07:03 -04:00
|
|
|
|
|
|
|
if lastSvnRevision == diffBaseRevision:
|
2015-11-01 10:27:43 -05:00
|
|
|
print gitRepo, gitRepo.getCurrentBranch(), "up to date with", svnWorkingCopy, svnRepoBranchName
|
2015-10-26 17:07:03 -04:00
|
|
|
return
|
|
|
|
|
|
|
|
if lastSvnRevision < diffBaseRevision: # unlikely, do nothing
|
2015-11-01 10:27:43 -05:00
|
|
|
print gitRepo, gitRepo.getCurrentBranch(), "later than", svnWorkingCopy, ", nothing to update."
|
2015-11-23 16:03:46 -05:00
|
|
|
# CHECK: generate svn commits from the git commits?
|
2015-10-26 17:07:03 -04:00
|
|
|
return
|
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
print gitRepo, gitRepo.getCurrentBranch(), "earlier than", svnWorkingCopy
|
|
|
|
|
|
|
|
if not gitRepo.workingDirectoryClean():
|
|
|
|
errorExit(gitRepo, "on branch", gitRepo.getCurrentBranch(), "not clean")
|
|
|
|
|
|
|
|
print gitRepo,"on branch", gitRepo.getCurrentBranch(), "and clean"
|
|
|
|
|
|
|
|
if not doCommitOnExistingTempBranch: # restart temp branch from branch
|
|
|
|
assert gitRepo.getCurrentBranch() == branchName
|
|
|
|
if gitRepo.branchExists(tempGitBranchName): # tempGitBranchName exists, delete it first.
|
|
|
|
print "Branch", tempGitBranchName, "exists, deleting"
|
|
|
|
gitRepo.deleteBranch(tempGitBranchName)
|
|
|
|
if gitRepo.branchExists(tempGitBranchName):
|
|
|
|
errorExit("Could not delete branch", tempGitBranchName, "from", gitRepo)
|
|
|
|
|
|
|
|
gitRepo.createBranch(tempGitBranchName)
|
|
|
|
gitRepo.checkOutBranch(tempGitBranchName)
|
|
|
|
print "Started branch", tempGitBranchName, "at", branchName
|
|
|
|
|
|
|
|
assert gitRepo.getCurrentBranch() == tempGitBranchName
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
patchStripDepth = 0 # patch generated at svn repo.
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
maxNumLogEntries = maxCommits + 1
|
|
|
|
svnLogEntries = svnWorkingCopy.getLogEntries(diffBaseRevision, lastSvnRevision, maxNumLogEntries)
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
numCommits = 0
|
2015-11-01 10:27:43 -05:00
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
for (logEntryFrom, logEntryTo) in allSuccessivePairs(svnLogEntries):
|
2015-11-01 10:27:43 -05:00
|
|
|
# create patch file from svn between the revisions:
|
|
|
|
svnWorkingCopy.createPatchFile(logEntryFrom.revision, logEntryTo.revision, patchFileName)
|
2015-11-23 16:03:46 -05:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
patchedFileNames = svnWorkingCopy.patchedFileNames(patchFileName)
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
if os.path.getsize(patchFileName) > 0:
|
|
|
|
gitRepo.applyPatch(patchFileName, patchStripDepth)
|
|
|
|
print "Applied patch", patchFileName
|
|
|
|
else: # only svn properties changed, do git commit for commit info only.
|
|
|
|
print "Empty patch", patchFileName
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
gitRepo.addAllToIndex() # add all patch changes to the git index to be committed.
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
# Applying the patch leaves files that have been actually deleted at zero size.
|
|
|
|
# Therefore delete empty patched files from the git repo that do not exist in svn working copy:
|
|
|
|
for patchedFileName in patchedFileNames:
|
2015-11-23 16:03:46 -05:00
|
|
|
fileNameInGitRepo = os.path.join(gitRepo.getPathName(), patchedFileName)
|
|
|
|
fileNameInSvnWorkingCopy = os.path.join(svnWorkingCopy.getPathName(), patchedFileName)
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
if os.path.isdir(fileNameInGitRepo):
|
|
|
|
# print "Directory:", fileNameInGitRepo
|
|
|
|
continue
|
|
|
|
|
|
|
|
if not os.path.isfile(fileNameInGitRepo):
|
2015-11-23 16:03:46 -05:00
|
|
|
print "Possibly new binary file in svn, ignored here:", fileNameInGitRepo
|
|
|
|
# FIXME: Take a new binary file out of the svn repository directly.
|
2015-11-01 10:27:43 -05:00
|
|
|
continue
|
|
|
|
|
|
|
|
fileSize = os.path.getsize(fileNameInGitRepo)
|
|
|
|
if fileSize > 0:
|
|
|
|
# print "Non empty file patched normally:", fileNameInGitRepo
|
|
|
|
continue
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
# fileNameInGitRepo exists and is empty
|
|
|
|
if os.path.isfile(fileNameInSvnWorkingCopy):
|
|
|
|
# FIXME: this only works correctly when the svn working copy is hecked out at the target revision.
|
2015-11-01 10:27:43 -05:00
|
|
|
print "Left empty file:", fileNameInGitRepo
|
|
|
|
continue
|
|
|
|
|
|
|
|
gitRepo.deleteForced(fileNameInGitRepo) # force, the file is not up to date. This also stages the delete for commit.
|
|
|
|
# print "Deleted empty file", fileNameInGitRepo # not needed, git rm is verbose enough
|
|
|
|
|
|
|
|
# commit, put toRevision at end so it can be picked up later.
|
|
|
|
revisionsRange = svnWorkingCopy.revisionsRange(logEntryFrom.revision, logEntryTo.revision)
|
|
|
|
message = logEntryTo.msg + "\n\n" + svnRemote + " diff -r " + revisionsRange
|
2015-11-23 16:03:46 -05:00
|
|
|
authorCommit = gitRepo.getLatestCommitForAuthor(logEntryTo.author)
|
|
|
|
authorName = gitRepo.getCommitAuthorName(authorCommit)
|
|
|
|
authorEmail = gitRepo.getCommitAuthorEmail(authorCommit)
|
|
|
|
# print "Author name and email:", authorName, authorEmail
|
2015-11-01 10:27:43 -05:00
|
|
|
gitRepo.commit(message,
|
2015-11-23 16:03:46 -05:00
|
|
|
authorName, authorEmail, logEntryTo.date,
|
|
|
|
authorName, authorEmail, logEntryTo.date) # author is also git committer, just like git-svn
|
2015-11-01 10:27:43 -05:00
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
numCommits += 1
|
|
|
|
|
|
|
|
# print "Commit author:", logEntryTo.author
|
|
|
|
# print "Commit date:", logEntryTo.date
|
2015-11-01 10:27:43 -05:00
|
|
|
print "Commit message:", logEntryTo.msg
|
|
|
|
|
|
|
|
gitRepo.cleanDirsForced() # delete untracked directories and files
|
|
|
|
|
|
|
|
if not gitRepo.workingDirectoryClean():
|
2015-11-23 16:03:46 -05:00
|
|
|
errorExit(gitRepo, "on branch", gitRepo.getCurrentBranch(), "not clean, numCommits:", numCommits)
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
print "Added", numCommits, "commit(s) to branch", tempGitBranchName
|
2015-10-26 17:07:03 -04:00
|
|
|
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
if __name__ == "__main__":
|
2015-11-01 10:27:43 -05:00
|
|
|
|
|
|
|
testMode = False # when true, leave branch where it is, as if the last commits from upstream did not arrive
|
|
|
|
defaultMaxCommits = 20
|
|
|
|
maxCommits = defaultMaxCommits
|
|
|
|
|
2015-11-23 16:03:46 -05:00
|
|
|
import sys
|
2015-11-01 10:27:43 -05:00
|
|
|
argv = sys.argv[1:]
|
|
|
|
while argv:
|
|
|
|
if argv[0] == "test":
|
|
|
|
testMode = True
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
maxCommits = int(argv[0])
|
|
|
|
assert maxCommits >= 1
|
|
|
|
except:
|
2015-11-23 16:03:46 -05:00
|
|
|
errorExit("Argument(s) should be test and/or a maximum number of commits, defaults are false and " + defaultMaxCommits)
|
2015-11-01 10:27:43 -05:00
|
|
|
argv = argv[1:]
|
|
|
|
|
2015-10-26 17:07:03 -04:00
|
|
|
repo = "lucene-solr"
|
|
|
|
branchName = "trunk"
|
|
|
|
tempGitBranchName = branchName + ".svn"
|
|
|
|
|
|
|
|
home = os.path.expanduser("~")
|
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
svnWorkingCopyOfBranchPath = os.path.join(home, "svnwork", repo, branchName)
|
|
|
|
svnRepoBranchName = "lucene/dev/" + branchName # for svn switch to
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
gitRepo = os.path.join(home, "gitrepos", repo)
|
2015-10-26 17:07:03 -04:00
|
|
|
gitUpstream = "upstream"
|
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
patchFileName = os.path.join(home, "patches", tempGitBranchName)
|
2015-10-26 17:07:03 -04:00
|
|
|
|
2015-11-01 10:27:43 -05:00
|
|
|
maintainTempGitSvnBranch(branchName, tempGitBranchName,
|
|
|
|
svnWorkingCopyOfBranchPath, svnRepoBranchName,
|
|
|
|
gitRepo, gitUpstream,
|
|
|
|
patchFileName,
|
|
|
|
maxCommits=maxCommits,
|
|
|
|
testMode=testMode)
|