repo命令詳解

1 repo介紹
Android 使用 Git 作爲代碼管理工具,開發了 Gerrit 進行代碼審覈以便更好的對代碼進行集中式管理,還開發了 Repo 命令行工具,對 Git 部分命令封裝,將百多個 Git 庫有效的進行組織。
1.1    清單庫文件介紹
一個清單庫可以包含多個清單文件和多個分支,每個清單文件和分支都有對應的版本。清單文件以xml格式組織的。舉個例子:
?         remote元素,定義了名爲korg的遠程版本庫,其庫的基址爲git://172.16.1.31/
?         default元素,設置各個項目默認遠程版本庫爲korg,默認的的分支爲gingerbread-exdroid-stable。當然各個項目(project元素)還可以定義自己的remote和revision覆蓋默認的配置
?         project元素,用於定義一個項目,path屬性表示在工作區克隆的位置,name屬性表示該項目的遠程版本庫的相對路徑
?         project元素的子元素copyfile,定義了項目克隆後的一個附件動作,從src拷貝文件到dest
1.2 下載repo代碼
$mkdir android2.3.4
$cd android2.3.4
$git clone git://172.16.1.31/repo.git
     於是在android目錄下便有repo文件夾,裏面包含了repo的源代碼,裏面有個repo腳本,用它來執行repo指令。
在本地開發的用戶需要下載repo代碼,在172.16.1.7服務器上開發的用戶則不用下載repo代碼,因爲已經把repo腳本添加到了環境變量,執行repo init 就會附加的下載repo代碼。
2 repo常用指令
備註:“*”表示新添加的指令
2.1 repo init (下載repo並克隆manifest)
Usage:
repo init –u URL [OPTIONS]
Options:
l         -u:指定一個URL,其連接到一個maniest倉庫
l         -m:在manifest倉庫中選擇一個xml文件
l         -b:選擇一個maniest倉庫中的一個特殊的分支
命令repo init 要完成如下操作:
?         完成repo工具的完整下載,執行的repo腳本只是引導程序
?         克隆清單庫manifest.git (地址來自於-u 參數)
?         克隆的清單庫位於manifest.git中,克隆到本地.repo/manifests.清單.repo/manifest.xml只是符號鏈接,它指向.repo/manifests/default.xml
?         如果manifests中有多個xml文件,repo init 可以任意選擇其中一個,默認選擇是default.xml
Example:
repo init  -u git://172.16.1.31/manifest.git
 
 
在android2.3.4目錄下面出現了.repo文件夾。
 
repo  init  -u git://172.16.1.31/manifest.git –m android.xml
選擇的是android.xml裏面的配置,.repo/manifest.xml便指向.repo/manifests/android.xml
2.2 repo sync(下載代碼)
Usage:
repo sync [<project>…]
用於參照清單文件.repo/manifest.xml克隆並同步版本庫。如果某個項目版本庫尚不存在,則執行repo sync 命令相當於執行git clone,如果項目版本庫已經存在,則相當於執行下面的兩條指令:
l         git remote update
相當於對每一個remote源執行了fetch操作
l         git rebase origin/branch
針對當前分支的跟蹤分支執行rebase操作。
Example:
repo sync




也可以選擇克隆其中的一個項目:
repo sync platform/build
2.3 repo start(創建並切換分支)
Usage:
repo start  <newbranchname> [--all | <project>…]
    剛克隆下來的代碼是沒有分支的,repo start實際是對git checkout –b 命令的封裝。爲指定的項目或所有項目(若使用—all參數),以清單文件中爲設定的分支,創建特性分支。這條指令與git checkout –b 還是有很大的區別的,git checkout –b 是在當前所在的分支的基礎上創建特性分支,而repo start是在清單文件設定分支的基礎上創建特性分支。
Example:
  repo start  stable  --all
假設清單文件中設定的分支是gingerbread-exdroid-stable,那麼執行以上指令就是對所有項目,在gingerbread-exdroid-stable的基礎上創建特性分支stable。
  repo start  stable  platform/build platform/bionic
