New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implementation of SCM feature for SVN #3192
Conversation
cmd_command = "svn" | ||
|
||
def __init__(self, runner=None, *args, **kwargs): | ||
def runner_no_strip(command): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot strip
SVN output because it relies on chars at specific columns. It may be used for git too and promote this function to the common ancestor class (here).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who is stripping the output? The regular runner
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If no runner
is given, the implementation of SCMBase::run
does (here):
if not self._runner:
return subprocess.check_output(command, shell=True).decode().strip()
else:
return self._runner(command)
conans/client/tools/scm.py
Outdated
output = self.run("status -u -r {}".format(wc_revision)) | ||
offending_columns = [0, 1, 2, 3, 4, 6, 7, 8] # 5th column informs if the file is locked (7th is always blank) | ||
it = iter(output.splitlines()) | ||
while True: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for item in output.splitlines():
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to skip next line in case of a tree conflict, so I need an iterator:
if item[6] == 'C':
next(it) # Skip line as it contains information about the conflict.
conans/client/tools/scm.py
Outdated
def get_revision(self): | ||
wc_revision = self.run("info --show-item revision").strip() | ||
|
||
# Check if working copy is consistent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point and a nice to have. But I'm not especially worried about it, we could start supposing that the user is following some good practices like continuous integration automation where the repository will be consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would output a warning to let the user know (in case they care about those lines)... but I don't know what is the conan way to emit a warning: shall I log it, do I need to pass an output stream to write into it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. Here we don't have any output. In other similar situations we are passing an optional output
to the constructor and then:
out = out or ConanOutput(sys.stdout, True)
The out
usage is the same as in the conanfile.output: out.warn("C is not C++")
conans/client/tools/scm.py
Outdated
return self.run("info --show-item url").strip() | ||
|
||
def get_revision(self): | ||
wc_revision = self.run("info --show-item revision").strip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the output of this command? (I have no idea) but in svn the "revisions" are just sequential integers, right?. if we want to have a unique ID of the code (reproducibility) probably we would need also the branch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its output is just the revision number, as I told you before there is no branch in SVN, it is just another directory/path inside the repository, so it will be encoded in the URL.
conans/client/tools/scm.py
Outdated
super(SVN, self).__init__(runner=runner, *args, **kwargs) | ||
|
||
def clone(self, url, branch=None, submodule=None): | ||
assert branch is None, "This concept has no meaning for SVN" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't it? As far as I remember there were branches in svn. Could we check out first the repo and then change to the branch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The concept of branch in SVN is just another place/path in the repository and it will be encoded in the URL. SVN is just a filesystem with historized files, branches and tags are just another directory and usually you have several projects (if not all) in the same repository.
The most common layout for a project inside a repo in SVN is to create three folders: trunk
, branches
and tags
; so you will have master branch at https://my.svn/project/trunk
a branch called featureX at https://my.svn/project/branches/featureX
... and in the same way a tag
is just another url like https://my.svn/project/tags/v1.2.3
.
But this layout is not guaranteed, so you cannot infer where is branchXYZ
given the path to trunk
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, now I remember. Thanks! it makes sense, the URL will define the "branch" and the revision (integer) will be unique.
cmd_command = "svn" | ||
|
||
def __init__(self, runner=None, *args, **kwargs): | ||
def runner_no_strip(command): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who is stripping the output? The regular runner
?
conans/client/tools/scm.py
Outdated
output = self.run("status --no-ignore") | ||
for it in output.splitlines(): | ||
if it[0] == 'I': | ||
file = it[9:].strip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
file
is reserved built-in. Hint: Using some linter could help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add more information where requested.
Change file
as variable name (added commit).
conans/client/tools/scm.py
Outdated
super(SVN, self).__init__(runner=runner, *args, **kwargs) | ||
|
||
def clone(self, url, branch=None, submodule=None): | ||
assert branch is None, "This concept has no meaning for SVN" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert? Raise exception? Ignore it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the parameters. I prefer each class making sense itself. The model SCM class can call Git().clone
and SVN().clone
with the right parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll do it, but I often prefer classes within the same hierarchy to have identical arguments for functions with the same name (and I would declare it in the parent class). I think it is easier for the developer to understand the code and also useful for the IDE.
I'm thinking about something like this:
repo_class = {"git": Git, "svn": SVN}.get(self._data.type)
repo = repo_class(...)
repo.clone(branch="mybranch")
but this is another topic and should be treated apart.
cmd_command = "svn" | ||
|
||
def __init__(self, runner=None, *args, **kwargs): | ||
def runner_no_strip(command): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If no runner
is given, the implementation of SCMBase::run
does (here):
if not self._runner:
return subprocess.check_output(command, shell=True).decode().strip()
else:
return self._runner(command)
conans/client/tools/scm.py
Outdated
super(SVN, self).__init__(runner=runner, *args, **kwargs) | ||
|
||
def clone(self, url, branch=None, submodule=None): | ||
assert branch is None, "This concept has no meaning for SVN" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The concept of branch in SVN is just another place/path in the repository and it will be encoded in the URL. SVN is just a filesystem with historized files, branches and tags are just another directory and usually you have several projects (if not all) in the same repository.
The most common layout for a project inside a repo in SVN is to create three folders: trunk
, branches
and tags
; so you will have master branch at https://my.svn/project/trunk
a branch called featureX at https://my.svn/project/branches/featureX
... and in the same way a tag
is just another url like https://my.svn/project/tags/v1.2.3
.
But this layout is not guaranteed, so you cannot infer where is branchXYZ
given the path to trunk
.
conans/client/tools/scm.py
Outdated
return self.run("info --show-item url").strip() | ||
|
||
def get_revision(self): | ||
wc_revision = self.run("info --show-item revision").strip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its output is just the revision number, as I told you before there is no branch in SVN, it is just another directory/path inside the repository, so it will be encoded in the URL.
conans/client/tools/scm.py
Outdated
def get_revision(self): | ||
wc_revision = self.run("info --show-item revision").strip() | ||
|
||
# Check if working copy is consistent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would output a warning to let the user know (in case they care about those lines)... but I don't know what is the conan way to emit a warning: shall I log it, do I need to pass an output stream to write into it?
conans/client/tools/scm.py
Outdated
output = self.run("status -u -r {}".format(wc_revision)) | ||
offending_columns = [0, 1, 2, 3, 4, 6, 7, 8] # 5th column informs if the file is locked (7th is always blank) | ||
it = iter(output.splitlines()) | ||
while True: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to skip next line in case of a tree conflict, so I need an iterator:
if item[6] == 'C':
next(it) # Skip line as it contains information about the conflict.
To start thinking about testing I need a way to create a repo to make requests against it. SVN allows to create a local repo and call it using |
Probably they are installed in the Linux slaves, don't worry, submit the test/tests you consider and if it fails in some slaves we will take care! |
…onflicted/... files (not implemented for Git)
It provides several tests and auxiliary classes to cover use cases for SVN implementation of SCM feature. It requires `svnadmin` and `svn` command line tools to be installed in testing machine.
@lasote, I've added a lot of tests (same as scm/git plus some unittesting around SVN class). Test suite works (Windows/py3) and I think it is more or less complete, but it requires some more testing from real users in real scenarios. As it is an experimental feature and it doesn't interfere with Git implementation it should be safe to release it... but before it CI must be provided with a SVN installation. |
@jgsogo |
To build the URL for SVN we are using also the |
@jgsogo
So probably
|
Also, we should consider modeling the From the user point of view, the recipe will look like: scm = {
"type": "svn",
"url": "auto",
"revision": "auto",
...
} there is nothing related to Maybe I'm not understanding your concerns. |
conans/client/tools/scm.py
Outdated
try: | ||
self.version = SVN.get_version() | ||
except ConanException: | ||
self.version = Version("1.10") # TODO: Go for a modern one, or raise? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lasote, what would you do here? What to do if the function to parse SVN version fails?
Yes The question is: is it really required to map all existing scm-keys to all SCM implementations or does it make sense to have just the Concerning |
There is no My last commit adds the check for SVN version 😉, defaulting to "1.10" if it fails. |
Oh, you are right. I'm sorry for that mistake. However, probably the ...
base = python_requires("myBaseRecipes/1.2.3@user/channel")
class Module1(base.ConanBase):
...
scm = {
"type": "svn",
"url": "https://..."
"relative_url": "trunk",
"revision": "HEAD"
} ...
base = python_requires("myBaseRecipes/1.2.3@user/channel")
class Module2(base.ConanBase):
...
scm = {
"type": "git",
"url": "https://..."
"branch": "feature/1-2-3",
"revision": "HEAD"
} # implementation of the base class
...
def source(self)
scm = tools.SCM()
scm.checkout() # whis uses the scm information provided by the scm dict which would be very useful for recipe in different repo than sources and in combination with Concerning version check 🥇 😃 👍 |
This is already working. Please remember, that the def get_svn_version(version):
try:
scm = tools.SVN()
revision = scm.get_revision()
if scm.is_pristine():
dirty = ""
else:
dirty = ".dirty"
return "%s-%s+%s.%s" % (version, revision, scm.get_branch(), dirty)
except:
return None
class BaseLibrary(ConanFile):
version = get_svn_version("1.2.0") |
Looks nice. I will think about it how to integrate in our recipe. Thanks for the hint. |
Btw. it would make sense to have a good example for this at https://docs.conan.io/en/latest/creating_packages/package_repo.html#package-repo Again, many thanks for your work. 👍 |
With this last change, I think that the feature is ready to be packed and included in 1.8. Thanks a lot for your valuable feedback, suggestions and testing. It will be experimental, as there are a lot of use cases that we haven't think about and for sure there will be some bugs and work to be done, but it is time to test it for real 💪 Please, review your reviews 😸 |
Agree with this! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my side I think everything is okay. The is_pristine
will be discussed in the other issue
I generated an issue for it: conan-io/docs#861. |
@@ -306,6 +314,60 @@ def create_local_git_repo(files=None, branch=None, submodules=None, folder=None) | |||
return tmp.replace("\\", "/"), git.get_revision() | |||
|
|||
|
|||
def handleRemoveReadonly(func, path, exc): # TODO: May promote to conan tools? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes please, this often fails for me in various situations using conan commands with git repositories, so had to do removes manually
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we issue a ticket for it? It would be nice to get this code review into the develop branch...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please, maybe open an issue or directly a PR on top on this.
Implementation of SCM feature for SVN
Following issue #3130 reported by @keithrob91, I'm implementing SCM for SVN. This is just the first draft as further testing and more comprenhensive use cases have to be tried. I need some advice from Conan team related to some TODOs stated at the code, and also how to test it against a mocked SVN server.
Changelog: Feature: [Experimental] Add SCM support for SVN.