What's the difference between `arc graft` and `arc patch`?
Asked Answered
S

2

10

arc help --full | less reveals this for graft:

  graft revision

      Grafts revision and its dependencies (if any) onto your working tree.

      --force
          Do not run any sanity checks.

      --skip-landed
          Do not try to patch landed/closed diffs.

and this for patch:

  patch D12345
  patch --revision revision_id
  patch --diff diff_id
  patch --patch file
  patch --arcbundle bundlefile
      Supports: git, svn, hg
      Apply the changes in a Differential revision, patchfile, or arc
      bundle to the working copy.

      --arcbundle bundlefile
          Apply changes from an arc bundle generated with 'arc export'.

which is vague to me. Using the word "graft" to describe what "graft" means doesn't help me much.


For those who don't know, arc (Arcanist) is a command-line tool inside of "Phabricator" which acts like a high-level wrapper around Git (or even Mercurial and Subversion) to aid in the development process for large software projects. Here are a few links:

https://phacility.com/phabricator/

Phabricator was originally developed as an internal tool at Facebook.[7][8][9] Phabricator's principal developer is Evan Priestley.[1] Priestley left Facebook to continue Phabricator's development in a new company called Phacility.[2] https://en.wikipedia.org/wiki/Phabricator

Sass answered 17/1, 2019 at 2:1 Comment(0)
S
10

Update 19 Mar. 2020:

Note that arc flow and arc cascade are apparently not part of the mainstream version of Phacility's arcanist. Rather, they are only part of the Uber fork of arcanist. Some of the arc flow source code is found here, for instance. Additionally, arc cascade is also only an Uber fork arcanist feature, and some of its arc cascade source code is found here.
To learn how to install Uber's fork of arcanist in order to begin using these features in your regular git workflow (you don't need to use any other features of arcanist if not needed), jump to the bottom of this answer.


ORIGINAL ANSWER Jan. 2019:

So, after some experimenting and trial and error, I think I figured it out:

Both arc graft and arc patch use git cherry-pick under the hood, and accomplish similar things. However, they have some subtle differences, and sometimes arc patch fails and you must use arc graft with the --skip-landed flag instead (update: or perhaps arc patch with the --skip-dependencies flag will work too?).

Examples:

# cherry-pick their "D999" "diff" (branch) onto your current branch, while creating 
# a new single branch for you named "arcpatch-D999", skipping dependencies in case 
# they've already landed on the branch (ex: master) you currently have checked out.
arc patch --skip-dependencies D999 

OR

# cherry-pick their "D999" "diff" (branch), *as well as all parent branch(es) it 
# depends on*, onto your current branch, while creating the entire dependency tree 
# of branches for you, exactly as the submitter originally had on their local 
# machine, skipping any commits that have already landed on your local branch 
# (ex: master) you currently have checked out
arc graft --skip-landed D999 

Imagine your arc flow dependency tree only contains the "master" branch:

master

A coworker, however, has the following arc flow dependency tree:

master                              
└──new_feature_1
   └──new_feature_2 

Short aside: what's an arc flow dependency tree? Ans: it is a tree structure, shown via the arc flow command, which shows which branches depend on what. It is a somewhat arbitrary thing that you as a human are tracking manually, since you know that one feature depends on another. To establish a "dependency", you have two options:

  1. Call arc flow new_branch_name to create a new child branch while you have currently checked-out the branch you want to be the parent, OR:
  2. Use git to create a new branch, then set its upstream to what you want to be the parent. Ex:

    git branch new_branch_name
    git checkout new_branch_name # Or use `git checkout -b new_branch_name` to do both at once
    git branch --set-upstream-to=upstream_branch_name # or `git branch -u upstream_branch_name` for short
    

Now, arc flow will show your dependency tree. This allows you to to things like arc cascade from a parent down to its children, which is just doing automated recursive git rebases from parents down to children (ie: rebasing children onto parents).

End of aside.


Anyway, with the dependency tree shown above, your coworker has "new_feature_2" checked out, and they arc diff it for you to review. You go to the web-based "Differential" tool and start reviewing the change. However, you want to test it. This means you need to pull their diff to your local machine. You have two options: 1. arc patch their diff (dependency-tree-aware branch) onto your local master, or 2. arc graft their diff onto your local master.

Assuming their diff is "D999", and you currently have your "master" branch checked-out, your commands and resulting dependency trees would look as follows:

  1. arc patch D999. You now have this tree, where your newly-created "arcpatch-D999" is their "new_feature_2" branch:

    master
    └──arcpatch-D999 
    
  2. arc graft D999. You now have this tree, just like they have:

    master                              
    └──new_feature_1
       └──new_feature_2 
    

However, (I think, based on my problems) that sometimes when they have a multi-generation dependency tree like this, arc patch will fail (giving an error that says "Cherry Pick Failed!"), and in such a case you must use arc graft instead! HOWEVER, if their master is NOT the exact same as your master (which is almost certainly will NOT be, since they probably pulled their master a while back and you should have just pulled yours to ensure you have the latest), then your attempts to graft or patch will fail. It is likely the failures will be related to the fact that some of the commits in their branch history contain changes which are already landed and present in your master. The solution is to use arc graft D999 --skip-landed, which will allow you to grab their diff and pull it down, mirroring their arc flow dependency tree. In such a case, arc patch D999 will likely continue to fail until they pull the latest master and arc cascade (or git rebase twice), then re-arc diff to push their changes to the server, at which point you can then arc patch D999 onto your master successfully. Since you can't always get them to rebase/arc cascade immediately, however, just do the arc graft D999 --skip-landed now and be done! Let them rebase and re-arc diff when they get to it.

