git-for-windows / git

A fork of Git containing Windows-specific patches.
http://gitforwindows.org/
Other
8.28k stars 2.51k forks source link

"git checkout head" inconsistent behavior #852

Open camsteffen opened 8 years ago

camsteffen commented 8 years ago

Running git checkout head results in a detached HEAD and git checkout HEAD does nothing (as expected). Since Windows is case-insensitive, each command should match what happens in Unix for git checkout HEAD.

Windows 10 64-bit git version 2.7.2.windows.1

This issue was previously reported on the old msysgit repo https://github.com/msysgit/msysgit/issues/114

PhilipOakley commented 8 years ago

From: Cameron Steffen Running git checkout head results in a detached HEAD and git checkout HEAD does nothing (as expected). Since Windows is case-insensitive, each command should match what happens in Unix for git checkout HEAD.

Windows 10 64-bit git version 2.7.2.windows.1

This issue was previously reported on the old msysgit repo msysgit/msysgit#114

I think you have it in one. As you said "Windows is case-insensitive". It's not Unix, so I think we are stuck with the current situation.

A bigger warning (but when) might be appropriate to help 'learn' [1] new users. Alternatively doe deep code changes to the Linux version to detect wrong case scenarios (where the thing provided has a name with a different case to the thing requested) would be needed.

The issue comes up in various guises quite often, and at the moment is essentially intractable, so ends up as a "Can't Fix" (the other similar issue is the allowable characters and phrases in filenames, such as ';' and 'AUX..'; and filename lengths)

If you can find a way of, without to great a re-write to the Git Linux code, detecting that the thing name and the requested name don't match, then I think we'd all like to see if it can be made to fly. Perhaps start with laooking at a regular branch name with case sensitivity - I didnt see an easy way.

Philip [1] "I'll learn you": (locally) a historical turn of phrase for education^W teaching via corporal punishment - the 'clue bat' in modern idiom - thankfully now gone in most places.

PhilipOakley commented 8 years ago

I meant to say ‘:’ as a not allowable char, though ‘;’ isn’t desired. https://support.microsoft.com/en-us/kb/177506

From: Philip Oakley [mailto:notifications@github.com]

similar issue is the allowable characters and phrases in filenames, such as ';'

camsteffen commented 8 years ago

I think you have it in one. As you said "Windows is case-insensitive". It's not Unix, so I think we are stuck with the current situation.

I don't see why we are stuck. This is what I expect to happen.

  1. User enters git checkout head
  2. Git for Windows resolves head to HEAD
  3. Git for Windows performs git checkout HEAD
  4. Nothing happens

Please let me know if this step by step is not representative of what actually happens. The problem is not in step 2 but step 3. So the case-insensitivity is not really the core problem.

PhilipOakley commented 8 years ago

I think you have it in one. As you said "Windows is case-insensitive". It's not Unix, so I think we are stuck with the current situation. I don't see why we are stuck. This is what I expect to happen.

  1. User enters 'git checkout head'
  2. Git for Windows resolves 'head' to 'HEAD'
  3. Git for Windows performs git checkout HEAD
  4. Nothing happens

Please let me know if this step by step is not representative of what actually happens. The problem is not in step 2 but step 3. So the case-insensitivity is not really the core problem.

