repo diffmanifests 子命令源码分析

这个命令的作用是比较2个 manifest xml 文件的差异的,比较里面每个仓库的git log差异,
一般用来对比2个构建版本之间的提交差异,生成 changelog 使用的。

效果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
repo diffmanifests old.xml new.xml

changed projects :

git-repo changed from 3bbbcaf99d6137b3ca88dd8bb0acc8733cf1e6be to 13f323b2c221db4e69e3f5a671455954b65f1fb3
[+] 13f323b event_log: turn id generation from a generator to a func call
[+] 12ee544 init: Remove -c short option for --current-branch
[+] e158e38 Merge "README: link in new bug tracker"
[+] d4b13c2 Leverage the next keyword from python 2.7

manifest changed from 09b321869e40aaac61ce91bba72ad884b3529122 to 372366704d46173df5b0ba6e5887beb6a8d0cbec
[+] 3723667 update magesfc/default.xml
[+] 05a820b update magesfc/default.xml
[+] a5ce0e6 add magesfc/default.xml
[+] d36f554 update README.md.
[+] 12689ea update README.md.
[+] 4763a14 add README.md.

使用的 repo init 命令如下:

1
2
3
4
5
repo init --repo-url https://gitee.com/mamh-mixed/git-repo --repo-branch stable-v1.13.2  --reference /home/mirror \
-u https://gitee.com/mamh-mixed/manifest -b master -m magesfc/default.xml



使用的repo 版本是 v1.13.2

从 diffmanifests old.xml new.xml 执行开始说起

1. Execute() 方法入口

1
2
3
def Execute(self, opt, args):
if not args or len(args) > 2:
self.Usage()

此时 opt, args 2个参数值如下:

1
2
3
4
5
6
7
8
args = {list: 2} ['old.xml', 'new.xml']
0 = {str} 'old.xml'
1 = {str} 'new.xml'

opt = {Values} {'color': True, 'raw': None, 'pretty_format': None}
color = {bool} True # 可以看到默认其他参数选项不带的时候,这个color是开启的状态
pretty_format = {NoneType} None
raw = {NoneType} None

2. self.out 的初始化

1
self.out = _Coloring(self.manifest.globalConfig)

class _Coloring 是 继承了 class Coloring。这个类来自 color.py 文件里面

1
2
3
4
5
6
7
8
9
10
11
12
class Coloring(object):
def __init__(self, config, section_type):
self._section = 'color.%s' % section_type
self._config = config
self._out = sys.stdout


class _Coloring(Coloring):
def __init__(self, config):
Coloring.__init__(self, config, "status")

#

在实例化 _Coloring 类的时候,传入了一个 self.manifest.globalConfig,他是一个 GitConfig 对象(来自 git_config.py 文件中的 class GitConfig(object)),如下:

1
2
3
globalConfig = {GitConfig} <git_config.GitConfig object at 0x7f68e172dc10>
defaults = {NoneType} None
file = {str} '/home/mamh/.gitconfig'

这个 self.manifest 成员变量是在什么时候初始化的呢? 在class Diffmanifests(PagedCommand)里面都没有找到,在她的父类里面也没找到,