One minor problem, however, is that if you are arc grafting much, it can get confusing who made which branches (you, or someone else?), so I recommend you get into the habit of grafting onto a new branch you name yourself, as follows, just for organization:

git checkout master # same as `arc flow master`
git pull origin master # pull latest master
arc flow graft-D999 # create and checkout a new child branch you are calling "graft-D999" (name it appropriately)
arc graft D999 --skip-landed # graft their entire dependency tree onto your branch "graft-D999"

Your dependency tree will now be as follows:

master                              
└──graft-D999
   └──new_feature_1
      └──new_feature_2 

Excellent! Nice and organized. Now you can check out "new_feature_2" and compile and test it. Note, however, that "master" and "graft-D999" will be exactly identical branches, but that's ok.

How to install Uber's fork of arcanist

(in case you'd like to start using arc flow and arc cascade in your own git work-flows):

Note: Arcanist runs on Windows (inside the git for Windows git bash terminal), Mac, and Linux. I'll just present the Linux installation instructions. To get help on installing in other systems start by seeing the References below.

Linux Ubuntu installation:

cd to where you'd like the installation files to live, then do the following:

# 1. Obtain the Uber fork of the arcanist program by cloning it into an "uber" directory.
mkdir uber
git clone https://github.com/uber/arcanist.git uber
# 2. Symbolically link the `arc` program to your ~/bin directory so you can call `arc` from anywhere.
# - this assumes that ~/bin is in your PATH.
# Ensure the ~/bin dir exists; if just creating this dir for the first time you may need to log out of Ubuntu
# and log back in AFTER running the `mkdir` command below, in order to force your ~/.profile script to 
# automatically add ~/bin to your PATH (assuming your ~/.profile script does this, as default Ubuntu scripts do).
mkdir -p ~/bin
ln -s $PWD/uber/arcanist/bin/arc ~/bin

Now try to run arc. It should fail with the following message:

$ arc
ERROR: Unable to load libphutil. Put libphutil/ next to arcanist/, or update your PHP 'include_path' to include the parent directory of libphutil/, or symlink libphutil/ into arcanist/externals/includes/.

So, do the following:

# 3. Obtain the libphutil program.
# - Note that git cloning it like this `git clone https://github.com/phacility/libphutil.git` will NOT work anymore
# for Uber's fork of arcanist because the libphutil project is now empty. So, do this instead:
sudo apt install libphutil
# 4. symbolically link the libphutil program into arcanist.
# First, we need to know where it is installed.
dpkg -L libphutil
# Now look at the output from the above command. Mine shows libphutil is installed in "/usr/share/libphutil/",
# so do the following:
ln -s /usr/share/libphutil uber/arcanist/externals/includes

Now test the arc command and you should see the following:

$ arc
Usage Exception: No command provided. Try `arc help`.

Run arc help to see the help menu, or arc help --full for the full help menu.

Let's grep for flow and cascade to prove they are in the help menu for the Uber fork only (but not for the main arcanist):

Grepping for flow in the full help menu:

$ arc help --full | grep flow
          This workflow is primarily useful for writing scripts which integrate
              soft version of '' used by other workflows.
      flow [options]
      flow name [options]
      flow name upstream [options]
          step in the standard Differential pre-publish code review workflow.
          The workflow selects a target branch to land onto and a remote where
          Consulting mystical sources of power, the workflow makes a guess
          step in the standard Differential pre-publish code review workflow.
          The workflow selects a target branch to land onto and a remote where
          Consulting mystical sources of power, the workflow makes a guess
          code review workflow.
          The workflow selects a target branch to land onto and a remote where

And cascade in the full help menu:

$ arc help --full | grep -A 4 cascade
      cascade [--halt-on-conflict] [rootbranch]

          Automates the process of rebasing and patching local working branches
          and their associated differential diffs. Cascades from current branch
          if branch is not specified.
--
              Rather than aborting any rebase attempts, cascade will drop the
              user
              into the conflicted branch in a rebase state.

References:

  1. Phacility's Arcanist (lacks arc flow and arc cascade features): https://github.com/phacility/arcanist
  2. Uber's fork of Arcanist (has arc flow and arc cascade features added): https://github.com/uber/arcanist
  3. Arcanist User Guide: https://secure.phabricator.com/book/phabricator/article/arcanist/ --> See "Installing Arcanist" section for their instructions on how to install it in Windows, Mac, Linux, or FreeBSD.
  4. ERROR: Unable to load libphutil

Related:

  1. Output of git branch in tree like fashion
Sass answered 17/1, 2019 at 7:57 Comment(2)
OK. +1. So the graft nature of arc does not seem to be related to the one in Git I mentioned in my own answer.Viol
Yeah, I'm still trying to wrap my mind around it all, but I think not.Sass
V
0

This might be related to git grafts, where you take a commit/revision, and change its parent commit, thus changing the history of a repo.
(although, since Git 2.18, Q2 2018, graft has been superseded by git ref/replace/)

As opposed to patches, which simply create a new commit/revision from an existing diff, adding to the history of a repo.

Viol answered 17/1, 2019 at 6:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.