假設清單文件中設定的分支是gingerbread-exdroid-stable,那麼執行以上指令就是對platform/build、platform/bionic項目,在gingerbread-exdroid-stable的基礎上創建特性分支stable
    
2.4 repo checkout(切換分支)
 Usage:
repo checkout <branchname>  [<project>…]
實際上是對git checkout 命令的封裝,但不能帶-b參數,所以不能用此命令來創建特性分支。
Example:
repo checkout crane-dev 
repo checkout crane-dev  platform/build  platform/bionic
2.5 repo branches(查看分支)
Usage:
repo branches [<project>…]
Example:
repo branches 
repo branches platform/build platform/bionic
 
2.6 repo diff(查看工作區文件差異)
 Usage:
repo diff [<project>…]
   實際是對git diff 命令的封裝,用於分別顯示各個項目工作區下的文件差異。
Example:
repo diff                            ---查看所有項目
repo diff platform/build platform/bionic  ---只查看其中兩個項目
2.7 repo stage(把文件添加到index表中)
     實際是對git add --interactive命令的封裝、用於挑選各個項目工作區中的改動以加入暫存區。
Usage:
repo stage -i [<project>…]
    -i代表git add --interactive命令中的--interactive,給出個界面供用戶選擇
2.8 repo prune(刪除已經合併分支)
   實際上是對git branch –d命令的封裝,該命令用於掃面項目的各個分支,並刪除已經合併的分支,用法如下:
repo prune [<project>…]
 
2.9 repo abandon(刪除指定分支)
   實際上是對git branch –D 命令的封裝,用法如下:
repo abandon <branchname> [<project>…]
2.10 repo status(查看文件狀態)
實際上是對git diff-index、git diff-filse命令的封裝,同時顯示暫存區的狀態和本地文件修改的狀態
$repo/repo status platform/bionic


以上的實例輸出顯示了platform/bionic項目分支的修改狀態
?         每個小節的首行顯示羨慕名稱,以及所在分支的名稱
?         第一個字母表示暫存區的文件修改狀態
l         -:沒有改變
l         A:添加(不在HEAD中,在暫存區中)
l         M:修改(在HEAD中,在暫存區中,內容不同)
l         D:刪除(在HEAD中,不在暫存區)
l         R:重命名(不在HEAD中,在暫存區,路徑修改)
l         C:拷貝(不在HEAD中,在暫存區,從其他文件拷貝)
l         T:文件狀態改變(在HEAD中,在暫存區,內容相同)
l         U:未合併,需要衝突解決
?         第二個字母表示工作區文件的更改狀態
l         -:新/未知(不在暫存區,在工作區)
l         m:修改(在暫存區,在工作區,被修改)
l         d:刪除(在暫存區,不在工作區)
?         兩個表示狀態的字母后面,顯示文件名信息。如果有文件重名還會顯示改變前後的文件名及文件的相似度
2.11 *repo remote(設置遠程倉庫)
Usage:
repo remote add <remotename>  <url> [<project>…] 
repo remote rm <remotename>  [<project>…]
Example:
repo remote add org ssh://172.16.1.31/git_repo
這個指令是根據xml文件添加的遠程分支,方便於向服務器提交代碼,執行之後的build目錄下看到新的遠程分支org:


刪除遠程倉庫:
$repo  remote  rm  org
2.12 *repo push
repo push org
   這是新添加的指令,用於向服務器提交代碼,使用方法:
repo push <remotename> [--all |<project>…]
repo會自己查詢需要向服務器提交的項目並提示用戶。
2.13repo forall
 Usage:
repo forall [<project>…] –c <command>
迭代器,可以在所有指定的項目中執行同一個shell指令
 Options:
l         -c:後面所帶的參數着是shell指令
l         -p:在shell指令輸出之前列出項目名稱
l         -v:列出執行shell指令輸出的錯誤信息
 additional environment variables:
