赞
踩
目录
作为一个软件工程师,我们在一个依赖于另一个同时也在工作的项目上工作的情况并不少见。场景可能如下:
我们有两个项目,每个项目都有其Git存储库:
当我们在myproj上工作时,我们可能还需要同时进行更新commonlib。如果commonlib和myproj同时碰巧是Python项目,我们可以使用安装工具(setuptools)的发展模式(开发模式)和Git的子模块(子模块),使工作更容易。本文演示了如何使用开发模式和子模块来处理这种情况。希望需要处理此类示例的人员可以发现本文有所帮助。
commonlib和myproj在本文的其余部分用作示例,并且该示例假定代码在具有以下条件的虚拟环境中运行:
首先,对于Python项目开发,我们通常首先设置一个虚拟环境,并将所有依赖项安装到虚拟环境中。然后,在这种情况下,我们开始进行myproj项目。但是,myproj需要commonlib,我们也在同一时间对其进行处理。如果commonlib以正常方式安装,例如,pip install,我们将无法使用Git来跟踪commonlib的更改。这是开发模式来解决的问题。
其次,commonlib被许多项目使用,包括myproj。一方面,在开发过程中,myproj可能需要坚持使用commonlib的特定版本或分支。另一方面,其他项目可能需要不同版本的commonlib。另外,为了确保在处理myproj时使用正确的commonlib分支或版本,可以将依赖项设置为Git子模块。
开发模式允许安装和编辑项目。
通常,我们从PyPi上安装Python包。
$ pip install <package_name>
或者,我们从本地软件包安装它。
$ pip install <path_to_local_archive>
无论哪种方式,软件包都将安装到我们的(虚拟)环境中。例如,当我们将Python软件包安装到虚拟环境中时,该软件包将被复制到/virtual_environment/lib/python3.7/site-packages/。如果要安装commonlib到虚拟环境中,可以执行以下操作:
- $ git clone https://github.com/shunsvineyard/commonlib.git
- $ pip install commonlib/
安装后,commonlib将在site-packages文件夹中显示为已安装的软件包。我们可以使用ls命令来检查它。例如,结果可能如下所示:
- (demo_env) shunsvineyard@remote-ubuntu:~$ ls -l demo_env/lib/python3.7/site-packages/
- total 40
- drwxrwxr-x 2 shunsvineyard shunsvineyard 4096 Dec 23 05:00 __pycache__
- drwxrwxr-x 3 shunsvineyard shunsvineyard 4096 Dec 23 05:01 commonlib
- drwxrwxr-x 2 shunsvineyard shunsvineyard 4096 Dec 23 05:01 commonlib-0.0.1.egg-info
- -rw-rw-r-- 1 shunsvineyard shunsvineyard 126 Dec 23 05:00 easy_install.py
- drwxrwxr-x 11 shunsvineyard shunsvineyard 4096 Dec 23 05:00 pip
- drwxrwxr-x 2 shunsvineyard shunsvineyard 4096 Dec 23 05:00 pip-9.0.1.dist-info
- drwxrwxr-x 5 shunsvineyard shunsvineyard 4096 Dec 23 05:00 pkg_resources
- drwxrwxr-x 2 shunsvineyard shunsvineyard 4096 Dec 23 05:00 pkg_resources-0.0.0.dist-info
- drwxrwxr-x 6 shunsvineyard shunsvineyard 4096 Dec 23 05:00 setuptools
- drwxrwxr-x 2 shunsvineyard shunsvineyard 4096 Dec 23 05:00 setuptools-39.0.1.dist-info
开发模式会创建一个从程序包到虚拟环境的链接。在开发模式下,可以以允许我们在安装后编辑代码的方式安装Python软件包。因此,当我们对代码进行任何更改时,该更改将在虚拟环境中立即生效。
要将Python软件包安装为开发模式,请使用以下命令
$ pip install -e <path to the package>
以commonlib为例,结果可能如下所示:
- (demo_env) shunsvineyard@remote-ubuntu:~$ pip install -e commonlib/
- Obtaining file:///home/shunsvineyard/commonlib
- Installing collected packages: commonlib
- Running setup.py develop for commonlib
- Successfully installed commonlib
- (demo_env) shunsvineyard@remote-ubuntu:~$ ls -l demo_env/lib/python3.7/site-packages/
- total 40
- drwxrwxr-x 2 shunsvineyard shunsvineyard 4096 Dec 23 05:08 __pycache__
- -rw-rw-r-- 1 shunsvineyard shunsvineyard 31 Dec 23 05:09 commonlib.egg-link
- -rw-rw-r-- 1 shunsvineyard shunsvineyard 30 Dec 23 05:09 easy-install.pth
- -rw-rw-r-- 1 shunsvineyard shunsvineyard 126 Dec 23 05:08 easy_install.py
- drwxrwxr-x 11 shunsvineyard shunsvineyard 4096 Dec 23 05:08 pip
- drwxrwxr-x 2 shunsvineyard shunsvineyard 4096 Dec 23 05:08 pip-9.0.1.dist-info
- drwxrwxr-x 5 shunsvineyard shunsvineyard 4096 Dec 23 05:08 pkg_resources
- drwxrwxr-x 2 shunsvineyard shunsvineyard 4096 Dec 23 05:08 pkg_resources-0.0.0.dist-info
- drwxrwxr-x 6 shunsvineyard shunsvineyard 4096 Dec 23 05:08 setuptools
- drwxrwxr-x 2 shunsvineyard shunsvineyard 4096 Dec 23 05:08 setuptools-39.0.1.dist-info
如果打开文件commonlib.egg-link,我们将看到它链接到的位置。例如,
- (demo_env) shunsvineyard@remote-ubuntu:~$ cat demo_env/lib/python3.7/site-packages/commonlib.egg-link
- /home/shunsvineyard/commonlib
请注意,开发模式仅适用于本地项目或VCS URL。如果我们尝试以开发模式从PyPi安装软件包,则会显示以下错误消息。使用numpy为例,
- $ pip install -e numpy
- numpy should either be a path to a local project or a VCS url beginning with svn+, git+, hg+, or bzr+
Git子模块是另一个Git存储库中的Git存储库。就像一个Git存储库引用了另一个Git存储库一样。例如,myproj对commonlib有依赖。如果commonlib是myproj的Git子模块,下图说明了它们之间的关系。
Git子模块允许我们将Git存储库保留为另一个Git存储库的子目录。当我们执行git clone myproj时,Myproj子模块引用commonlib中定义的的特定版本将从commonlib存储库下载。这样,我们可以将另一个存储库(即commonlib)克隆到我们的项目(即myproj)中,并使提交分开。
以下各节以commonlib和myproj作为示例来演示开发模式和子模块的设置和工作流程。以下各节还假设我们从头开始做所有事情,包括设置Git存储库。
假设commonlib提供了一个非常简单且唯一的功能:greeting。项目布局和代码如下所示:
- commonlib/
- ├── LICENSE
- ├── README.rst
- ├── commonlib
- │ ├── __init__.py
- │ └── greeting.py
- └── setup.py
greeting.py
- def greeting(name: str):
- """Print a simple greeting with the name."""
- print(f"Howdy, {name}")
setup.py
- import pathlib
- import setuptools
-
- # The directory containing this file
- HERE = pathlib.Path(__file__).parent
-
- # The text of the README file
- README = (HERE / "README.rst").read_text()
-
- # This call to setup() does all the work
- setuptools.setup(
- name="commonlib",
- version="0.0.1",
- description="A simple Python package",
- long_description=README,
- long_description_content_type="text/x-rst",
- author="Author Name",
- author_email="author@email.com",
- license="MIT",
- classifiers=[
- "License :: OSI Approved :: MIT License",
- "Programming Language :: Python"
- ],
- packages=setuptools.find_packages(),
- python_requires=">=3.7"
- )
(commonlib的完整示例可以在https://github.com/shunsvineyard/commonlib中找到)
现在,我们准备设立两个Git仓库commonlib和myproj。在此之前,我们需要设置一个Git服务器。此示例使用本地主机(即127.0.0.1)作为Git服务器。
- $ sudo useradd git
- $ sudo passwd git
- $ su git
- $ cd ~
- $ git init --bare commonlib
- $ git init --bare myproj
拥有Git服务器之后,我们可以将现有commonlib服务器添加到Git服务器中。返回到本地用户。
- user:~$ cd commonlib/
- user:~/commonlib$ git init
- user:~/commonlib$ git add –all
- user:~/commonlib$ git commit -a -m "Initialize commonlib repository"
- user:~/commonlib$ git remote add origin git@127.0.0.1:commonlib
- user:~/commonlib $ git push -u origin master
对于myproj,我们可以做类似commonlib的事情。项目布局和代码如下:
- myproj/
- ├── LICENSE
- ├── README.rst
- ├── app.py
- └── setup.py
app.py
- from commonlib import greeting
-
- def run():
- greeting.greeting("Git Submodule")
-
- if __name__ == "__main__":
- run()
setup.py
- import pathlib
- import setuptools
-
- # The directory containing this file
- HERE = pathlib.Path(__file__).parent
-
- # The text of the README file
- README = (HERE / "README.rst").read_text()
-
- # This call to setup() does all the work
- setuptools.setup(
- name="myproj",
- version="0.0.1",
- description="A simple Python project",
- long_description=README,
- long_description_content_type="text/x-rst",
- url="https://github.com/shunsvineyard/myproj",
- author="Author Name",
- author_email="author@email.com",
- license="MIT",
- classifiers=[
- "License :: OSI Approved :: MIT License",
- "Programming Language :: Python"
- ],
- packages=setuptools.find_packages(),
- python_requires=">=3.7"
- )
然后,将现有代码添加到Git服务器。
- user:~$ cd myproj/
- user:~/myproj$ git init
- user:~/myproj$ git add –all
- user:~/myproj$ git commit -a -m "Initialize myprojrepository"
- user:~/myproj$ git remote add origin git@127.0.0.1: myproj
- user:~/myproj$ git push -u origin master
尽管Git子模块为各种情况提供了许多功能,但使用最多的两个用例是:1.将存储库添加为子模块;以及2.更新子模块。
通过以下命令可以简单地将现有存储库添加为另一个存储库的子模块:
- user:~$ cd myproj/
- user:~/myproj$ git submodule add git@127.0.0.1:commonlib
- user:~/myproj$ git submodule init
- user:~/myproj$ git commit -a -m "Add commonlib as submodule"
- user:~/myproj$ git push
添加子模块后,将创建一个子模块引用,即.gitmodules文件。它看起来可能如下所示:
- shunsvineyard@remote-ubuntu:~/workspace/myproj$ ls -al
- total 40
- drwxrwxr-x 4 shunsvineyard shunsvineyard 4096 Dec 20 07:20 .
- drwxrwxr-x 10 shunsvineyard shunsvineyard 4096 Dec 20 06:47 ..
- drwxrwxr-x 9 shunsvineyard shunsvineyard 4096 Dec 20 07:22 .git
- -rw-rw-r-- 1 shunsvineyard shunsvineyard 1233 Dec 20 06:44 .gitignore
- -rw-rw-r-- 1 shunsvineyard shunsvineyard 73 Dec 20 07:20 .gitmodules
- -rw-rw-r-- 1 shunsvineyard shunsvineyard 1067 Dec 20 06:44 LICENSE
- -rw-rw-r-- 1 shunsvineyard shunsvineyard 278 Dec 20 06:58 README.rst
- -rw-rw-r-- 1 shunsvineyard shunsvineyard 123 Dec 20 06:57 app.py
- drwxrwxr-x 3 shunsvineyard shunsvineyard 4096 Dec 20 07:20 commonlib
- -rw-rw-r-- 1 shunsvineyard shunsvineyard 724 Dec 20 06:57 setup.py
如果打开文件.gitmodules,我们可以看到它记录了子模块的信息。
- $ cat .gitmodules
- [submodule "commonlib"]
- path = commonlib
- url = git@127.0.0.1:commonlib
注意:.gitmodules中的子模块的url可以是相对路径。例如,commonlib和myproj都位于Git服务器的同一文件夹中。url可以简化为../commonlib。
如果我们使用Github托管我们的存储库,则子模块可能如下所示:
(示例myproj可以在https://github.com/shunsvineyard/myproj上找到)
通常,在两种情况下,我们可能需要更新子模块:1.由于某些代码更改,因此更新了子模块。2.将子模块更新为较新的或特定的版本。
情况1:由于代码更改而更新子模块
子模块只是另一个Git存储库中的一个Git存储库。当我们在子模块上进行一些代码更改时,我们将执行与通常在常规Git存储库上相同的操作。
例如,我们添加了一个调用greeting2到commonlib的新功能。
greeting.py
- def greeting2(name: str):
- """Print a simple greeting with the name."""
- print(f"How are you, {name}?")
我们对子模块执行的操作与常规存储库相同:提交更改并推送更改。
- user:~$ cd myproj/commonlib
- user:~/myproj/commonlib$ git status
- On branch master
- Your branch is up to date with 'origin/master'.
-
- Changes not staged for commit:
- (use "git add <file>..." to update what will be committed)
- (use "git checkout -- <file>..." to discard changes in working directory)
-
- modified: greeting.py
-
- no changes added to commit (use "git add" and/or "git commit -a")
-
- user:~/myproj/commonlib$ git commit -a -m "Added a new greeting function."
- user:~/myproj/commonlib$ git push
提交并推送子模块的更改后,我们可以看到主项目的子模块引用,即myproj,也已更改,然后我们可以做同样的事情来更新引用。然后,myproj将附加较新的commonlib。
- user:~/myproj/commonlib$ cd ../
- user:~/myproj$ git status
- On branch master
- Your branch is up to date with 'origin/master'.
-
- Changes not staged for commit:
- (use "git add <file>..." to update what will be committed)
- (use "git checkout -- <file>..." to discard changes in working directory)
-
- modified: commonlib (new commits)
-
- no changes added to commit (use "git add" and/or "git commit -a")
-
- user:~/myproj$ git commit -a -m "Update submodule, commonlib"
- user:~/myproj$ git push
情况2:将子模块更新为较新的或特定的版本
当其他人修改commonlib或添加了新功能时,我们可能需要将commonlib子模块更新为较新的版本。
例如,有人就加了一个新的功能greeting3到commonlib。
greeting.py
- def greeting3():
- """Print a simple greeting with the name."""
- print("How's going?")
提交哈希7735cf8460acd03f92e7c0529486c86ec83b2c0e如下所示。
- user2:~$ git clone git@127.0.0.1:commonlib
- user2:~$ cd commonlib
- user2:~/commonlib$ vim commonlib/greeting.py # add greeting3 function as the following
- user2:~/commonlib$ git commit -a -m "Added greeting3 function."
- user2:~/commonlib$ git push
- user2:~/commonlib$ git log
- commit 7735cf8460acd03f92e7c0529486c86ec83b2c0e (HEAD -> master, origin/master, origin/HEAD)
- Author: user2 <user2@email.com>
- Date: Sun Dec 22 00:27:09 2019 +0000
-
- Added greeting3 function.
我们将子模块更新为较新版本或特定版本的方法是更新子模块指向的提交哈希。
Git子模块官方文档说:“子模块存储库处于指向特定提交的分离HEAD状态。更改提交仅涉及签出其他标签或提交,然后将更改添加到父存储库。”
以下是更新子模块以提交的示例hash7735cf8460acd03f92e7c0529486c86ec83b2c0e。
- user:~/myproj$ cd commonlib
- user:~/myproj/commonlib$ git pull
- user:~/myproj/commonlib$ git checkout 7735cf8460acd03f92e7c0529486c86ec83b2c0e
- Note: checking out '7735cf8460acd03f92e7c0529486c86ec83b2c0e'.
-
- You are in 'detached HEAD' state. You can look around, make experimental
- changes and commit them, and you can discard any commits you make in this
- state without impacting any branches by performing another checkout.
-
- If you want to create a new branch to retain commits you create, you may
- do so (now or later) by using -b with the checkout command again. Example:
-
- git checkout -b <new-branch-name>
-
- HEAD is now at 7735cf8 Added greeting3 function.
- user:~/myproj/commonlib$ cd ..
- user:~/myproj$ git status
- On branch master
- Your branch is up to date with 'origin/master'.
-
- Changes not staged for commit:
- (use "git add <file>..." to update what will be committed)
- (use "git checkout -- <file>..." to discard changes in working directory)
-
- modified: commonlib (new commits)
-
- no changes added to commit (use "git add" and/or "git commit -a")
- user:~/myproj$ git commit -a -m "Update submodule, commonlib, to the newer one."
- user:~/myproj$ git push
开发模式是安装工具(setuptools)提供的功能,因此与编写用于打包Python项目的setup.py没什么不同。但是,当一个Python项目中有另一个Python项目作为子模块,而我们想将该子模块安装为开发模式时,则需要将该子模块添加到主项目的requirements.txt文件中。例如,myproj的requirements.txt可以是以下内容。
- # Install commonlib as development mode
- -e ./commonlib # Path to the submodule
因此,当我们安装myproj的依赖项时,commonlib将自动安装为开发模式。
当我们处理包含多个较小项目的大项目时,会同时需要同时处理主项目及其从属项目。在这种情况下,我们通常与其他团队一起工作。针对这种情况的建议工作流程分为两个阶段:设置阶段和工作阶段。
此阶段准备代码和工作环境。
1.创建一个虚拟环境
2.使用 --recurse-submodules下载源代码。--recurse-submodules将下载所有子模块。
$ git clone --recurse-submodules <URL_to_the_repository>
3.签出分支。通常,当我们处理某个功能或修复错误时,我们将为该工作创建一个分支。我们应该避免直接与master(或develop)分支工作。关于此的更多信息可以在https://guides.github.com/introduction/flow/中找到
$ git checkout <branch_name>
4.将依赖项安装到虚拟环境中。
这一阶段表明我们正努力解决我们的问题。除了代码更改外,还有两种情况需要修改子模块。
情况1:如果我们需要对子模块进行一些代码更改:
情况2:某人更新了一个存储库,这是我们的子模块,我们想将该子模块更新为较新的提交:
当我们同时从事多个相关项目时,很容易出错。当我们必须在这种情况下工作时,开发模式和子模块提供了一种管理项目的简便方法。一开始使用开发模式和子模块可能并不容易。但是一旦我们熟悉了它的使用,开发模式和子模块的结合不仅可以防止我们犯错误,而且可以提高生产率。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。