Trident D'Gao
Trident D'Gao

Reputation: 19690

How do I see a diff from untracked, unstaged, staged and committed changes all against the remote?

So I have master which is the baseline, and I have an arbitrary situation on the developer's machine including:

Is there a way to see a diff of all of above against the remote?

Upvotes: 2

Views: 1754

Answers (2)

Trident D'Gao
Trident D'Gao

Reputation: 19690

his is the solution i discovered

git add --all -N :/
git --no-pager diff origin/master

Upvotes: 2

torek
torek

Reputation: 488463

Not as a single command, but as long as you have access to the developer's machine and it has access to the other repository, yes. Let's just call the developer's machine M, and assume that on M, accessing the "main" repository is done through origin, i.e., the developer ran git clone <url> originally to create the repository that is now on M.

The first step is to make sure that the repository on M is up to date with the repository on the other system(s). Assume M$ is the prompt on machine M when in the clone:

M$ git fetch origin

The repository on M now has:

  • all its local commits;
  • the staged files, in the index;
  • the unstaged files, in the work-tree;
  • any untracked files, in the work-tree;
  • all the commits available on origin.

Now M has everything needed to generate any diff you like, and the question becomes: How would you like these diffs generated and presented?

Let's assume further that we are interested in:

  • Any commits on local branch B that are not on origin/B:

    ...--o--o--*--G--H   <-- B
                \
                 J--K--L   <-- origin/B
    

    Here there are two such commits, with hashes G and H.

    You can git show each such commit, to get a log message and a patch. Or, you can run git format-patch <options> origin/B..B, to produce either individual patch files (the default) or a stdout stream (--stdout) containing each commit as a patch. Note that format-patch will not show merge commits and diffs of merge commits are tricky to get right, so we'll just ignore them.

    If the repository is currently on branch B (so that B is HEAD), you can just run git format-patch <options> origin/B: the default for format-patch is to treat a single argument as meaning <argument>..HEAD. Hence:

    M$ git format-patch --stdout origin/B > /tmp/commits
    
  • The difference between the tip of branch B (commit H) and what's in the index/staging-area. Let's assume, again, that HEAD names branch B, to make this easier:

    git diff --cached > /tmp/tip-to-staged
    

    This writes the diff to the standard output.

  • The difference between what's in the index and what's in the work-tree:

    git diff > /tmp/staged-to-work-tree
    

    This writes the diff to the standard output.

  • The difference between "nothing" (an empty tree) and each untracked file. This is the most difficult to get from Git, and also in a sense the silliest, since the difference between "nothing" and "some set of files" is just "some set of files". There is no single Git command to generate this, but you could just use git ls-files --other to get the list of such files and then wrap them up however you like. Alternatively, you could write them into a commit; see below.

  • Files that are not only untracked, but also ignored. (Note that listing a tracked file as ignored has no effect on the tracked file: such a file is tracked and not ignored. "Ignored" is a status that applies only to a file that is also, already, untracked. In any case "untracked" merely means "not in the index".) This is the same as the untracked files in the previous point: we merely divide them into "untracked and ignored" vs "untracked and not-ignored". In fact, if you choose to use git ls-files --other, you get all untracked files, including ignored ones, unless you add the --exclude-standard option.

Using git stash save to get untracked or all files

Note that we have had to use at least three Git commands:

git format-patch
git diff --cached
git diff

and we still do not have the untracked and/or ignored files. If we use git stash save, we can get the last. We could even drop one (but only one) git diff while adding at least one more git diff. However, other things become more complex. In particular, git stash save does nothing if there is nothing to stash, and we must check for this:

M$ old_stash=$(git rev-parse -q --verify refs/stash)
M$ git stash save --untracked # assuming you do not want ignored files
M$ new_stash=$(git rev-parse -q --verify refs/stash)
M$ [ "$new_stash" != "$old_stash" ] && have_stash=true || have_stash=false

If you want all files (untracked+ignored), use --all instead of --untracked.

We also need the hash ID of the empty tree:

M$ empty_tree=$(git hash-object -t tree /dev/null)

We can now get the diff from the empty tree to the commit containing the untracked-minus-ignored (--untracked) or untracked-including-ignored (--all) files:

M$ ($have_stash && git diff $empty_tree ${new_stash}^3) > /tmp/extra
M$ $have_stash && git stash pop

It's now safe to generate the other diffs, or you can do them before the stash save and maybe-pop. (And if you turn the above into a script there are somewhat less clumsy ways to write it.)

If you just want a single diff

If you're not interested in preserving individual commits, you can just run a single git diff from either commit * or commit L in the original diagram above. (If there are no new commits on origin/branch these are the same commit.) This won't show what's in the index (staged) or work-tree (unstaged), of course. As before, you can play a few tricks with git stash save to make commits from the index and work-tree and (optionally) --untracked or --all files. Since the work-tree commit from this action is, internally, a merge commit, you must manually diff it against the interesting parent. As before, untracked files should be diff-ed against an empty tree.

Upvotes: 1

Related Questions