@log_calls
  def track(self, scope, track_path_list=[], mode=None, no_download=False, recursive=False):
    '''Update working directory. track_path_list is a list of filenames to track.'''
    # If mode is unspecified, use the mode that the given scope currently in
    if mode is None:
      partial_mode = self.get_scope_mode(scope) == Mode.PARTIAL
      if not partial_mode and track_path_list:
        raise InvalidOperation("scope %s is not in tracking mode %s; cannot track files" % (scope, Mode.PARTIAL))
    else:
      partial_mode = mode == Mode.PARTIAL and (len(track_path_list) > 0 or self.get_scope_mode(scope) != Mode.PARTIAL)
    partial2auto_mode = self.get_scope_mode(scope) == Mode.PARTIAL and mode == Mode.AUTO and len(track_path_list) == 0
    # Error checking
    if not (partial_mode or partial2auto_mode):
      raise InvalidOperation("scope %s is already in %s mode; specyfing mode %s does not make much sense" % (scope, Mode.AUTO, mode))
    # Get manifest for working dir
    cur_rev = self.get_checkout_rev(scope)
    mf = self.repo.get_manifest(scope, cur_rev)

    # We need any local file changes even we are not tracking them in order to avoid overwriting
    # the files that have the same name in the scope; I.e., use mode=Mode.AUTO when in partial_mode
    status_changelist = self.status(scope, force_mode=Mode.AUTO)

    # Find out what files we need to update.
    changelist = changelist_for_items([], mf.items)
    log.debug("update from %s, changing %s", mf, changelist)
    track_changelist = Changelist()
    # Filter down the changelist if checking out / track only specified files
    if partial_mode:
      self.update_scope_mode(scope, Mode.PARTIAL)
      log.info("%s: scope '%s' at revision %s in tracking mode '%s'", "track", scope, mf.rev, Mode.PARTIAL)
      path_candidates = [item.path for item in mf.items] + list(status_changelist.add_paths)
      for path in expand_wildcards(track_path_list, path_candidates, recursive=recursive):
        with self._yield_fingerprint(scope, path) as (fp, local_path):
          if fp is not None and fp.state in FileStateTrackedList:
            log.warn("Warning: scope %s path %s is already %s", scope, path, fp.state)
            changelist.discard(path)
          else:
            track_changelist.update(path, Changelist.Status.ADDED)
      changelist = changelist.intersect(track_changelist)

    # if changing mode from auto to partial, register it
    if partial2auto_mode:
      self.update_scope_mode(scope, Mode.AUTO)
      log.info("%s: scope '%s' at revision %s in tracking mode '%s'", "track", scope, mf.rev, Mode.AUTO)

    # mod_changelist is for files that manifest has but locally modified or deleted
    mod_changelist = status_changelist.intersect(changelist)
    # add_changelist is for files that manifest does no have but locally newlly added
    add_changelist =  track_changelist.difference(changelist)
    # retrack_changelist is for files that has been untracked but brought back to be tracked
    retrack_changelist = changelist.difference(mod_changelist)
    retrack_changelist_paths = retrack_changelist.all_paths()
    for path in retrack_changelist_paths:
      with self._yield_fingerprint(scope, path) as (fp, local_path):
        if fp is not None and fp.state in FileStateTrackedList:
          # do not include paths that are already tracked since the file should be there
          self.checkout_state._check_file_exists(local_path) # sanity check
          retrack_changelist.discard(path)
    log.debug("update changelist:\n%s", changelist.to_summary())
    log.debug("status changelist:\n%s", status_changelist.to_summary())
    log.debug("modified changelist:\n%s", mod_changelist.to_summary())
    log.debug("added changelist:\n%s", add_changelist.to_summary())
    log.debug("retrack changelist:\n%s", retrack_changelist.to_summary())

    # Update mod_changelist checkout state; do not update local files
    if mod_changelist.is_empty():
      log.info("no conflicting local changes")
    else:
      log.info("preserving files: %s (assuming these changes in working directory are ahead of those in repository)", mod_changelist.brief_summary())
      for path in mod_changelist.all_paths():
        with self._yield_fingerprint(scope, path) as (fp, local_path):
          if fp is None:
            assert False # since it is picked-uped by status change, there must be a fingerprint
          fp.state = FileState.Tracked
          fp.md5 = INVALID_MD5 # for precaution since this md5 could be outdated. No need to invalidate size. This file is marked as MODIFIED or REMOVED anyway.

    # Update add_changelist checkout state; do no update local files
    if not add_changelist.is_empty():
      add_changelist_paths = add_changelist.all_paths()
      for path in add_changelist_paths:
        local_path = join_path(scope, path)
        self.checkout_state._check_file_exists(local_path) # sanity check
      log.info("newly tracking: %s (files that are not in repository yet)", add_changelist.brief_summary())
      local_scope_path = join_path(self.work_dir, scope)
      new_fingerprints = FingerprintList.of_files(local_scope_path, add_changelist_paths, compute_md5=True, state=FileState.ToBeTracked)
      self.checkout_state.update_from_changelist(scope, add_changelist, new_fingerprints)

    # Update retrack files
    if retrack_changelist.is_empty():
      if no_download:
        log.warn("Warning: --no-download is used, even though there is nothing to download")
    else:
      log.info("updating files: %s", retrack_changelist.brief_summary())
      if no_download:
        for path in retrack_changelist_paths:
          local_path = join_path(scope, path)
          fp = Fingerprint(md5=INVALID_MD5, mtime=INVALID_MTIME, size=INVALID_SIZE, state=FileState.Tracked) # fake MD5 in order to pass sanity checks later
          self.checkout_state.fingerprints.update_fingerprint(local_path, fp)
      else:
        new_fingerprints = self._update_local_files(scope, mf.items, retrack_changelist, delete_all=True)
        # Changes to files are done. Update checkout state. We already have hashes in new_fingerprints.
        self.checkout_state.update_from_changelist(scope, retrack_changelist, new_fingerprints)

    self._write_checkout_state()

    return mf
dataset/ETHPy150Open bloomreach/zinc/zinc.py/WorkingDir.track/original