l         REPO_PROJECT:指定項目的名稱
l         REPO_PATH:指定項目在工作區的相對路徑
l         REPO_REMOTE:指定項目遠程倉庫的名稱
l         REPO_LREV:指定項目最後一次提交服務器倉庫對應的哈希值
l         REPO_RREV:指定項目在克隆時的指定分支,manifest裏的revision屬性
 另外,如果-c後面所帶的shell指令中有上述環境變量,則需要用單引號把shell指令括起來。
3.13.1 添加的環境變量
 
repo forall –c ‘echo $REPO_PROJECT’
 
 


  
$repo forall  –c ‘echo $REPO_PATH’
 
   
 
 
3.13.2 merge(合併多個分支)
    把所有項目多切換到master分支,執行以下指令將topic分支合併到master分支
 
repo forall –p –c git merge topic
 
   
3.13.3 tag(打標籤)
在所有項目下打標籤
repo forall –c git tag crane-stable-1.6
3.13.4 remote (設置遠程倉庫)
引用環境變量REPO_PROJECT添加遠程倉庫:
repo forall –c ‘git remote add korg ssh://[email protected]/$REPO_PROJECT.git’
刪除遠程倉庫:
repo forall –c git remote add korg
3.13.5 branch(創建特性分支)
repo forall –c git branch crane-dev
repo forall –c git checkout –b crane-dev
3 repo的額外命令集
3.1 repo grep
相當於對git grep 的封裝,用於在項目文件中進行內容查找
3.2 repo manifest
顯示manifest文件內容
Usage:
repo manifest –o android.xml
3.3 repo version
顯示repo的版本號
3.4 repo upload
repo upload相當於git push,但是又有很大的不同。它不是將版本庫改動推送到克隆時的遠程服務器,而是推送到代碼審覈服務器(Gerrit軟件架設)的特殊引用上,使用SSH協議。代碼審覈服務器會對推送的提交進行特殊處理,將新的提交顯示爲一個待審覈的修改集,並進入代碼審查流程,只有當審覈通過後,纔會合併到官方正式的版本庫中。
因爲全志沒有代碼審覈服務器,所以這條指令用不到。
 Usage:
repo upload [--re --cc] {[<project>]… | --replace <project>}
 Options:
l         -h, --help:顯示幫助信息
l         -t:發送本地分支名稱到Gerrit代碼審覈服務器
l         --replace:發送此分支的更新補丁集
l         --re=REVIEWERS:要求指定的人員進行審覈
l         --cc=CC:同時發送通知到如下郵件地址
3.5 repo download
主要用於代碼審覈者下載和評估貢獻者提交的修訂
Usage
repo download {project change [patchset]}…
3.6 repo selfupdate
    用於repo自身的更新
參考:
http://wenku.baidu.com/view/672c8faddd3383c4bb4cd257.html


4 添加的remote指令
   在sumcmds中添加remote.py,程序如下:
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import sys
from color import Coloring
from command import Command
from progress import Progress








class Remote(Command):
  common = True
  helpSummary = "View current topic branches"
  helpUsage = """
%prog add <remotebranchname> <url> [<project>...]
       %prog rm  <remotebranchname> [<project>...]


--------------


"""
  def str_last(self ,string):
     for c in string:
         last=c
     return last


  def Execute(self, opt, args):
    if not args:
      print >>sys.stderr, "error:..........wrong command........"
      print >>sys.stderr, "Usage:repo remote add <remotebranchname> <url> [<project>...]"
      print >>sys.stderr, "      repo remote rm  <remotebranchname> [<project>...] "          
      print >>sys.stderr, "................................"
      return


    err = []
    operate=args[0]
    #url = args[2]
   # branch_name=args[1]
    if operate == "rm":
       if not len(args) >=2:
         print >>sys.stderr, "error:miss remotebrancname"
         return


       branch_name=args[1]
       projects = args[2:]
    elif operate == "add":
       if not len(args) >=3:
         print >>sys.stderr, "error:miss remotebranchname or url"
         return


       branch_name=args[1]
       projects = args[3:]
    else:
       print >>sys.stderr, "error: the operand is add or rm "
       return
     
    all = self.GetProjects(projects)
   # print >>sys.stderr, all
    pm = Progress('remote %s' % operate, len(all))
    for project in all:
       if operate == "add":
          if self.str_last(args[2])=="/":
             url = args[2]+project.name+'.git'
          else :
             url = args[2]+'/'+project.name+'.git'
       else:
         url = ""


       pm.update() 
       if not project.Remote(operate,branch_name,url):
         err.append(project)
    pm.end()
       
    if err:
      if len(err) == len(all):
        print >>sys.stderr, 'error: no project remote  %s %s' % (operate,branch_name)  
      else:
        for p in err:
          print >>sys.stderr,\
            "error: %s/: cannot remote %s %s " \
            % (p.relpath, operate,branch_name)
      sys.exit(1)




在preject.py中添加Remote(operate,branch_name,url)方法:
   def Remote(self,operate,branch_name,url):
     """Prune  topic branches already merged into upstream.
     """
     if url=="":   #rm
       return GitCommand(self,
                         ['remote', operate, branch_name],
                         capture_stdout = True,
                         capture_stderr = True).Wait() == 0


     else:  #add
       return GitCommand(self,
                         ['remote', operate, branch_name,url],
                         capture_stdout = True,
                         capture_stderr = True).Wait() == 0


5  添加push指令
 在subcmds中添加push.py,代碼如下:
 #
# Copyright (C) 2010 [email protected]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import copy
import re
import sys


from command import InteractiveCommand
from editor import Editor
from error import UploadError, GitError
from project import ReviewableBranch


def _ConfirmManyUploads(multiple_branches=False):
  if multiple_branches:
    print "ATTENTION: One or more branches has an unusually high number of commits."
  else:
    print "ATTENTION: You are uploading an unusually high number of commits."
  print "YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across branches?)"
  answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip()
  return answer == "yes"


def _die(fmt, *args):
  msg = fmt % args
  print >>sys.stderr, 'error: %s' % msg
  sys.exit(1)


def _SplitEmails(values):
  result = []
  for str in values:
    result.extend([s.strip() for s in str.split(',')])
  return result


