简体   繁体   中英

Commit to git with a different path?

Say I have a directory /home/userX/whatever structured:

./wrapper
./wrapper/folder_A
./wrapper/folder_A/file_1
./wrapper/folder_A/file_2
./wrapper/folder_B
./wrapper/folder_B/file_3
./wrapper/folder_B/file_4

I want to commit them into a git repo /home/userX/somewhere/repo.git with this structure inside the git tree:

./blah
./blah/folder_A
./blah/folder_A/file_1
./blah/folder_A/file_2
./blah/folder_B
./blah/folder_B/file_3
./blah/folder_B/file_4

WITHOUT having to copy or move those files 1~4 to another folder or having to touch the system privilege (the userX is not a sudoer).

Is it possible? Any illustration?

Your requirements aren't what I'd call crystal clear around the general file ownership area, but here are a few ways to do it; hopefully you'll find one that fits your actual need.

Level 0—don't do that

You can most probably talk yourself out of really needing to do this. It's never going to be as atomic as you'd like it to be, so spare yourself the complexity.

If that fails…

Level 1—hardlink

With most operating systems, you can cheat your way out by faking duplication at a filesystem level. You seem to refuse this because root or sudo is needed, but it's so much easier than anything else I have to mention it for a start.

In the olden days you'd just have hardlinked your whatever/wrapper directory to cheat/blah , but it seems deprecated enough by now that my example will use a bind mount instead.

git clone /home/userX/somewhere/repo.git level1
cd level1
mkdir blah
sudo mount --bind /home/userX/whatever/wrapper blah
git add blah
git commit
git push
sudo umount blah

In plain English: mount the files you want to commit in a place where they make sense to git. Then add, commit and push them to where you want them stored.

Level 2—no cp

You can spare a copy by hashing the files directly in a repository.

git clone /home/userX/somewhere/repo.git level2
cd level2
for f in folder_A/file_1 folder_A/file_2 folder_B/file_3 folder_B/file_4
do
  sha=$(git hash-object -w /home/userX/whatever/wrapper/$f)
  git update-index --add --cacheinfo 644 $sha blah/$f
done

Then commit and push as usual:

git commit
git push

Note that the git hash-object -w command (in addition to the git push , obviously) is the one that copies the files around.

Level 3—no clone

You can commit “directly” to a local bare repository, from the outside. The blob hashing part is the same as for level 2, only without the clone. Building the actual commit is going to be a bit more tricky.

First, we'll need to create an updated index, that references the new files. Since git doesn't really have any command to operate on an external tree (and we don't want to depend too much on the internals' format), we'll use a secondary index that we'll initially copy from the repository's head. Apart from that, it'll look similar to level 2.

mkdir level3
cd level3
export GIT_DIR=/home/userX/somewhere/repo.git
export GIT_INDEX_FILE=hack
git read-tree HEAD
for f in folder_A/file_1 folder_A/file_2 folder_B/file_3 folder_B/file_4
do
  sha=$(git hash-object -w /home/userX/whatever/wrapper/$f)
  git update-index --add --cacheinfo 644 $sha blah/$f
done
tree=$(git write-tree)

Now you can create a commit object and update HEAD.

parent=$(git rev-parse HEAD)
commit=$(git commit-tree -p $parent -m Your_message_here $tree)
git update-ref HEAD $commit $parent

Don't forget to clean up behind you:

rm hack
unset GIT_DIR GIT_INDEX_FILE

Level 4—remotely

It's possible to do all of this remotely, but as far as I know no git plumbing command will help us. git send-pack assumes a local repository, which seems out of scope here. So you'd need to write a dedicated git protocol client.

Closing thoughts

If you aim for serious portability with all this, you ought to:

  • further investigate the --path option of git hash-object
  • make sure you avoid naming collisions on the secondary index name

Import what you want through a sideband index

loaded_tree=$(
    export GIT_INDEX_FILE=$PWD/.git/throwaway
    export GIT_DIR=$PWD/.git
    rm -f .git/throwaway

    export GIT_WORK_TREE=/home/userX/whatever/wrapper
    cd $GIT_WORK_TREE

    git add folder_A folder_B  # add contents (relative to GIT_WORK_TREE)
    git add anything else      # . to repo, record locations ditto in index

    git write-tree             # make an honest tree of it
)

And load it where you want into a new branch

git checkout --orphan newbranch
git read-tree --empty          # (wipe it because `--prefix=` merges)
git read-tree  $loaded_tree --prefix=blah/
git commit -m "You're done."       

This is very possible using git internal commands . Be careful of the following commands as these meddle with the files git uses to represent the file structure, if used incorrectly it could lead to corrupt or incorrect states.

This will follow only the creation of a single file, the rest would follow a similar process. All this commands are run from the repo.git folder mentioned, which is assumed to be a --bare repository:

> cd /home/userX/somewhere

The first step is to create the git object that will contain the file you want to commit. This command will create a compressed copy of your file in the git object storage. This copy is inevitable since it is the internal copy git needs to keep a version of your file:

> git hash-object -w /home/userX/whatever/wrapper/folder_A/file_1
cafebabe00000000000000000000000000000000

The printed hash is the object that was created to store file_1 . The hash is of course fictitious since it depends on the actual contents of the file.

In order to create the subfolders where file_1 is to be located you need to create a tree object for each subfolder level. Notice how the hash created is used in hand for the next subfolder tree:

> echo -e "100644 blob cafebabe00000000000000000000000000000000\tfile_1" | git mktree
1111face11111111111111111111111111111111

> echo -e "040000 tree 1111face11111111111111111111111111111111\tfolder_a" | git mktree
2222face22222222222222222222222222222222

This last hash is the one that will be added the the current tree. If working in a --bare repo we need to create first an clean index. This step can be skipped if you are working in a normal repository since there is an index file already, just make sure git status shows a clean state.

In a --bare repo there should be no index file, but just in case there is, remove it:

> rm index

To create a clean index file we add the contents of the branch we want to commit towards, we will assume master . This command will print the contents of the current tree existing as master and set it as the current index:

> git cat-file -p master^{tree} | git update-index --index-info

Now with a clean index, we add the subdirectory containing the tree that leads to the file that we added manually. read-tree will take the given tree hash and add it to the current index as a subfolder named as stated in --prefix :

> git read-tree --prefix=blah 2222face22222222222222222222222222222222

And then we create yet another tree that stands for the current state of the index. write-tree will read from the current index and generate a tree hash out of it:

> git write-tree
deadbeafaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

This last hash is the one that we will use to create the commit:

> echo "at last a commit" | git commit-tree deadbeafaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -p master
baddcafe11111111111111111111111111111111

Last but not least we update master to point to the new commit:

> git branch --force master baddcafe11111111111111111111111111111111

And with that we managed to add into git a file without having to copy it to the workspace. All this steps is what git add and git commit end up doing. To replicate them through the git internal commands ends up being a lot of work and it is easily prone to accidents. If you really need to do this thread with care and understand the the underlying structure that git handles for you so that you can play with it and modify it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM