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 方法。
  1. repo 对象是 class _Repo(object) 类的一个实例,在 main.py 里面定义的。
  2. 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)

  1. cmd.Execute(copts, cargs) 这里就开始执行子命令里面的 Execute 方法了。
  2. 接着执行 subcmds/sync.py 里面的 Execute 方法了。

3.第3步:

1
2
def Execute(self, opt, args):
fetched = self._Fetch(to_fetch, opt)
  1. opt 就是命令参数选项,例如 -j1, -c, -d 这些
  2. 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)

  1. 这里的 projects 是 清单文件里面的project或者是命令行传入的。
  2. 这里为什么转换 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)

  1. 这一步会判断 self.jobs 是不是大于1,大于1就会使用多线程来更新代码仓库的。
  2. 多线程这里的 target 是一个callable的方法。
  3. 这里是搞了一堆 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()

  1. 这里又封装了一层,开始对 projects 列表里面的仓库进行遍历,然后分别串行的调用 self._FetchHelper(opt, project, *args, **kwargs) 方法了。
  2. 在上一步是会判断是否需要使用多线程的,多线程的情况也是调用到 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)


  1. 在_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()

  1. 这个 _InitGitDir 里面会 初始化 .repo/project-objects 和 .repo/projects 下面的仓库。
  2. 会调用 self.bare_objdir.init() 进行仓库的初始化,self.bare_objdir 是 class _GitGetByExec(object) 类的实例。self.bare_objdir 是 project 实例里面的成员变量。
  3. class _GitGetByExec(object) 类 都是对 git 命令的一个封装,调用 init() 方法其实就是执行 git init 命令。
  4. 在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):

  1. gitdir 是 .repo/project-objects/abl/tianocore/edk2.git。 dotgit 是 .repo/projects/bootable/bootloader/edk2.git
  2. 需要进行软连接的文件有 [‘description’, ‘info’]. 需要进行软连接的目录有 [‘hooks’, ‘objects’, ‘rr-cache’, ‘svn’]
  3. 关于 share_refs参数,后续在进行 worktree的初始化的时候 才会是 True。 这里目前都是 False。
  4. 后续进行 worktree 初始化的时候会做软连接的 有 [‘config’, ‘packed-refs’, ‘shallow’] 和 [‘logs’, ‘refs’]
  5. 经过软连接之后 如下:
    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')

  1. 判断是否需要引用 到 mirror 目录下面的 objects 数据。

10.第10步

1
2
3
4
5
6
self._UpdateHooks()


def _UpdateHooks(self):
if os.path.exists(self.gitdir):
self._InitHooks()
  1. 处理 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()
  1. 这一步是把.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
  1. 这里的self.worktree = u’bootable/bootloader/edk2’ 需要检出代码的一个路径。
  2. 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")


  1. 在 _InitWorkTree 方法里面 调用了 read-tree 把代码检出了。