class Push(InteractiveCommand):
  common = True
  helpSummary = "Upload changes for code review"
  helpUsage="""
%prog <remotebranchname> {[<project>]... }
"""
  helpDescription = """
The '%prog' command is used to send changes to the Gerrit Code
Review system.  It searches for topic branches in local projects
that have not yet been published for review.  If multiple topic
branches are found, '%prog' opens an editor to allow the user to
select which branches to upload.


'%prog' searches for uploadable changes in all projects listed at
the command line.  Projects can be specified either by name, or by
a relative or absolute path to the project's local directory. If no
projects are specified, '%prog' will search for uploadable changes
in all projects listed in the manifest.


If the --reviewers or --cc options are passed, those emails are
added to the respective list of users, and emails are sent to any
new users.  Users passed as --reviewers must already be registered
with the code review system, or the upload will fail.


If the --replace option is passed the user can designate which
existing change(s) in Gerrit match up to the commits in the branch
being uploaded.  For each matched pair of change,commit the commit
will be added as a new patch set, completely replacing the set of
files and description associated with the change in Gerrit.


Configuration
-------------


review.URL.autoupload:


To disable the "Upload ... (y/n)?" prompt, you can set a per-project
or global Git configuration option.  If review.URL.autoupload is set
to "true" then repo will assume you always answer "y" at the prompt,
and will not prompt you further.  If it is set to "false" then repo
will assume you always answer "n", and will abort.


review.URL.autocopy:


To automatically copy a user or mailing list to all uploaded reviews,
you can set a per-project or global Git option to do so. Specifically,
review.URL.autocopy can be set to a comma separated list of reviewers
who you always want copied on all uploads with a non-empty --re
argument.


review.URL.username:


Override the username used to connect to Gerrit Code Review.
By default the local part of the email address is used.


The URL must match the review URL listed in the manifest XML file,
or in the .git/config within the project.  For example:


  [remote "origin"]
    url = git://git.example.com/project.git
    review = http://review.example.com/


  [review "http://review.example.com/"]
    autoupload = true
    autocopy = [email protected],[email protected]


References
----------


Gerrit Code Review:  http://code.google.com/p/gerrit/


"""




   
    


  def _SingleBranch(self, opt, branch):
    project = branch.project
    name = branch.name
    remote = project.GetBranch(name).remote


    key = 'review.%s.autoupload' % remote.review
    answer = project.config.GetBoolean(key)


    if answer is False:
      _die("upload blocked by %s = false" % key)


    if answer is None:
      date = branch.date
      list = branch.commits


      print 'Upload project %s/:' % project.relpath
      print '  branch %s (- commit%s, %s):' % (
                    name,
                    len(list),
                    len(list) != 1 and 's' or '',
                    date)
      for commit in list:
        print '         %s' % commit


      pushurl = project.manifest.manifestProject.config.GetString('repo.pushurl')
      sys.stdout.write('to %s (y/n)? ' % (pushurl and 'server: ' + pushurl or 'remote') )
      answer = sys.stdin.readline().strip()
      answer = answer in ('y', 'Y', 'yes', '1', 'true', 't')


    if answer:
      self._UploadAndReport(opt, [branch])
    else:
      _die("upload aborted by user")


  def _MultipleBranches(self, opt,remoebranch ,pending):
    projects = {}
    branches = {}


    script = []
    script.append('# Uncomment the branches to upload:')
    for project, avail in pending:
      script.append('#')
      script.append('# project %s/:' % project.relpath)


      b = {}
      for branch in avail:
        name = branch.name
        date = branch.date
        list = branch.commits
       # print >>sys.stdout, name
        


        if b:
          script.append('#')
        script.append('  branch %s (- commit%s, %s):' % (    ##########3
                      name,
                      len(list),
                      len(list) != 1 and 's' or '',
                      date))
        for commit in list:
          script.append('#         %s' % commit)
        b[name] = branch


      projects[project.relpath] = project
      branches[project.name] = b
    script.append('')


  #  script = Editor.EditString("\n".join(script)).split("\n")


    project_re = re.compile(r'^#?\s*project\s*([^\s]+)/:$')
    branch_re = re.compile(r'^\s*branch\s*([^\s(]+)\s*\(.*')


    project = None
    todo = []


    for line in script:
      m = project_re.match(line)
      if m:
        name = m.group(1)
        project = projects.get(name)
        if not project:
          _die('project %s not available for upload', name)
        continue


      m = branch_re.match(line)
      if m:
        name = m.group(1)
        if not project:
          _die('project for branch %s not in script', name)
        branch = branches[project.name].get(name)
        if not branch:
          _die('branch %s not in %s', name, project.relpath)
        todo.append(branch)
    if not todo:
      _die("nothing uncommented for upload")


    self._UploadAndReport(opt, remoebranch,todo)


  def _UploadAndReport(self, opt,remoebranch,todo):
    have_errors = False
    for branch in todo:
      try:
        # Check if there are local changes that may have been forgotten
        if branch.project.HasChanges():
            key = 'review.%s.autoupload' % branch.project.remote.review
            answer = branch.project.config.GetBoolean(key)


            # if they want to auto upload, let's not ask because it could be automated
            if answer is None:
                sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/n) ')
                a = sys.stdin.readline().strip().lower()
                if a not in ('y', 'yes', 't', 'true', 'on'):
                    print >>sys.stderr, "skipping upload"
                    branch.uploaded = False
                    branch.error = 'User aborted'
                    continue


        branch.project.UploadNoReview(opt, remoebranch,branch=branch.name)
        branch.uploaded = True
      except UploadError, e:
        branch.error = e
        branch.uploaded = False
        have_errors = True
      except GitError, e:
        print >>sys.stderr, "Error: "+ str(e)
        sys.exit(1)




    print >>sys.stderr, ''
    print >>sys.stderr, '--------------------------------------------'


    if have_errors:
      for branch in todo:
        if not branch.uploaded:
          print >>sys.stderr, '[FAILED] %-15s %-15s  (%s)' % (
                 branch.project.relpath + '/', \
                 branch.name, \
                 branch.error)
      print >>sys.stderr, ''


    for branch in todo:
        if branch.uploaded:
          print >>sys.stderr, '[OK    ] %-15s %s' % (
                 branch.project.relpath + '/',
                 branch.name)


    if have_errors:
      sys.exit(1)


  def Execute(self, opt, args):
    
    if len(args)==0:
      print >>sys.stdout,"error:miss remotebranchname"
      print >>sys.stdout, "Usage:repo push <remotebranchname> [<project>...]"
      return
    project_list = self.GetProjects(args[1:])
    pending = []


    remoebranch=args[0]
    # if not create new branch, check whether branch has new commit.
    for project in project_list:
      if (project.GetUploadableBranch(project.CurrentBranch) is None):
        continue
      branch = project.GetBranch(project.CurrentBranch)
      
      rb = ReviewableBranch(project, branch, branch.LocalMerge)
      pending.append((project, [rb]))


    # run git push
    if not pending:
      print >>sys.stdout, "no branches ready for upload"
   # elif len(pending) == 1 and len(pending[0][1]) == 1:
     # self._SingleBranch(opt, pending[0][1][0])
    else:
      self._MultipleBranches(opt,remoebranch ,pending)








  在preject.py中添加如下方法:UploadNoReview(opt, remoebranch,branch=branch.name)


   def UploadNoReview(self, opt, remoebranch,branch=None):
    """If not review server defined, uploads the named branch directly to git server.
    """
    #print >>sys.stdout, branch
    now_branch=branch
    if branch is None:
      branch = self.CurrentBranch
    if branch is None:
      raise GitError('not currently on a branch')


    branch = self.GetBranch(branch)


    if not branch.LocalMerge:
      raise GitError('branch %s does not track a remote' % branch.name)


  


   # if opt.new_branch:
   #   dest_branch = branch.name
   # else:
    dest_branch = branch.merge


    if dest_branch.startswith(R_TAGS):
      raise GitError('Can not push to TAGS (%s)! Run repo push with --new flag to create new feature branch.' % dest_branch)
    if not dest_branch.startswith(R_HEADS):
      dest_branch = R_HEADS + dest_branch


    if not branch.remote.projectname:
      branch.remote.projectname = self.name
      branch.remote.Save()


    # save git config branch.name.merge
   ## if opt.new_branch:
    #  branch.merge = dest_branch
    #  branch.Save()


    ref_spec = '%s:%s' % (R_HEADS + branch.name, dest_branch)
    pushurl = self.manifest.manifestProject.config.GetString('repo.%s.pushurl'
              % branch.remote.name)
    if not pushurl:
      pushurl = self.manifest.manifestProject.config.GetString('repo.pushurl')
    if not pushurl:
      pushurl = branch.remote.name
    else:
      pushurl = pushurl.rstrip('/') + '/' + self.name
      remote = self.manifest.remotes.get(branch.remote.name)
      if remote and remote.autodotgit is not False:
        pushurl += ".git"


    cmd = ['push']
    #print >>sys.stdout, now_branch+'now'
    cmd.append(remoebranch)
    cmd.append(now_branch)
    print >>sys.stdout, "push"+" "+self.name+':'
    if GitCommand(self, cmd).Wait() != 0:
      raise UploadError('Upload failed')


    if branch.LocalMerge and branch.LocalMerge.startswith('refs/remotes'):
      self.bare_git.UpdateRef(branch.LocalMerge,
                              R_HEADS + branch.name)
發佈了52 篇原創文章 · 獲贊 7 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章