Слияние без изменения рабочего каталога - PullRequest
16 голосов
/ 04 августа 2010

У меня есть следующий сценарий:

* ab82147 (HEAD, topic) changes
* 8993636 changes
* 82f4426 changes
* 18be5a3 (master) first

Я хотел бы объединить (без перемотки вперед) topic в master. Это требует от меня:

  • git checkout master
  • git merge --no-ff topic

Но проверка мастера, а затем слияние темы в него приводит к тому, что git меняет мой рабочий каталог (хотя конечный результат идентичен тому, что был до проверки мастера), и проблема, с которой я столкнулся, связана с размером В нашем проекте на его сборку уходит около 30 минут (с IncrediBuild), хотя на самом деле ничего не изменилось и это просто невыносимо.

Итак, я бы хотел получить следующее:

*   9075cf4 (HEAD, master) Merge branch 'topic'
|\  
| * ab82147 (topic) changes
| * 8993636 changes
| * 82f4426 changes
|/  
* 18be5a3 first

Без особого прикосновения к рабочему каталогу (или, по крайней мере, как-то обманывают git).

Ответы [ 5 ]

8 голосов
/ 05 августа 2010

Интересно!Я не думаю, что есть встроенный способ сделать это, но вы должны быть в состоянии выдумать это, используя слесарное дело:

#!/bin/bash

branch=master
# or take an argument:
# if [ $@ eq 1 ];
#      branch="$1";
# fi

# make sure the branch exists
if ! git rev-parse --verify --quiet --heads "$branch" > /dev/null; then
     echo "error: branch $branch does not exist"
     exit 1
fi

# make sure this could be a fast-forward   
if [ "$(git merge-base HEAD $branch)" == "$(git rev-parse $branch)" ]; then
    # find the branch name associated with HEAD
    currentbranch=$(git symbolic-ref HEAD | sed 's@.*/@@')
    # make the commit
    newcommit=$(echo "Merge branch '$currentbranch'" | git commit-tree $(git log -n 1 --pretty=%T HEAD) -p $branch -p HEAD)
    # move the branch to point to the new commit
    git update-ref -m "merge $currentbranch: Merge made by simulated no-ff" "refs/heads/$branch" $newcommit
else
    echo "error: merging $currentbranch into $branch would not be a fast-forward"
    exit 1
fi

Интересно, что строка newcommit=;он использует дерево коммитов для непосредственного создания коммита слияния.Первый аргумент - это дерево для использования;это дерево ГОЛОВА, ветвь, содержимое которой вы хотите сохранить.Сообщение коммита передается на стандартный ввод, а остальные аргументы указывают на родителей, которых должен иметь новый коммит.SHA1 коммита печатается на стандартный вывод, поэтому, если коммит завершился успешно, вы фиксируете его, а затем объединяете этот коммит (это будет ускоренная перемотка вперед).Если вы одержимы, вы можете убедиться, что дерево коммитов выполнено успешно, но это должно быть в значительной степени гарантировано.

Ограничения:

  • Это работает только при слияниях, которые мог бы быть перемотка вперед.Очевидно, что на самом деле вам придется проверить и объединить (возможно, в клоне, чтобы сохранить вашу систему сборки) в этом случае.
  • Сообщение reflog отличается.Я сделал это сознательно, потому что когда вы используете --no-ff, git фактически заставит себя использовать стратегию по умолчанию (рекурсивную), но записать это в reflog будет ложью.
  • Если вы находитесь врежим отсоединенного HEAD, дела пойдут плохо.С этим нужно обращаться особенно.

И да, я проверил это на игрушечном репо, и, похоже, он работает правильно!(Хотя я не старался сломать его.)

3 голосов
/ 05 августа 2010

Самый простой способ, который я могу придумать, - это git clone в отдельную рабочую копию, выполнить слияние там, а затем git pull назад. В этом случае перемотка будет ускоренной и затронет только те файлы, которые действительно изменились.

Конечно, при таком большом проекте создание временных клонов не является идеальным и требует значительного дополнительного пространства на жестком диске. Затраты времени на дополнительный клон могут быть сведены к минимуму (в долгосрочной перспективе), если хранить копию слияния так долго, пока вам не понадобится место на диске.

Отказ от ответственности: я не проверял, что это работает. Я полагаю, что это должно произойти (git не записывает временные метки файлов)

0 голосов
/ 18 сентября 2018
  1. Слияние мастера с темой: git источник слияния / мастер или слияние темы с самим собой
  2. Смена главы мастера: git update-ref refs / заголовки / основные ссылки / головы / тема

Теперь вы можете вернуться в тему для предварительного коммита слияния: git reset HEAD ~

0 голосов
/ 06 августа 2010

Вот разновидность читерской версии.

  1. git stash
  2. git tag tmptag
  3. git merge --no-ff topic
  4. git checkout tmptag (-b tha_brunch)?
  5. git stash pop
  6. git tag -D tmptag
0 голосов
/ 05 августа 2010

Кроме того, вы можете исправить симптомы напрямую, сохраняя и восстанавливая временные метки файлов.Это немного уродливо, но было интересно писать.

Сценарий сохранения / восстановления Python Timestamp

#!/usr/bin/env python

