1

By using this command

git merge-base --is-ancestor origin/master master

it is possible to check if a fast-forward merge is possible to do or not. But what happens if there are untracked file conflicts? That is the new commit that you want to fast-forward to have introduced a new tracked file that would shadow an untracked file within the repo folders? Is it possible to check if there would be a conflict between tracked and untracked files before a merege/rebase operation (or before you attempt to switch branches) and if so how?

Update: I have read the comments and they have convinced me that the best approach is to attempt an FF-Only merge and see what happens as you have suggested.

Thank you @joanis and @torek for your answers that helped me see the light! ;)

Ermingol
  • 33
  • 4
  • 1
    Presumably the point of this question is you'd like a tool to perform that check without actually doing the merge or checkout, right? The simplest solution is to attempt the checkout, note its return status, and revert it if it was successful, but I'm guessing that's what you'd like to avoid? – joanis May 30 '22 at 12:51
  • 2
    Generally, the thing to do is to try the operation and check whether it succeeded: `git merge --ff-only @{u}`. If it worked, that was a fast-forward and there was no problem. If not, it already told you what the problem was. The "test first, then attempt operation" idea is often tempting but in practice it leads to race-condition errors: the test says "this will work" and then it ... doesn't, because something changed between the test and the attempt. – torek May 30 '22 at 13:02
  • 1
    Python people refer to this as [EAFP](https://stackoverflow.com/q/11360858/1256452). The opposite (LBYL) is the source of numerous security problems in the real world, though EAFP is not a panacea either. – torek May 30 '22 at 13:04
  • @torek experimenting with OP's setup, I notice that if a file exists in `somebranch` and in `currentbranch` with different contents, with local uncommitted changes in addition, `git diff somebranch` will compare my uncommitted file contents to the contents in `somebranch`. However, if the file is not in `currentbranch` at all, `git diff somebranch` compares the file to `/dev/null` instead, ignoring my local untracked contents. I master Git well enough that this is not surprising to me, but I think it'll be quite counter-intuitive to beginners. – joanis May 30 '22 at 13:14
  • Are you aware, by any chance, of a way to ask `git diff` to compare a file that is untracked in the current branch to a version that exists in some other branch? (Without first using `git add`, that is.) – joanis May 30 '22 at 13:15
  • @joanis: unfortunately, no - and of course, you further have to be aware that there *is* this committed (in other commit) vs untracked (not in current commit and index, but in the working tree) file situation in the first place. For scripting, though, you probably want `git ls-files --other` (perhaps with `--exclude-standard` and perhaps, as in this case, not). – torek May 31 '22 at 05:43
  • Yes, I wanted to check first because I'm writing scripts that update branches and they have to handle update of a branch regardless of if it is master/main/develop where I can just force the update since you must work on feature branches and do a controlled integration to master/main/develop. Or on feature branches (or other branches) where I do not want to trash things that should be kept. – Ermingol May 31 '22 at 12:32

1 Answers1

0

I fully agree with @torek's comment, the easiest way to deal with conflicts between files that are untracked in your sandbox but exist in a branch is to try the merge or the checkout, and deal with the problem if a problem is reported. Git produces excellent error messages, read them carefully and use them well!

That being said, it's not that hard to script the query you are asking about.

Step 1 - list all the untracked files

git status will give you a list of untracked files, although not in a script friendly output. However, with --porcelain, you can get output that can be reliably used for scripting:

git status --porcelain --ignored | grep '^??' | sed 's/^.. //'

will list the names of untracked files. Caveat: untracked files located in untracked directories don't actually get listed, this is a bit of a problem with my solution.

@torek suggests a better command, git ls-files --other, but as far as I can tell, it only lists untracked files under the current directory. But if you know you're working from the root of the sandbox, then this will be better:

git ls-files --other

Step 2 - list all the files in your target branch

git ls-tree --full-tree --name-only -r <branch> | cut -f2

will list all the files in <branch>.

Step 3 - Use grep to compare

If the lists produced in steps 1 and 2 have any overlap, you have a conflict between an local untracked file and a tracked file in <branch>.

You can use grep to find that overlap:

git ls-tree --full-tree --name-only -r <branch> | grep -F -x -f <(git status --porcelain --ignored | grep '^??' | sed 's/^.. //')

Better yet, if run from the root of the sandbox:

git ls-tree --full-tree --name-only -r <branch> | grep -F -x -f <(git ls-files --other)

Caveats

If somedir/somefile exists in <branch> but no file in somedir/ is tracked in your current branch, my git ls-tree will show somedir/somefile, while my git status will just show somedir/, so that conflict won't get found by my command. @torek's git ls-files --other fixes that, as long as you're working from the root of the sandbox.

Any other corner case I didn't think of could cause problems too.

So, after all this, I go back to @torek's original recommendation: I don't think you should use this. But I found it informative to try to solve it, and I hope you find my write up informative too.

Edit

Following @torek's feedback, I added the git ls-files --other variant.

joanis
  • 10,635
  • 14
  • 30
  • 40
  • As I noted above, you may want `git ls-files --other` here. The default is *not* to use `--exclude-standard`, which in turn is also what you want. To make the script really reliable, if you have a `'\0'`-capable language, add `-z` to the `git ls-files` command and read that way. – torek May 31 '22 at 05:44
  • @torek Nice command, it's almost perfect, but... the commands I used list files in the whole sandbox/repo even if you're in a subdirectory, but `git ls-files --other`, even when I add `--full-name`, only lists untracked files under the current directory. I was in the process of editing my answer, but while testing the results I can't find a way to make ls-files work as I want anywhere but at the root of a sandbox, which I find unfortunate. – joanis May 31 '22 at 12:46
  • 1
    Hm. `git ls-files --full-name` is the only mention that it works "relative to the current directory". Alas, adding `--full-name` doesn't take it *out* of the current directory either, but you can run `git -C $(git rev-parse --show-toplevel) ls-files`. In theory `--show-cdup` can help too but it (annoyingly) produces the empty string, instead of `.`, when at the top level! – torek May 31 '22 at 13:12