最后发现是在 main.py 文件中的class _Repo(object):里面进行的初始化 cmd.manifest = XmlManifest(cmd.repodir)

  1. 实例化 _Coloring 的时候 第一个参数是 self.manifest.globalConfig

  2. _Coloring __init__ 方法里面会调用 父类 Coloring 的 __init__ 方法, 第二个参数是固定死的 "status"

  3. 最终效果就是 self.out 是一个 Coloring 类的实例,第一个参数是 self.manifest.globalConfig ,第二个参数是 "status"

    a. 这个参数 "status" 会拼接 'color.%s' % section_type 得到 color.status, 这个是 git config 里面可能会配置的一项.

    b. 查看 git config 的说明:

    1
    2
    3
    4
    color.status
    A boolean to enable/disable color in the output of git-status(1). May be set to always, false (or never) or
    auto (or true), in which case colors are used only when the output is to a terminal. If unset, then the value
    of color.ui is used (auto by default).
  4. 这2个参数有什么作用,展开说明一下这个 class Coloring 类中的__init__方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Coloring(object):
    def __init__(self, config, section_type):
    self._section = 'color.%s' % section_type
    self._config = config
    self._out = sys.stdout

    on = DEFAULT
    if on is None:
    on = self._config.GetString(self._section)
    if on is None:
    on = self._config.GetString('color.ui')

    if on == 'auto':
    if pager.active or os.isatty(1):
    self._on = True
    else:
    self._on = False
    elif on in ('true', 'always'):
    self._on = True
    else:
    self._on = False

    a. self._section 就被赋值为 color.status 了。self._config 就是GitConfig实例对象。self._out sys.stdout

    b. 继续往下面的一段代码就是初始化 self._on 了。初始 on 被赋值一个 全局的 DEFAULT。 经过一系列判断,这个 on 会被赋值给 self._on
    初始化 on 的时候可以看到 先去 获取 color.status 有没有配置,没有会去使用 color.ui

    c. 如果 on 是 auto的情况,会判断 pager.active or os.isatty(1), 如果是真,那么 最终 self._on 被赋值 True了。

    d. 如果 on 是 'true', 'always' 那么 最终 self._on 被赋值 True了。其他情况 self._on 会被设置为 False。

    e. 这个 self._on 其实会决定后面的打印输出会不会带上颜色。

  5. self.out 的作用

    1
    self.out.nl()
    1
    2
    def nl(self):
    self._out.write('\n')

    主要作用就是打印换行

2. self.printText 的初始化

1
self.printText = self.out.nofmt_printer('text')
  1. 通过 self.out 调用 nofmt_printer(‘text’) 方法,返回的是一个 method的。

    1
    2
    3
    4
    5
    6
    7
    def nofmt_printer(self, opt=None, fg=None, bg=None, attr=None):
    s = self
    c = self.nofmt_colorer(opt, fg, bg, attr)

    def f(fmt):
    s._out.write(c(fmt))
    return f
  2. 改方法返回了一个def f(fmt):方法,这个 f 方法里面就是调用了s._out.write()效果就是sys.stdout.write()

  3. write() 方法里面的参数是会经过 c(fmt) 包装一下的,c 这个也是一个方法,是通过 self.nofmt_colorer(opt, fg, bg, attr) 包装出来的一个方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def nofmt_colorer(self, opt=None, fg=None, bg=None, attr=None):
    if self._on:
    c = self._parse(opt, fg, bg, attr)

    def f(fmt):
    return ''.join([c, fmt, RESET])
    return f
    else:
    def f(fmt):
    return fmt
    return f
  4. 这个nofmt_colorer方法比较绕,发主要就是返回另外一个方法,赋值给 c 变量使用, c(fmt) 经过这样去调用,把需要打印的字符串fmt包装一下,返回一个新的格式的字符串fmt

    a. 如果self._on是 False 的情况,返回一个

    1
    2
    def f(fmt):
    return fmt

    这样的方法,这个时候 其实是什么都没有做,这样经过 c(fmt) 调用,返回的还是原来的fmt的内容。

    b. 如果 如果self._on是 True 的情况,返回一个

    1
    2
    3
    4
    5
    c = self._parse(opt, fg, bg, attr)

    def f(fmt):
    return ''.join([c, fmt, RESET])
    return f

    这样的方法,这个时候,这样经过 c(fmt) 调用,返回的是 ''.join([c, fmt, RESET]), 其实就是在fmt的前后加上了一个 c 和一个 RESET。

    c. c 是什么东东呢? c 是 self._parse(opt, fg, bg, attr) 调用后返回的一个值。

    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
    def _parse(self, opt, fg, bg, attr):
    if not opt:
    return _Color(fg, bg, attr)

    v = self._config.GetString('%s.%s' % (self._section, opt))
    if v is None:
    return _Color(fg, bg, attr)

    v = v.strip().lower()
    if v == "reset":
    return RESET
    elif v == '':
    return _Color(fg, bg, attr)

    have_fg = False
    for a in v.split(' '):
    if is_color(a):
    if have_fg:
    bg = a
    else:
    fg = a
    elif is_attr(a):
    attr = a

    return _Color(fg, bg, attr)

    这个 self._parse(opt, fg, bg, attr) 也是经过一系列判断,最终返回 _Color(fg, bg, attr) 方法调用的返回值。
    它有有三个参数,fg 表示前景颜色, bg表示背景颜色,attr 表示属性。

    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    def _Color(fg=None, bg=None, attr=None):
    fg = COLORS[fg]
    bg = COLORS[bg]
    attr = ATTRS[attr]

    if attr >= 0 or fg >= 0 or bg >= 0:
    need_sep = False
    code = "\033["

    if attr >= 0:
    code += chr(ord('0') + attr)
    need_sep = True

    if fg >= 0:
    if need_sep:
    code += ';'
    need_sep = True

    if fg < 8:
    code += '3%c' % (ord('0') + fg)
    else:
    code += '38;5;%d' % fg

    if bg >= 0:
    if need_sep:
    code += ';'

    if bg < 8:
    code += '4%c' % (ord('0') + bg)
    else:
    code += '48;5;%d' % bg
    code += 'm'
    else:
    code = ''
    return code

    其实也是经过一系列判断,返回一串字符串的。 _Color("red","blue","dim") 这样调用 返回一个 '\033[2;31;44m' 这样的字符串,它表示的就是蓝色背景红色字体的。

    d. RESET 是什么东东呢?RESET是一个写死的值: RESET = "\033[m". 这个值是终端上打印彩色的时候重置颜色的一个特殊的值。

    e. 这里在初始化self.printText的时候是没有传递fg=None, bg=None, attr=None三个参数的。其实最终的效果self.printText()就是sys.out.write()的调用效果

    f. opt 这个参数的作用,是要和 color.status 进行拼接的。 color.status.text 。 这个可以配置reset和配置颜色 normal black red green yellow blue magenta cyan white或者是属性 bold dim ul blink reverse

    1
    2
    3
    4
    5
    $ head ~/.gitconfig 
    [color]
    ui = true
    [color "status"]
    text = reset

    如果这个配置文件里面这样配置了

    如果self._config.GetString("color.ui") 这样调用会,得到 ~/.gitconfig 中的 true。

    如果self._config.GetString("color.ui.text") 这样调用会,得到 ~/.gitconfig 中的 reset。

    如果text = reset, 经过 self._parse(opt, fg, bg, attr)调用后返回的是 RESET = "\033[m" 这个值。

    另外一种配置方式就是3个值分别表示 fg,bg,attr,例如text = black red blod

    这里的 have_fg 变量没有被再次赋值呀,是有bug的呀。。。。作者的意思应该是先判断fg是否被赋值了,赋值过之后第二个颜色应该是要给到bg上了。

