+def reset(files = None, tree_id = None, check_out = True):
+ """Revert the tree changes relative to the given tree_id. It removes
+ any local changes
+ """
+ if not tree_id:
+ tree_id = get_head()
+
+ if check_out:
+ cache_files = tree_status(files, tree_id)
+ # files which were added but need to be removed
+ rm_files = [x[1] for x in cache_files if x[0] in ['A']]
+
+ checkout(files, tree_id, True)
+ # checkout doesn't remove files
+ map(os.remove, rm_files)
+
+ # if the reset refers to the whole tree, switch the HEAD as well
+ if not files:
+ __set_head(tree_id)
+
+def resolved(filenames, reset = None):
+ if reset:
+ stage = {'ancestor': 1, 'current': 2, 'patched': 3}[reset]
+ GRun('checkout-index', '--no-create', '--stage=%d' % stage,
+ '--stdin', '-z').input_nulterm(filenames).no_output()
+ GRun('update-index', '--add', '--').xargs(filenames)
+ for filename in filenames:
+ gitmergeonefile.clean_up(filename)
+ # update the access and modificatied times
+ os.utime(filename, None)
+
+def fetch(repository = 'origin', refspec = None):
+ """Fetches changes from the remote repository, using 'git fetch'
+ by default.
+ """
+ # we update the HEAD
+ __clear_head_cache()
+
+ args = [repository]
+ if refspec:
+ args.append(refspec)
+
+ command = config.get('branch.%s.stgit.fetchcmd' % get_head_file()) or \
+ config.get('stgit.fetchcmd')
+ Run(*(command.split() + args)).run()
+
+def pull(repository = 'origin', refspec = None):
+ """Fetches changes from the remote repository, using 'git pull'
+ by default.
+ """
+ # we update the HEAD
+ __clear_head_cache()
+
+ args = [repository]
+ if refspec:
+ args.append(refspec)
+
+ command = config.get('branch.%s.stgit.pullcmd' % get_head_file()) or \
+ config.get('stgit.pullcmd')
+ Run(*(command.split() + args)).run()
+
+def rebase(tree_id = None):
+ """Rebase the current tree to the give tree_id. The tree_id
+ argument may be something other than a GIT id if an external
+ command is invoked.
+ """
+ command = config.get('branch.%s.stgit.rebasecmd' % get_head_file()) \
+ or config.get('stgit.rebasecmd')
+ if tree_id:
+ args = [tree_id]
+ elif command:
+ args = []
+ else:
+ raise GitException, 'Default rebasing requires a commit id'
+ if command:
+ # clear the HEAD cache as the custom rebase command will update it
+ __clear_head_cache()
+ Run(*(command.split() + args)).run()
+ else:
+ # default rebasing
+ reset(tree_id = tree_id)
+
+def repack():
+ """Repack all objects into a single pack
+ """
+ GRun('repack', '-a', '-d', '-f').run()
+
+def apply_patch(filename = None, diff = None, base = None,
+ fail_dump = True):
+ """Apply a patch onto the current or given index. There must not
+ be any local changes in the tree, otherwise the command fails
+ """
+ if diff is None:
+ if filename:
+ f = file(filename)
+ else:
+ f = sys.stdin
+ diff = f.read()
+ if filename:
+ f.close()
+
+ if base:
+ orig_head = get_head()
+ switch(base)
+ else:
+ refresh_index()
+
+ try:
+ GRun('apply', '--index').raw_input(diff).no_output()
+ except GitRunException:
+ if base:
+ switch(orig_head)
+ if fail_dump:
+ # write the failed diff to a file
+ f = file('.stgit-failed.patch', 'w+')
+ f.write(diff)
+ f.close()
+ out.warn('Diff written to the .stgit-failed.patch file')
+
+ raise
+
+ if base:
+ top = commit(message = 'temporary commit used for applying a patch',
+ parents = [base])
+ switch(orig_head)
+ merge_recursive(base, orig_head, top)
+
+def clone(repository, local_dir):
+ """Clone a remote repository. At the moment, just use the
+ 'git clone' script
+ """
+ GRun('clone', repository, local_dir).run()
+
+def modifying_revs(files, base_rev, head_rev):
+ """Return the revisions from the list modifying the given files."""
+ return GRun('rev-list', '%s..%s' % (base_rev, head_rev), '--', *files
+ ).output_lines()
+
+def refspec_localpart(refspec):
+ m = re.match('^[^:]*:([^:]*)$', refspec)
+ if m:
+ return m.group(1)
+ else:
+ raise GitException, 'Cannot parse refspec "%s"' % line
+
+def refspec_remotepart(refspec):
+ m = re.match('^([^:]*):[^:]*$', refspec)
+ if m:
+ return m.group(1)
+ else:
+ raise GitException, 'Cannot parse refspec "%s"' % line
+
+def __remotes_from_config():
+ return config.sections_matching(r'remote\.(.*)\.url')
+
+def __remotes_from_dir(dir):
+ d = os.path.join(basedir.get(), dir)
+ if os.path.exists(d):
+ return os.listdir(d)
+ else:
+ return []
+
+def remotes_list():
+ """Return the list of remotes in the repository
+ """
+ return (set(__remotes_from_config())
+ | set(__remotes_from_dir('remotes'))
+ | set(__remotes_from_dir('branches')))
+
+def remotes_local_branches(remote):
+ """Returns the list of local branches fetched from given remote
+ """
+
+ branches = []
+ if remote in __remotes_from_config():
+ for line in config.getall('remote.%s.fetch' % remote):
+ branches.append(refspec_localpart(line))
+ elif remote in __remotes_from_dir('remotes'):
+ stream = open(os.path.join(basedir.get(), 'remotes', remote), 'r')
+ for line in stream:
+ # Only consider Pull lines
+ m = re.match('^Pull: (.*)\n$', line)
+ if m:
+ branches.append(refspec_localpart(m.group(1)))
+ stream.close()
+ elif remote in __remotes_from_dir('branches'):
+ # old-style branches only declare one branch
+ branches.append('refs/heads/'+remote);
+ else:
+ raise GitException, 'Unknown remote "%s"' % remote
+
+ return branches
+
+def identify_remote(branchname):
+ """Return the name for the remote to pull the given branchname
+ from, or None if we believe it is a local branch.
+ """
+
+ for remote in remotes_list():
+ if branchname in remotes_local_branches(remote):
+ return remote
+
+ # if we get here we've found nothing, the branch is a local one
+ return None
+
+def fetch_head():
+ """Return the git id for the tip of the parent branch as left by
+ 'git fetch'.
+ """
+
+ fetch_head=None
+ stream = open(os.path.join(basedir.get(), 'FETCH_HEAD'), "r")
+ for line in stream:
+ # Only consider lines not tagged not-for-merge
+ m = re.match('^([^\t]*)\t\t', line)
+ if m:
+ if fetch_head:
+ raise GitException, 'StGit does not support multiple FETCH_HEAD'
+ else:
+ fetch_head=m.group(1)
+ stream.close()
+
+ if not fetch_head:
+ out.warn('No for-merge remote head found in FETCH_HEAD')
+
+ # here we are sure to have a single fetch_head
+ return fetch_head
+
+def all_refs():
+ """Return a list of all refs in the current repository.
+ """
+
+ return [line.split()[1] for line in GRun('show-ref').output_lines()]