from optparse import OptionParser
import os
import subprocess
import cPickle as pickle

try:
    check_output = subprocess.check_output
except AttributeError:
    # check_output was added in Python 2.7, so it's not always available
    def check_output(*args, **kwargs):
        kwargs['stdout'] = subprocess.PIPE
        proc = subprocess.Popen(*args, **kwargs)
        output = proc.stdout.read()
        retcode = proc.wait()
        if retcode != 0:
            cmd = kwargs.get('args')
            if cmd is None:
                cmd = args[0]
            err = subprocess.CalledProcessError(retcode, cmd)
            err.output = output
            raise err
        else:
            return output

def git_cmd(*args):
    return check_output(['git'] + list(args), stderr=subprocess.STDOUT)

def walk_git_tree(rev):
    """ Generates (sha1,path) pairs for all blobs (files) listed by git ls-tree. """
    tree = git_cmd('ls-tree', '-r', '-z', rev).rstrip('\0')
    for entry in tree.split('\0'):
        print entry
        mode, type, sha1, path = entry.split()
        if type == 'blob':
            yield (sha1, path)
        else:
            print 'WARNING: Tree contains a non-blob.'

def collect_timestamps(rev):
    timestamps = {}
    for sha1, path in walk_git_tree(rev):
        s = os.lstat(path)
        timestamps[path] = (sha1, s.st_mtime, s.st_atime)
        print sha1, s.st_mtime, s.st_atime, path
    return timestamps

def restore_timestamps(timestamps):
    for path, v in timestamps.items():
        if os.path.isfile(path):
            sha1, mtime, atime = v
            new_sha1 = git_cmd('hash-object', '--', path).strip()
            if sha1 == new_sha1:
                print 'Restoring', path
                os.utime(path, (atime, mtime))
            else:
                print path, 'has changed (not restoring)'
        elif os.path.exists(path):
            print 'WARNING: File is no longer a file...'

def main():
    oparse = OptionParser()
    oparse.add_option('--save',
        action='store_const', const='save', dest='action',
        help='Save the timestamps of all git tracked files')
    oparse.add_option('--restore',
        action='store_const', const='restore', dest='action',
        help='Restore the timestamps of git tracked files whose sha1 hashes have not changed')
    oparse.add_option('--db',
        action='store', dest='database',
        help='Specify the path to the data file to restore/save from/to')

    opts, args = oparse.parse_args()
    if opts.action is None:
        oparse.error('an action (--save or --restore) must be specified')

    if opts.database is None:
        repo = git_cmd('rev-parse', '--git-dir').strip()
        dbpath = os.path.join(repo, 'TIMESTAMPS')
        print 'Using default database:', dbpath
    else:
        dbpath = opts.database

    rev = git_cmd('rev-parse', 'HEAD').strip()
    print 'Working against rev', rev

    if opts.action == 'save':
        timestamps = collect_timestamps(rev)
        data = (rev, timestamps)
        pickle.dump(data, open(dbpath, 'wb'))
    elif opts.action == 'restore':
        rev, timestamps = pickle.load(open(dbpath, 'rb'))
        restore_timestamps(timestamps)

if __name__ == '__main__':
    main()

Bash Test Script

#!/bin/bash

if [ -d working ]; then
    echo "Cowardly refusing to mangle an existing 'working' dir."
    exit 1
fi

mkdir working
cd working

# create the repository/working copy
git init

# add a couple of files
echo "File added in master:r1." > file-1
echo "File added in master:r1." > file-2
mkdir dir
echo "File added in master:r1." > dir/file-3
git add file-1 file-2 dir/file-3
git commit -m "r1: add-1, add-2, add-3"
git tag r1
# sleep to ensure new or changed files won't have the same timestamp
echo "Listing at r1"
ls --full-time
sleep 5

# make a change
echo "File changed in master:r2." > file-2
echo "File changed in master:r2." > dir/file-3
echo "File added in master:r2." > file-4
git add file-2 dir/file-3 file-4
git commit -m "r2: change-2, change-3, add-4"
git tag r2
# sleep to ensure new or changed files won't have the same timestamp
echo "Listing at r2"
ls --full-time
sleep 5

# create a topic branch from r1 and make some changes
git checkout -b topic r1
echo "File changed in topic:r3." > file-2
echo "File changed in topic:r3." > dir/file-3
echo "File added in topic:r3." > file-5
git add file-2 dir/file-3 file-5
git commit -m "r3: change-2, change-3, add-5"
git tag r3
# sleep to ensure new or changed files won't have the same timestamp
echo "Listing at r3"
ls --full-time
sleep 5

echo "Saving timestamps"
../save-timestamps.py --save

echo "Checking out master and merging"
# merge branch 'topic'
git checkout master
git merge topic
echo "File changed in topic:r3." > file-2 # restore file-2
echo "File merged in master:r4." > dir/file-3
git add file-2 dir/file-3
git commit -m "r4: Merge branch 'topic'"
git tag r4
echo "Listing at r4"
ls --full-time

echo "Restoring timestamps"
../save-timestamps.py --restore
ls --full-time

Я оставлю в качестве упражнения для читателя очистку скрипта Python для удаления посторонних выводов и добавление лучшей проверки ошибок.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...