3. 其他几个 print 方法的初始化

1
2
3
4
5
6
7
if opt.color:
self.printProject = self.out.nofmt_printer('project', attr = 'bold')
self.printAdded = self.out.nofmt_printer('green', fg = 'green', attr = 'bold')
self.printRemoved = self.out.nofmt_printer('red', fg = 'red', attr = 'bold')
self.printRevision = self.out.nofmt_printer('revision', fg = 'yellow')
else:
self.printProject = self.printAdded = self.printRemoved = self.printRevision = self.printText
  1. 如果命令行上有 –no-color 会影响到 opt.color 的值。默认这个是True的。
  2. 这几个方法的初始化和上面self.printText都比较类似,无非就是传递给 nofmt_printer(self, opt=None, fg=None, bg=None, attr=None) 方法的参数值不一样。
  3. 从中可以发现 gitconfig 下面的 color.status 下面还可以 配置 'project' 'green' 'red' 'revision'
    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ head ~/.gitconfig 
    [color]
    ui = true
    [color "status"]
    text = black red dim # 这个会影响` changed projects` `removed projects` `added projects` 这3行的颜色。其实不单单颜色还有样式的,例如粗体字,下划线,闪烁
    project = black red dim # 这个控制仓库名称的显示的颜色的。
    green = black red dim # 这个控制[+] 和 [-] 这个方括号这一段的颜色的。默认是加号绿色的。
    red = black red dim # 这个控制[+] 和 [-] 这个方括号这一段的颜色的。默认是减号红色的。
    revision = black red dim # 控制 from to 之间的 revision 的颜色的。
  4. 默认情况下:仓库名称, revision, 加 减 号 都是会有颜色样式显示的。 如果命令行上带 –no-color 的时候:这几个都会是self.printText
  5. 如果带命令行上带 –no-color 的时候, 并且 text 选项没有设置的化,是不会显示颜色的,但是输出的文本内容中会有 RESET 这个特殊字符。这里的行为感觉也有点小bug。

    如果要解析这样的命令输出的化要特别注意。 可以在repo后面带上–color=never这样全局的禁用颜色样式和特殊字符了。