The problem is distinguishing G4W from Git.

  1. User enters 'git checkout head'
    1. Git for Windows passses command to Git, with pathname substiturion (none given)
  2. Git requests 'git checkout head' passing 'head' through its ref DWIM machinery 3a. Git requests Wndows to get the ref (via the file named) 'head' (perfectly valid Windows filename, no conversion needed beyond the /\/\'s in the path) 3b. Windows returns contents of ref file 'HEAD' (case insensitive) 3c. Git happily accepts the contents (Git code doesn't care, it doesn't even know what the command line string was at this point)
  3. HEAD is checked out.

So the 'problem' is Linux Git's code - see the caveat to 3c.

Unless upstream can be persuaded to include a load on redunant name checking code, with all the architectural upheaval that may entail (which is the 'problem' - there's no benefit for them), we don't have an easy way (with fast execution) to insert a shim into the code do actually do the checking.

I say 'We', in this context it's probably dscho and some of the other better coders than I, that would be needed to write a Windows fopen() shim that is explicitly case sensitive (there may even be one, I just don't know). In this case ('branch' checkout) it would need to be case insensitive on the path-to part, while sensitive on the final filename part.

Then the correct points in the upstream code (some fopen()'s are more important than others) would/should be changed to some macro expansion that would insert the Windows checks when compiled on windows.

Ensuring that this is fast, just like real git, is an awkward task, as dscho has noted elsewhere (quite vocally, 'cos it matters)

Philip (hope this formats ok..)

My rambling notes from a previous report are attached.

Git Checkout Case sensitivity

User Deigo j. (saintplay96@gmail.com) reported https://groups.google.com/forum/#!topic/git-users/EryCnwKL4_E that he could create a branch 'name' and then checkout Name sucessuflly but the HEAD and branch commands didn't think he had checked 'it' out (despite the earlier sucess)

This is a probably File system case insensitivity issue.

The test

git init casetest cd casetest/

temp.txt git add -A & git commit -m'first' git status git branch case git branch git status git checkout Case

success!

git branch

note that no branch is marked as current, but neither are we detached head..

git status cat .git/HEAD

HEAD records the requested name, not the branch that was actually checked out!

Trace

checkout.c

L811 static int switch_branches(c

L836 ret = merge_working_tree(opts, &old, new, &writeout_error)

L846 update_refs_for_switch(opts, &old, new);

L613 static void update_refs_for_switch

L673 create_symref("HEAD", new->path, msg.buf);

L688 fprintf(stderr, _("Switched to branch '%s'\n") # this is the 'success' message

Summary, the success message is printed after the create_symref for HEAD as part of update_refs_for_switch, which is itself called from switch branches after it has done merge_working_tree. The merge working tree maybe should have noticed that what it asked for, wasn't what it got.

now to delve into the merge_working_tree and the 'new' struct and msg.buf

checkout.c

L471 static int merge_working_tree

**\ This merge_working_tree looks to be it (where the problem is hidden)!

in

L1105 static int parse_branchname_arg(

L1225 new->name = arg;

in static void update_refs_for_switch( #L613

L641 create_branch(old->name, opts->new_branch, new->name,

L647 new->name = opts->new_branch;

meanwhile

L672 } else if (new->path) { /* Switch branches. */

from the top of cmd_checkout #L1338 we find

L1452 int n = parse_branchname_arg( (is this getting the name??)

defined at #L1105 parse_branchname_arg() we find

L1118 if (get_sha1_mb(arg, rev)) _mb: merge base i.e. A...B syntax, the if must be saying it wasn't that..

after the get_sha1_mb() we, either way, do the

but before, at #L1192 check_filename(

L1225

new->name = arg;
setup_branch_path(new);

trace eventually to (in refs.c #L1603) stat_ref: if (bad_name) {

L1338 int cmd_checkout(i

contains at the end..

L1498 return checkout_branch(&opts, &new);

so inside (see above)

Windows methods of getting true file name http://stackoverflow.com/questions/4763117/how-can-i-obtain-the-case-sensitive-path-on-windows/4763137#4763137 http://stackoverflow.com/questions/478826/c-sharp-filepath-recasing/479198#479198 http://stackoverflow.com/questions/74451/getting-actual-file-name-with-proper-casing-on-windows

Need to look at the alternates such as the git status, git branch etc which actually look at the ref and the HEAD.

L1338 int cmd_checkout() git\builtin\checkout.c

|#L1433 * Extract branch name from command line arguments |#L1452 int n = parse_branchname_arg( (is this getting the name??)

L1105 parse_branchname_arg() git\builtin\checkout.c

| #L1181 if (get_sha1_mb(arg, rev)) _note _mb: merge base i.e. A...B syntax, the if must be saying it wasn't that.. | #L1192 check_filename( | #L1225 new->name = arg; | #L1226 setup_branch_path(new);

L460 setup_branch_path()

| #L464 strbuf_branchname

L1166 strbuf_branchname() git\sha1_name.c

| #L1169 int used = interpret_branch_name

L1126 interpret_branch_name()

| #L1130 int len = interpret_nth_prior_checkout Parse @{-N} syntax | #L1152 len = interpret_branch_mark

L1072 interpret_branch_mark()

| #L1092 branch = branch_get(name_str);

L1681 branch_get();

| #L1689 ret = make_branch(name, 0); adds "refs/heads/" prefix to new->path | trace eventually to (in refs.c #L1603) stat_ref: if (bad_name) { |at the end of cmd_checkout |#L1498 return checkout_branch(&opts, &new);

camsteffen commented 8 years ago

Thanks for the explanation, Philip. What I understand is that this is a very minor bug and the potential fix involves adding complexity to upstream, Unix-focused code. That's good enough for me. I'll defer closing the issue to someone else. Sorry I'm not much help.

fourpastmidnight commented 7 years ago

It is interesting to note that the Windows NTFS file system is case sensitive. Through a command interpreter that operates case-sensitively (and uses case-sensitive Windows file system APIs), you could create a file named foo and a file named FOO, or with any combination of casing, within a single directory on an NTFS volume.

However, because traditionally the FAT file system was not case-sensitive, the Windows Explorer Shell and the Windows Command Prompt operate over files case-insensitively. (That last statement is a bit of an "over-simplification"--there are some weird quirks about how Windows treats files with names that differ only in their casing that are mentioned in the article I linked to above, but that's only a short synopsis and I'm sure there's a lot more weirdness going on with respect to file casing which could be found with additional searching). There is a way to turn off case-insensitivity with case preservation to require that file system operations respect the casing of filenames (at the file system level); however, for normal use of Windows, you probably wouldn't want to do that because some (most?) programs would most likely not work correctly.

In any event, I don't believe this is a bug. What you are experiencing is the result of legacy behavior persisting to this day in Windows for compatibility reasons (and frankly, ease of use--though, that's questionable).

Quite simply, this most likely will never be fixed. For one thing, all nices have case-sensitive shells and file systems (excluding, of course, FAT--which is not a default file system on any nix)--all that I've come into contact with, anyway. The NTFS file system is case-sensitive as well--and who's to say that sometime in the next 5 - 10 years Windows won't in fact default to a fully case-sensitive Explorer shell and command interpreter? Lest you laugh at me, no one ever thought MS would embrace Linux as much as they have over the last 2 years or so, nor open source as much software as they currently have. Things change, and case-sensitivity would be a huge change (for Windows and its ecosystem of users and software), and one that won't happen overnight, but it is possible. This may be even particularly more true now that we have things like the Windows Subsystem for Linux.