repo sync 子命令源码分析 使用的repo 版本是 v1.13.2
源码文件在subcmds/sync.py
从 repo sync -cdj1 abl/tianocore/edk2 执行开始开始说起 TODO 后续再讲一下 -c -d -j 这几个参数作用,在源码里面是如何影响repo sync的行为的。
1.第1步: 1 2 3 4 def _Main(argv): 入口在这里的,main.py 的 _Main 方法这里, ...... result = repo._Run(argv) or 0 然后调用到这里的 _Run 方法。
repo 对象是 class _Repo(object) 类的一个实例,在 main.py 里面定义的。
repo 里面有2个成员变量 self.repodir 和 self.commands
self.repodir 值是 ‘xxxx/.repo’ 目录
self.commands 是一个字典,每个key都是 repo 子命令,例如 init,sync 这些。对应的value是 subcmds 下面的类实例对象,也就是这些子命令真正实现的类实例对象
2.第2步: 1 2 3 def _Run(self, argv): 然后调用到这里。 result = cmd.Execute(copts, cargs)
cmd.Execute(copts, cargs) 这里就开始执行子命令里面的 Execute 方法了。
接着执行 subcmds/sync.py 里面的 Execute 方法了。
3.第3步: 1 2 def Execute(self, opt, args): fetched = self._Fetch(to_fetch, opt)
opt 就是命令参数选项,例如 -j1, -c, -d 这些
to_fetch 这里是 project.Project 类型的对象的一个列表,这里是只有一个原生, 因为我们命令行上只传入了一个。如果不传就会使用 manifest xml 里面的 project。 也就是命令行里面传入的 一个仓库名 abl/tianocore/edk2 所对应的封装。
4.第4步 1 2 3 4 objdir_project_map = dict() for project in projects: objdir_project_map.setdefault(project.objdir, []).append(project)
这里的 projects 是 清单文件里面的project或者是命令行传入的。
这里为什么转换 objdir_project_map???这里以每个project的objdir作为key,转换为字典了。 每个字典的值是一个列表。其实就是 manifest xml 里面name是多个的情况,这里统一转换为 字典的key, 这样name就是唯一的,然后name对应的多个path的更新就是在列表里面通过循环串行执行了,估计是防止并发引起的问题的。 执行的顺序是先把 .repo/下面的 .repo/project-objects .repo/projects 目录仓库初始化更新完成。如果name有多个,然后分到不同的线程里面处理了就会引起并发问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def _Fetch(self, projects, opt): kwargs = dict(opt=opt, projects=project_list, sem=sem, lock=lock, fetched=fetched, pm=pm, err_event=err_event) if self.jobs > 1: t = _threading.Thread(target = self._FetchProjectList, kwargs = kwargs) # Ensure that Ctrl-C will not freeze the repo process. t.daemon = True threads.add(t) t.start() else: self._FetchProjectList(**kwargs)
这一步会判断 self.jobs 是不是大于1,大于1就会使用多线程来更新代码仓库的。
多线程这里的 target 是一个callable的方法。
这里是搞了一堆 thead,然后同时调用start() 方法了,怎么保证jobs个数个执行呢?是利用了 sem = _threading.Semaphore(self.jobs) 信号量。 后续的高版本的 repo 这里好像就优化了,使用线程数了???
5.第5步 1 2 3 4 5 6 7 8 9 def _FetchProjectList(self, opt, projects, sem, *args, **kwargs): try: for project in projects: success = self._FetchHelper(opt, project, *args, **kwargs) if not success and not opt.force_broken: break finally: sem.release()
这里又封装了一层,开始对 projects 列表里面的仓库进行遍历,然后分别串行的调用 self._FetchHelper(opt, project, *args, **kwargs) 方法了。
在上一步是会判断是否需要使用多线程的,多线程的情况也是调用到 self._FetchProjectList 方法里面的
6.第6步 1 2 3 4 5 6 7 8 9 10 11 def _FetchHelper(self, opt, project, lock, fetched, pm, err_event): success = project.Sync_NetworkHalf( quiet=opt.quiet, current_branch_only=opt.current_branch_only, force_sync=opt.force_sync, clone_bundle=not opt.no_clone_bundle, no_tags=opt.no_tags, archive=self.manifest.IsArchive, optimized_fetch=opt.optimized_fetch, prune=opt.prune)
在_FetchHelper里面就调用 project 对象里面的 Sync_NetworkHalf() 方法了
7.第7步 1 2 3 4 5 6 7 if archive:# 这里是 False self._FetchArchive(tarpath, cwd=topdir) if is_new: # 这里是 True self._InitGitDir(force_sync=force_sync) # 参数定义: def _InitGitDir(self, mirror_git=None, force_sync=False):
这里进行 self.objdir = ‘.repo/project-objects/abl/tianocore/edk2.git’
1 2 3 4 5 6 7 8 9 def _InitGitDir(self, mirror_git=None, force_sync=False): init_git_dir = not os.path.exists(self.gitdir) init_obj_dir = not os.path.exists(self.objdir) try: # Initialize the bare repository, which contains all of the objects. if init_obj_dir: os.makedirs(self.objdir) self.bare_objdir.init()
这个 _InitGitDir 里面会 初始化 .repo/project-objects 和 .repo/projects 下面的仓库。
会调用 self.bare_objdir.init() 进行仓库的初始化,self.bare_objdir 是 class _GitGetByExec(object) 类的实例。self.bare_objdir 是 project 实例里面的成员变量。
class _GitGetByExec(object) 类 都是对 git 命令的一个封装,调用 init() 方法其实就是执行 git init 命令。
在class _GitGetByExec(object) 类里面有个 getattr 方法,里面会返回 一个 runner 方法,这里面会实例化 GitCommand 对象,这个对象会 调用 subprocess 执行外部的git命令的。
这里进行 self.gitdir = ‘.repo/projects/bootable/bootloader/edk2.git’ 的初始化
1 2 3 4 5 6 7 8 9 10 11 if self.objdir != self.gitdir: if init_git_dir: os.makedirs(self.gitdir) if init_obj_dir or init_git_dir: self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False, copy_all=True) def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
gitdir 是 .repo/project-objects/abl/tianocore/edk2.git。 dotgit 是 .repo/projects/bootable/bootloader/edk2.git
需要进行软连接的文件有 [‘description’, ‘info’]. 需要进行软连接的目录有 [‘hooks’, ‘objects’, ‘rr-cache’, ‘svn’]
关于 share_refs参数,后续在进行 worktree的初始化的时候 才会是 True。 这里目前都是 False。
后续进行 worktree 初始化的时候会做软连接的 有 [‘config’, ‘packed-refs’, ‘shallow’] 和 [‘logs’, ‘refs’]
经过软连接之后 如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ tree .repo/project-objects/abl/tianocore/edk2.git .repo/projects/bootable/bootloader/edk2.git -L 1 -p .repo/project-objects/abl/tianocore/edk2.git ├── [drwxrwxr-x] branches ├── [-rw-rw-r--] config ├── [-rw-rw-r--] description ├── [-rw-rw-r--] HEAD ├── [drwxrwxr-x] hooks ├── [drwxrwxr-x] info ├── [drwxrwxr-x] objects ├── [drwxrwxr-x] refs ├── [drwxrwxr-x] rr-cache └── [drwxrwxr-x] svn .repo/projects/bootable/bootloader/edk2.git ├── [drwxrwxr-x] branches ├── [-rw-rw-r--] config ├── [lrwxrwxrwx] description -> ../../../../project-objects/abl/tianocore/edk2.git/description ├── [-rw-rw-r--] HEAD ├── [lrwxrwxrwx] hooks -> ../../../../project-objects/abl/tianocore/edk2.git/hooks ├── [lrwxrwxrwx] info -> ../../../../project-objects/abl/tianocore/edk2.git/info ├── [lrwxrwxrwx] objects -> ../../../../project-objects/abl/tianocore/edk2.git/objects ├── [drwxrwxr-x] refs ├── [lrwxrwxrwx] rr-cache -> ../../../../project-objects/abl/tianocore/edk2.git/rr-cache └── [lrwxrwxrwx] svn -> ../../../../project-objects/abl/tianocore/edk2.git/svn
8.第8步 1 2 3 4 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
9.第9步 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 if init_git_dir: mp = self.manifest.manifestProject ref_dir = mp.config.GetString('repo.reference') or '' if ref_dir or mirror_git: if not mirror_git: mirror_git = os.path.join(ref_dir, self.name + '.git') repo_git = os.path.join(ref_dir, '.repo', 'projects', self.relpath + '.git') if os.path.exists(mirror_git): ref_dir = mirror_git elif os.path.exists(repo_git): ref_dir = repo_git else: ref_dir = None if ref_dir: if not os.path.isabs(ref_dir): # The alternate directory is relative to the object database. ref_dir = os.path.relpath(ref_dir, os.path.join(self.objdir, 'objects')) _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'), os.path.join(ref_dir, 'objects') + '\n')
判断是否需要引用 到 mirror 目录下面的 objects 数据。
10.第10步 1 2 3 4 5 6 self._UpdateHooks() def _UpdateHooks(self): if os.path.exists(self.gitdir): self._InitHooks()
处理 commmit-msg hooks 文件。 commit-msg -> ../../../../../../../hooks/commit-msg 做成软连接。
以上 self._InitGitDir(force_sync=force_sync)的调用 都紧紧是 .repo/project-objects/abl/tianocore/edk2.git .repo/projects/bootable/bootloader/edk2.git 这2个目录仓库的初始化。还没有到 worktree目录的初始化的。
11.第11步 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 self._InitRemote() def _InitRemote(self): if self.remote.url: remote = self.GetRemote(self.remote.name) remote.url = self.remote.url remote.pushUrl = self.remote.pushUrl remote.review = self.remote.review remote.projectname = self.name if self.worktree: remote.ResetFetch(mirror=False) else: remote.ResetFetch(mirror=True) remote.Save()
这一步是把.repo/projects/bootable/bootloader/edk2.git/config中配上 [remote “shgit”] 相关的信息1 2 3 4 5 6 [remote "shgit"] url = ssh://gerrit.xxx.com:29418/git/android/abl/tianocore/edk2 review = gerrit.xxxx.com projectname = abl/tianocore/edk2 fetch = +refs/heads/*:refs/remotes/shgit/*
12.第12步 1 2 3 4 5 6 7 8 if self.worktree: self._InitMRef() else: self._InitMirrorHead() try: platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD')) except OSError: pass
这里的self.worktree = u’bootable/bootloader/edk2’ 需要检出代码的一个路径。
self._InitMRef() 是更新代码调用的, self._InitMirrorHead() 是更新mirror时候调用,最后都会调用到 self._InitAnyMRef(…) 里面。
以上 self._Fetch(to_fetch, opt) –> project.Sync_NetworkHalf() 的调用,还紧紧是 更新了 .repo 目录下面仓库。代码目录还没有检出。 后面还会有个 project.Sync_LocalHalf() 的调用。
self._Fetch(to_fetch, opt) 的调用完毕之后 又回到了 sync.py 文件中的 Execute() 方法里面了。
此时如果opt.network_only是True就不会再继续往下执行了,可以知道 –network-only 选项就紧紧是控制.repo 下面数据的更新的。
13.第13步 1 2 3 4 5 6 7 8 9 10 11 12 13 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False): self._InitWorkTree(force_sync=force_sync, submodules=submodules) if init_dotgit: _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId()) cmd = ['read-tree', '--reset', '-u'] cmd.append('-v') cmd.append(HEAD) if GitCommand(self, cmd).Wait() != 0: raise GitError("cannot initialize work tree")
在 _InitWorkTree 方法里面 调用了 read-tree 把代码检出了。