4. XmlManifest 的初始化

1
2
3
4
5
6
7
manifest1 = XmlManifest(self.manifest.repodir)
manifest1.Override(args[0])
if len(args) == 1:
manifest2 = self.manifest
else:
manifest2 = XmlManifest(self.manifest.repodir)
manifest2.Override(args[1])
  1. 这一段代码会初始化2个 XmlManifest 类的实例, 命令行参数会带上2个xml文件名,默认第一个的xml文件会和manifest1实例绑定。

    如果没有第二个会使用 .repo/manifest.xml 这个xml文件和manifest2实例绑定。如果有第二个xml参数,那么就第2个的xml文件会和manifest2实例绑定。

    1
    2
    3
    args = {list: 2} ['old.xml', 'new.xml']
    0 = {str} 'old.xml'
    1 = {str} 'new.xml'

    我们测试的命令行参数是带的2个xml文件名的。

    self.manifest.repodir 这个路径就是 .repo/ 这个路径,转换过的一个绝对路径。

4. 对比2个xml获取差异信息

其实就是对比2个xml里面的各个仓库的git log的差异。最终的调用的命令就是git log oldRev..newRev

1
diff = manifest1.projectsDiff(manifest2)

4.1 projectsDiff() 方法

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
28
29
def projectsDiff(self, manifest):
fromProjects = self.paths
toProjects = manifest.paths

fromKeys = sorted(fromProjects.keys())
toKeys = sorted(toProjects.keys())

diff = {'added': [], 'removed': [], 'changed': [], 'unreachable': []}

for proj in fromKeys:
if not proj in toKeys:
diff['removed'].append(fromProjects[proj])
else:
fromProj = fromProjects[proj]
toProj = toProjects[proj]
try:
fromRevId = fromProj.GetCommitRevisionId()
toRevId = toProj.GetCommitRevisionId()
except ManifestInvalidRevisionError:
diff['unreachable'].append((fromProj, toProj))
else:
if fromRevId != toRevId:
diff['changed'].append((fromProj, toProj))
toKeys.remove(proj)

for proj in toKeys:
diff['added'].append(toProjects[proj])

return diff

这个方法在manifest_xml.py文件里面

  1. 首先是获取了两个 paths 相关的信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fromProjects = self.paths     # 获取第1个manifest xml里面的project信息
    toProjects = manifest.paths # 获取第2个manifest xml里面的project信息

    fromKeys = sorted(fromProjects.keys()) # 对字典的key进行排序
    toKeys = sorted(toProjects.keys())

    @property
    def paths(self):
    self._Load()
    return self._paths
    1
    2
    3
    fromProjects = {dict: 2} {u'git-repo': <project.Project object at 0x7efce8971a90>, u'git-repo-go': <project.Project object at 0x7efce8971c50>}
    u'git-repo' = {Project} <project.Project object at 0x7efce8971a90>
    u'git-repo-go' = {Project} <project.Project object at 0x7efce8971c50>

    是一个字典,其实就是解析manifest xml得到里面的所有的project相关的信息。以path作为字典的key了。

  2. 定义一个 diff 字典,后续用来存放差异信息的

    1
    diff = {'added': [], 'removed': [], 'changed': [], 'unreachable': []}

    这个对比其实就是2个集合关系的比较。集合的交集,补集等。

    added为新增的仓库,removed是删除的仓库,这2种就是2个集合的补集。

    changed的就是集合的交集的仓库。

    unreachable 表示不可达的仓库,revision是不在这个仓库里面的。

  3. 开始循环fromKeys这个集合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    for proj in fromKeys:
    if not proj in toKeys:
    diff['removed'].append(fromProjects[proj])
    else:
    fromProj = fromProjects[proj]
    toProj = toProjects[proj]
    try:
    fromRevId = fromProj.GetCommitRevisionId()
    toRevId = toProj.GetCommitRevisionId()
    except ManifestInvalidRevisionError:
    diff['unreachable'].append((fromProj, toProj))
    else:
    if fromRevId != toRevId:
    diff['changed'].append((fromProj, toProj))
    toKeys.remove(proj)

    a. 开始这里 判断 不在 toKeys 集合里面的仓库就是被移除删除的情况

    1
    2
    3
    for proj in fromKeys:  # from 是旧的,是左边, to是新的,是右边。 遍历旧的集合,发现不在新的集合里面的那就是删除的情况。直接是追加到` diff['removed'] `对应的列表里面。
    if not proj in toKeys:
    diff['removed'].append(fromProjects[proj])

    b. 交集的情况,就是 from 和 to 两个集合都存在的仓库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    else:
    fromProj = fromProjects[proj]
    toProj = toProjects[proj]
    try:
    fromRevId = fromProj.GetCommitRevisionId()
    toRevId = toProj.GetCommitRevisionId()
    except ManifestInvalidRevisionError:
    diff['unreachable'].append((fromProj, toProj))
    else:
    if fromRevId != toRevId:
    diff['changed'].append((fromProj, toProj))
    toKeys.remove(proj)

    分别获取from的仓库和to的仓库,然后分别获取到对应的revision id值,保存在 fromRevId 和 toRevId 两个变量里面。

    只有 fromRevId 和 toRevId 不相等的情况,才会把这2个revision id值追加到diff['changed']对应的列表里面。如果这2个revid是相等说明这个仓库没有改变,说明提交历史是一致的。

    在最后还需要 toKeys.remove(proj) 把它从右边to那个集合里面删除掉。这个是为 后面判断 diff['added'] 的情况做准备的。

    c. 最后处理新增的仓库的情况, 把 to 那个集合里面剩下的遍历一遍就是了。因为上面在循环的过程中把 相同仓库的情况已经从 to 集合里面删除掉了。所以剩下的没有被处理的就是新增的仓库了。

    1
    2
    for proj in toKeys:  # toKeys 集合只是path,这里是把对应的 project获取到,追加到列表里面。
    diff['added'].append(toProjects[proj])

5. 打印差异信息

1
2
3
4
if opt.raw:
self._printRawDiff(diff)
else:
self._printDiff(diff, color=opt.color, pretty_format=opt.pretty_format)
  1. 命令行上的--raw选项会影响opt.raw的值,默认是false的,带上这个选项才会变成True的。其实就是最终的打印格式不太一样
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    repo diffmanifests old.xml new.xml --raw
    A manifest 372366704d46173df5b0ba6e5887beb6a8d0cbec

    R git-repo-go 8de1b394acad3db7c6b0b691be045f897082fdce

    C git-repo 13f323b2c221db4e69e3f5a671455954b65f1fb3 3bbbcaf99d6137b3ca88dd8bb0acc8733cf1e6be
    R 13f323b event_log: turn id generation from a generator to a func call
    R 12ee544 init: Remove -c short option for --current-branch
    R e158e38 Merge "README: link in new bug tracker"
    R d4b13c2 Leverage the next keyword from python 2.7

    带上--raw的效果如上。 很类似git status --short那样的。

5.1 调用 _printRawDiff 打印差异信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def _printRawDiff(self, diff):
for project in diff['added']:
self.printText("A %s %s" % (project.relpath, project.revisionExpr))
self.out.nl()

for project in diff['removed']:
self.printText("R %s %s" % (project.relpath, project.revisionExpr))
self.out.nl()

for project, otherProject in diff['changed']:
self.printText("C %s %s %s" % (project.relpath, project.revisionExpr,
otherProject.revisionExpr))
self.out.nl()
self._printLogs(project, otherProject, raw=True, color=False)

for project, otherProject in diff['unreachable']:
self.printText("U %s %s %s" % (project.relpath, project.revisionExpr,
otherProject.revisionExpr))
self.out.nl()

  1. 里面是按照那个 diff 字典来打印的,先处理 diff['added'] 的情况,然后是 diff['removed'], 再次 diff['changed'], 最后是 diff['unreachable']

  2. 打印新增仓库的情况 和 删除仓库的情况,这2个情况是一样的。

    a. 循环遍历即可,打印 "A" 或者是 "R" ,仓库路径, revision ID。打印一个换行\n

    b. 这里的 self.printText 方法就是上面讲过的 通过 self.out.nofmt_printer('text') 获取的,其实最终效果就是 调用的 sys.out.write() 方法

    c. 效果如下

    1
    2
    A manifest 372366704d46173df5b0ba6e5887beb6a8d0cbec
    R git-repo-go 8de1b394acad3db7c6b0b691be045f897082fdce
  3. 打印共有的仓库的情况。

    a. 循环遍历。 打印"C",打印仓库路径,打印左侧的revisionID,打印右侧的revisionID,打印换行符。

    b. 调用 self._printLogs(project, otherProject, raw=True, color=False) 打印2个revID之间的git log的差异信息。后面再展开这个方法。 这里会打印R开头的或者A开头的。其实和另外一种打印格式中的 [+] [-] 相对应了。

    c. 效果如下

    1
    2
    3
    4
    5
    6
    C git-repo 13f323b2c221db4e69e3f5a671455954b65f1fb3 3bbbcaf99d6137b3ca88dd8bb0acc8733cf1e6be
    R 13f323b event_log: turn id generation from a generator to a func call
    R 12ee544 init: Remove -c short option for --current-branch
    R e158e38 Merge "README: link in new bug tracker"
    R d4b13c2 Leverage the next keyword from python 2.7

  4. 打印 unreachable 的情况。

    a. 循环遍历即可,打印 "U %s %s %s", 仓库路径,左侧revision ID, 右侧revision ID。 打印一个换行符。

    b 效果如下

    1
    U git-repo xxxxxx 13f323b2c221db4e69e3f5a671455954b65f1fb3
  5. add的情况打印是带 A 开头的。 removed的情况是打印的 R 开头的。unreachable 的情况是打印的 U 开头的。

5.2 调用 _printDiff 打印差异信息

1
2
3
4
5
6
7
8
9
10
11
12
def _printDiff(self, diff, color=True, pretty_format=None):
if diff['added']:
xxxx

if diff['removed']:
xxxx

if diff['changed']:
xxxx

if diff['unreachable']:
xxxx
  1. 和上面的一样。里面是按照那个 diff 字典来打印的,先处理 diff['added'] 的情况,然后是 diff['removed'], 再次 diff['changed'], 最后是 diff['unreachable']

  2. 打印新增仓库的情况 和 删除仓库的情况,这2个情况是一样的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    self.out.nl()
    self.printText('added projects : \n')
    self.out.nl()

    for project in diff['added']:
    self.printProject('\t%s' % (project.relpath))
    self.printText(' at revision ')
    self.printRevision(project.revisionExpr)
    self.out.nl()

    a. 先打印换行,然后打印 'added projects : \n' 或者是 'removed projects : \n'。打印仓库路径。打印 ' at revision ' 。打印 revision id。

    b. self.printProjectself.printRevision 是2个方法,和 self.printText 是类似的。

    c. 上面合起来打印的效果如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    added projects : 

    manifest at revision 372366704d46173df5b0ba6e5887beb6a8d0cbec

    removed projects :

    git-repo-go at revision 8de1b394acad3db7c6b0b691be045f897082fdce

  3. 打印共有的仓库的情况。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    self.out.nl()
    self.printText('changed projects : \n')
    self.out.nl()

    for project, otherProject in diff['changed']:
    self.printProject('\t%s' % (project.relpath))
    self.printText(' changed from ')
    self.printRevision(project.revisionExpr)
    self.printText(' to ')
    self.printRevision(otherProject.revisionExpr)
    self.out.nl()
    self._printLogs(project, otherProject, raw=False, color=color,
    pretty_format=pretty_format)

    a. 先是打印换行和 ‘changed projects : \n’

    b. 循环打印, 每一个仓库会按如下内容打印:制表符, 仓库路径,' changed from ', 左侧revision id, ' to ', 右侧的 revision id, 换行

    效果如下:

    1
    git-repo changed from 3bbbcaf99d6137b3ca88dd8bb0acc8733cf1e6be to 13f323b2c221db4e69e3f5a671455954b65f1fb3

    c. 然后调用 self._printLogs 方法打印2个reivison id 直接的git log的差异信息。
    效果如下:每一行前面都有2个制表符,起到缩进的效果。

    1
    2
    3
    4
    5
    [+] 13f323b event_log: turn id generation from a generator to a func call
    [+] 12ee544 init: Remove -c short option for --current-branch
    [+] e158e38 Merge "README: link in new bug tracker"
    [+] d4b13c2 Leverage the next keyword from python 2.7

  4. 打印 unreachable 的情况。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    self.out.nl()
    self.printText('projects with unreachable revisions : \n')
    self.out.nl()
    for project, otherProject in diff['unreachable']:
    self.printProject('\t%s ' % (project.relpath))
    self.printRevision(project.revisionExpr)
    self.printText(' or ')
    self.printRevision(otherProject.revisionExpr)
    self.printText(' not found')
    self.out.nl()

    a. 和上面的打印类似。先打印换行,然后打印 'projects with unreachable revisions : \n'

    b. 循环打印, 每一个仓库会按如下内容打印:制表符, 仓库路径, 左侧仓库的revision id, 'or', 左侧仓库的revision id, ' not found', 换行。

    c.效果如下:

    1
    2
    3
    projects with unreachable revisions : 

    git-repo 8de1b394acad3db7c6b0b691be045f897082fdce or 13f323b2c221db4e69e3f5a671455954b65f1fb3 not found

5.3 调用 _printLogs 打印git log 差异信息

1
2
def _printLogs(self, project, otherProject, raw=False, color=True, pretty_format=None):
logs = project.getAddedAndRemovedLogs(otherProject, oneline=(pretty_format is None), color=color, pretty_format=pretty_format)

上面是先获取 logs 信息,然后按换行符截取之后循环打印。

参数 project 是左侧仓库相关信息的一个对象。也可以理解为旧的manifest xml里面的仓库。

参数 otherProject 是右侧仓库相关信息的一个对象。也可以理解为新的manifest xml里面的仓库。

参数 raw 是 True 或者是 False,这个是可以根据 命令行上 是否带 –raw 选项来决定。默认是False的。

参数 color 是 True 或者是 False,这个是可以根据 命令行上 是否带 –no-color 选项来决定。默认是True的。

参数pretty_format是命令行上 是否带 –pretty-format 选项来决定。默认是 None的。

1
2
3
4
5
6
7
8
9
10
11
if logs['removed']:
removedLogs = logs['removed'].split('\n')
for log in removedLogs:
if log.strip():
if raw:
self.printText(' R ' + log)
self.out.nl()
else:
self.printRemoved('\t\t[-] ')
self.printText(log)
self.out.nl()
1
2
3
4
5
6
7
8
9
10
11
if logs['added']:
addedLogs = logs['added'].split('\n')
for log in addedLogs:
if log.strip():
if raw:
self.printText(' A ' + log)
self.out.nl()
else:
self.printAdded('\t\t[+] ')
self.printText(log)
self.out.nl()
  1. 这个方法里面最后打印的时候分了2个情况,一个是打印 logs['removed'] 里面的,一个是打印 logs['added']。 这里就是 对应了 [+] [-] 加号和减号的情况

  2. logs['removed']logs['added'] 就是 git log --oneline 的输出。 每一行是一个 revisoinid 然后跟着这次提交的 subject。

  3. 是 raw 的情况直接打印 一个 ' R ' 然后紧接着是 revisoinid 和 subject了。最后打印一个换行。

  4. 非 raw 的情况 打印 一个 '\t\t[-] ' 或者是 '\t\t[+] ' 然后紧接着是 revisoinid 和 subject了。最后打印一个换行。

    self.printAdded 和 self.printRemoved 是前面提交到的一个打印方法,可以打印带有颜色字的输出。

    self.printText 一般可以认为的打印没有颜色的输出的。简单的可以这样理解。

5.4 调用 getAddedAndRemovedLogs 获取git log 差异信息

1
2
3
4
5
6
7
8
9
10
11
12
def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True, pretty_format=None):
logs = {}

selfId = self.GetRevisionId(self._allrefs)
toId = toProject.GetRevisionId(toProject._allrefs)

logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color, pretty_format=pretty_format)

logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color, pretty_format=pretty_format)

return logs

  1. 首先是 获取旧仓库的revID,然后获取新仓库的revID.
    1
    2
    selfId = {unicode} u'3bbbcaf99d6137b3ca88dd8bb0acc8733cf1e6be'  这个表示旧的
    toId = {unicode} u'13f323b2c221db4e69e3f5a671455954b65f1fb3' 这个表示新的
  2. 然后调用self._getLogs方法,调用2次,分别是 新旧revision ID 交换顺序调用。一种存放到 logs['added'] 。 一种存放到logs['removed']。 其实效果是一样的。
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
def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
comp = '..'
if rev1:
revs = [rev1]
if rev2:
revs.extend([comp, rev2])
cmd = ['log', ''.join(revs)]
out = DiffColoring(self.config)
if out.is_on and color:
cmd.append('--color')
if pretty_format is not None:
cmd.append('--pretty=format:%s' % pretty_format)
if oneline:
cmd.append('--oneline')

try:
log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
if log.Wait() == 0:
return log.stdout
except GitError:
# worktree may not exist if groups changed for example. In that case,
# try in gitdir instead.
if not os.path.exists(self.worktree):
return self.bare_git.log(*cmd[1:])
else:
raise
return None
  1. 举例这里调用:参数 rev1 = u’3bbbcaf99d6137b3ca88dd8bb0acc8733cf1e6be’ 参数 rev2 = u’13f323b2c221db4e69e3f5a671455954b65f1fb3’。oneline = True。 color=True 。pretty_format=None

    最终会调用 git log --oneline 这个命令来生成git log差异信息的。['log', u'3bbbcaf99d6137b3ca88dd8bb0acc8733cf1e6be..13f323b2c221db4e69e3f5a671455954b65f1fb3', '--color', '--oneline']

    最终就是执行git log 3bbbcaf99d6137b3ca88dd8bb0acc8733cf1e6be..13f323b2c221db4e69e3f5a671455954b65f1fb3 --color --oneline

    输出如下:

    1
    2
    3
    4
    13f323b event_log: turn id generation from a generator to a func call
    12ee544 init: Remove -c short option for --current-branch
    e158e38 Merge "README: link in new bug tracker"
    d4b13c2 Leverage the next keyword from python 2.7

    这个命令直接终端上执行是会带 (HEAD -> default, tag: v1.13.2, origin/stable-v1.13.2, origin/stable, gitee/stable-v1.13.2) 这些信息的,

    1
    2
    3
    4
    5
    git log 3bbbcaf99d6137b3ca88dd8bb0acc8733cf1e6be..13f323b2c221db4e69e3f5a671455954b65f1fb3 --color --oneline
    13f323b (HEAD -> default, tag: v1.13.2, origin/stable-v1.13.2, origin/stable, gitee/stable-v1.13.2) event_log: turn id generation from a generator to a func call
    12ee544 (tag: v1.13.1, origin/bs_stable, origin/bs_master) init: Remove -c short option for --current-branch
    e158e38 Merge "README: link in new bug tracker"
    d4b13c2 Leverage the next keyword from python 2.7

    但是repo代码里面执行是没有的。感觉是 默认带上了 --no-decorate 选项的效果。

    查看git log帮助手册看到如下描述:

    1
    2
    3
    4
    5
    --no-decorate, --decorate[=short|full|auto|no]
    Print out the ref names of any commits that are shown. If short is specified, the ref name prefixes refs/heads/,
    refs/tags/ and refs/remotes/ will not be printed. If full is specified, the full ref name (including prefix) will
    be printed. If auto is specified, then if the output is going to a terminal, the ref names are shown as if short
    were given, otherwise no ref names are shown. The default option is short.

    如果是输出到终端默认是short的效果。

    执行到 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True) 这里 就是 调用了 subprocess.Popen 去调用外部git 命令了。